@nocobase/app-template-default 1.0.0-beta.80 → 1.0.0-beta.82

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 (48) hide show
  1. package/.agents/rules/shell.md +2 -0
  2. package/.agents/rules/workspace.md +9 -0
  3. package/.prettierignore +3 -0
  4. package/AGENTS.md +2 -1
  5. package/README.MD +4 -0
  6. package/client/components/attachment-list.tsx +589 -0
  7. package/client/components/back-button.tsx +15 -4
  8. package/client/components/breadcrumbs.tsx +12 -1
  9. package/client/extensions/nocobase-file-component-ui/components/file-list.tsx +68 -71
  10. package/client/extensions/nocobase-file-component-ui/components/file-preview-dialog.tsx +26 -12
  11. package/client/extensions/nocobase-file-component-ui/components/file-preview-field.tsx +6 -6
  12. package/client/extensions/nocobase-file-component-ui/components/file-thumbnail.tsx +3 -3
  13. package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +19 -19
  14. package/client/extensions/nocobase-file-component-ui/components/previewers/file-preview-content.tsx +27 -19
  15. package/client/extensions/nocobase-file-component-ui/components/previewers/office-open-xml-preview.tsx +12 -8
  16. package/client/extensions/nocobase-file-component-ui/index.ts +6 -6
  17. package/client/extensions/nocobase-file-component-ui/lib/message-key.ts +14 -11
  18. package/client/extensions/nocobase-file-component-ui/locales/de-DE.ts +49 -0
  19. package/client/extensions/nocobase-file-component-ui/locales/en-US.ts +47 -0
  20. package/client/extensions/nocobase-file-component-ui/locales/es-ES.ts +48 -0
  21. package/client/extensions/nocobase-file-component-ui/locales/fr-FR.ts +49 -0
  22. package/client/extensions/nocobase-file-component-ui/locales/id-ID.ts +46 -0
  23. package/client/extensions/nocobase-file-component-ui/locales/ja-JP.ts +51 -0
  24. package/client/extensions/nocobase-file-component-ui/locales/pt-BR.ts +49 -0
  25. package/client/extensions/nocobase-file-component-ui/locales/vi-VN.ts +47 -0
  26. package/client/extensions/nocobase-file-component-ui/locales/zh-CN.ts +43 -0
  27. package/client/extensions/nocobase-file-component-ui/locales/zh-TW.ts +43 -0
  28. package/client/extensions/nocobase-file-component-ui/types.ts +3 -1
  29. package/client/locales/en-US.ts +2 -0
  30. package/client/locales/system/settings-en-US.ts +3 -0
  31. package/client/locales/system/settings-zh-CN.ts +2 -0
  32. package/client/locales/zh-CN.ts +2 -0
  33. package/client/pages/settings/role-form.tsx +5 -1
  34. package/client/pages/settings/user-form.tsx +13 -1
  35. package/client/pages/settings/users.tsx +1 -1
  36. package/client/routing/route-context.tsx +33 -1
  37. package/eslint.config.js +2 -0
  38. package/package.json +23 -21
  39. package/scripts/check-packages.mjs +46 -0
  40. package/tailwind.config.mjs +3 -9
  41. package/tests/client/list-return-state.test.tsx +202 -0
  42. package/tests/client/role-return.test.tsx +104 -0
  43. package/tests/client/user-drawer.test.tsx +48 -0
  44. package/tests/project/internal-packages.test.ts +98 -0
  45. package/tests/project/tailwind-sources.test.ts +2 -2
  46. package/tests/server/authorization-assignment-safety.test.ts +10 -9
  47. package/tests/server/member-role.test.ts +299 -0
  48. package/client/extensions/nocobase-file-component-ui/README.md +0 -13
@@ -0,0 +1,47 @@
1
+ import type { FileUiResource } from './en-US.js';
2
+ const messages = {
3
+ 'files.ooxmlLoadFailed': 'Không thể hiển thị tệp Office Open XML này.',
4
+ 'files.ooxmlLoadTimedOut':
5
+ 'Bản xem trước không tải xong trong vòng {{minutes}} phút.',
6
+ 'files.previewRequestFailed':
7
+ 'Yêu cầu xem trước không thành công ({{status}}).',
8
+ uploadFailed: 'Tải lên tệp không thành công.',
9
+ fileLimitReached: 'Đã đạt đến số lượng tệp tối đa.',
10
+ fileTooLarge: 'Tệp vượt quá kích thước tối đa.',
11
+ fileTypeNotAllowed: 'Loại tệp không được phép.',
12
+ removalFailed: 'Xóa tệp không thành công.',
13
+ urlNotAllowed: 'Tệp URL không được phép.',
14
+ downloadFailed: 'Tải xuống tệp không thành công.',
15
+ invalidUrl: 'Tệp URL bị thiếu hoặc không được phép.',
16
+ pdfPreviewFailed: 'Không thể tải bản xem trước PDF.',
17
+ pdfPreviewMarkup: 'Tệp URL trả về HTML hoặc XML thay vì PDF.',
18
+ previewFailed: 'Không thể tải bản xem trước tệp.',
19
+ 'files.empty': 'Không có tập tin.',
20
+ 'files.preview': 'Xem trước',
21
+ 'files.download': 'Tải xuống',
22
+ 'files.remove': 'Loại bỏ',
23
+ 'files.chooseMany': 'Chọn tập tin',
24
+ 'files.chooseOne': 'Chọn tập tin',
25
+ 'files.retry': 'Thử lại',
26
+ 'files.done': 'Hoàn thành',
27
+ 'files.uploading': 'Đang tải lên',
28
+ 'files.failed': 'Thất bại',
29
+ 'files.pending': 'Đang chờ',
30
+ 'files.previous': 'Tệp trước đó',
31
+ 'files.next': 'Tập tin tiếp theo',
32
+ 'files.loadingPreview': 'Đang tải bản xem trước...',
33
+ 'files.downloadFile': 'Tải tập tin',
34
+ 'files.previewUnavailable': 'Bản xem trước không khả dụng cho loại tệp này.',
35
+ 'files.imageFailed':
36
+ 'Không thể hiển thị hình ảnh này. Nó có thể bị hỏng hoặc ở định dạng không được hỗ trợ.',
37
+ 'files.officeFailed': 'Office Online không thể tải tệp này.',
38
+ 'files.officeUrl':
39
+ 'Office Online yêu cầu tệp tuyệt đối có thể truy cập internet URL.',
40
+ 'files.cancelFile': 'Hủy {{filename}}',
41
+ 'files.status.uploading': 'Đang tải lên',
42
+ 'files.status.pending': 'Đang chờ',
43
+ 'files.status.error': 'Thất bại',
44
+ };
45
+
46
+ const resource: FileUiResource = { fileUi: messages };
47
+ export default resource;
@@ -0,0 +1,43 @@
1
+ import type { FileUiResource } from './en-US.js';
2
+ const messages = {
3
+ 'files.ooxmlLoadFailed': '无法渲染此 Office Open XML 文件。',
4
+ 'files.ooxmlLoadTimedOut': '预览未能在 {{minutes}} 分钟内加载完成。',
5
+ 'files.previewRequestFailed': '预览请求失败({{status}})。',
6
+ uploadFailed: '文件上传失败。',
7
+ fileLimitReached: '已达到文件数量上限。',
8
+ fileTooLarge: '文件超出大小限制。',
9
+ fileTypeNotAllowed: '不允许上传此文件类型。',
10
+ removalFailed: '文件删除失败。',
11
+ urlNotAllowed: '不允许访问此文件 URL。',
12
+ downloadFailed: '文件下载失败。',
13
+ invalidUrl: '文件 URL 缺失或不允许访问。',
14
+ pdfPreviewFailed: '无法加载 PDF 预览。',
15
+ pdfPreviewMarkup: '文件 URL 返回的是 HTML 或 XML,而不是 PDF。',
16
+ previewFailed: '无法加载文件预览。',
17
+ 'files.empty': '暂无文件。',
18
+ 'files.preview': '预览',
19
+ 'files.download': '下载',
20
+ 'files.remove': '移除',
21
+ 'files.chooseMany': '选择文件',
22
+ 'files.chooseOne': '选择文件',
23
+ 'files.retry': '重试',
24
+ 'files.done': '已完成',
25
+ 'files.uploading': '正在上传',
26
+ 'files.failed': '失败',
27
+ 'files.pending': '等待中',
28
+ 'files.previous': '上一个文件',
29
+ 'files.next': '下一个文件',
30
+ 'files.loadingPreview': '正在加载预览…',
31
+ 'files.downloadFile': '下载文件',
32
+ 'files.previewUnavailable': '此文件类型不支持预览。',
33
+ 'files.imageFailed': '无法显示此图片,文件可能已损坏或格式不受支持。',
34
+ 'files.officeFailed': 'Office Online 无法加载此文件。',
35
+ 'files.officeUrl': 'Office Online 需要可通过互联网访问的文件绝对地址。',
36
+ 'files.cancelFile': '取消 {{filename}}',
37
+ 'files.status.uploading': '正在上传',
38
+ 'files.status.pending': '等待中',
39
+ 'files.status.error': '失败',
40
+ };
41
+
42
+ const resource: FileUiResource = { fileUi: messages };
43
+ export default resource;
@@ -0,0 +1,43 @@
1
+ import type { FileUiResource } from './en-US.js';
2
+ const messages = {
3
+ 'files.ooxmlLoadFailed': '無法呈現此 Office Open XML 檔案。',
4
+ 'files.ooxmlLoadTimedOut': '預覽未在 {{minutes}} 分鐘內完成載入。',
5
+ 'files.previewRequestFailed': '預覽請求失敗 ({{status}})。',
6
+ uploadFailed: '檔案上傳失敗。',
7
+ fileLimitReached: '已達到最大檔案數。',
8
+ fileTooLarge: '檔案超出最大大小。',
9
+ fileTypeNotAllowed: '不允許的檔案型別。',
10
+ removalFailed: '檔案刪除失敗。',
11
+ urlNotAllowed: '不允許使用檔案 URL。',
12
+ downloadFailed: '檔案下載失敗。',
13
+ invalidUrl: '檔案 URL 遺失或不允許。',
14
+ pdfPreviewFailed: '無法載入 PDF 預覽。',
15
+ pdfPreviewMarkup: '檔案 URL 傳回 HTML 或 XML,而不是 PDF。',
16
+ previewFailed: '無法載入檔案預覽。',
17
+ 'files.empty': '沒有檔案。',
18
+ 'files.preview': '預覽',
19
+ 'files.download': '下載',
20
+ 'files.remove': '刪除',
21
+ 'files.chooseMany': '選擇檔案',
22
+ 'files.chooseOne': '選擇檔案',
23
+ 'files.retry': '重試',
24
+ 'files.done': '已完成',
25
+ 'files.uploading': '上傳中',
26
+ 'files.failed': '失敗',
27
+ 'files.pending': '待處理',
28
+ 'files.previous': '上一個檔案',
29
+ 'files.next': '下一個檔案',
30
+ 'files.loadingPreview': '載入預覽...',
31
+ 'files.downloadFile': '下載檔案',
32
+ 'files.previewUnavailable': '此檔案型別無法預覽。',
33
+ 'files.imageFailed': '此影像無法顯示。它可能已損壞或格式不受支援。',
34
+ 'files.officeFailed': 'Office Online 無法載入此檔案。',
35
+ 'files.officeUrl': 'Office Online 需要可透過 Internet 存取的絕對檔案 URL。',
36
+ 'files.cancelFile': '取消 {{filename}}',
37
+ 'files.status.uploading': '上傳中',
38
+ 'files.status.pending': '待處理',
39
+ 'files.status.error': '失敗',
40
+ };
41
+
42
+ const resource: FileUiResource = { fileUi: messages };
43
+ export default resource;
@@ -17,7 +17,7 @@ export interface FileUiLabels {
17
17
  }
18
18
 
19
19
  export interface FileUploadFieldProps {
20
- readonly repository: ClientFileRepository;
20
+ readonly repository: Pick<ClientFileRepository, 'uploadOne' | 'deleteOne'>;
21
21
  readonly value: readonly FileRecord[];
22
22
  readonly onChange: (value: readonly FileRecord[]) => void;
23
23
  readonly onError?: (error: Error) => void;
@@ -47,6 +47,8 @@ export interface FilePreviewDialogProps {
47
47
  readonly initialIndex?: number;
48
48
  readonly open: boolean;
49
49
  readonly onOpenChange: (open: boolean) => void;
50
+ /** Override downloads for both the toolbar and preview fallbacks. */
51
+ readonly onDownload?: (file: FileRecord) => void | Promise<void>;
50
52
  readonly onError?: (error: Error) => void;
51
53
  readonly download?: boolean;
52
54
  readonly labels?: FileUiLabels;
@@ -1,3 +1,4 @@
1
+ import fileUi from '../extensions/nocobase-file-component-ui/locales/en-US.js';
1
2
  import type { LocaleResource } from '@nocobase/i18n';
2
3
 
3
4
  import systemEnUS from './system/en-US.js';
@@ -6,6 +7,7 @@ const enUS = {
6
7
  // The template's own copy: the shell, the sign-in pages, shared components and the homepage. Keep it in
7
8
  // `./system/` so a template upgrade can replace it without touching the application's copy below.
8
9
  ...systemEnUS,
10
+ ...fileUi,
9
11
  // The application's copy goes here, one group per feature. A shared group such as `navigation` is extended by
10
12
  // spreading the system one first, `navigation: { ...systemEnUS.navigation, orders: 'Orders' }`, since a group
11
13
  // written here replaces the system group of the same name rather than merging with it.
@@ -148,6 +148,9 @@ const settings = {
148
148
  'Contact an administrator to add users or grant the permissions needed to create users and assign roles.',
149
149
  rolesReadOnlyEmpty:
150
150
  'Contact an administrator to create a role or grant you permission to create roles.',
151
+ noRoleAccess:
152
+ 'This user can still sign in and use account basics. No business access is granted by roles.',
153
+ noAssignedRoles: 'No assigned roles',
151
154
  noRoles: 'No roles yet',
152
155
  create: 'Create',
153
156
  edit: 'Edit',
@@ -136,6 +136,8 @@ const settings = {
136
136
  usersReadOnlyEmpty:
137
137
  '请联系管理员添加用户,或授予创建用户和分配角色所需的权限。',
138
138
  rolesReadOnlyEmpty: '请联系管理员创建角色,或授予你创建角色的权限。',
139
+ noRoleAccess: '此用户仍可登录并使用账号基础功能,角色未授予业务访问。',
140
+ noAssignedRoles: '未分配角色',
139
141
  noRoles: '暂无角色',
140
142
  create: '创建',
141
143
  edit: '编辑',
@@ -1,9 +1,11 @@
1
+ import fileUi from '../extensions/nocobase-file-component-ui/locales/zh-CN.js';
1
2
  import type { AppResource } from './en-US.js';
2
3
  import systemZhCN from './system/zh-CN.js';
3
4
 
4
5
  const zhCN: AppResource = {
5
6
  // The template's own copy; see `./en-US.ts`.
6
7
  ...systemZhCN,
8
+ ...fileUi,
7
9
  };
8
10
 
9
11
  export default zhCN;
@@ -31,6 +31,7 @@ import {
31
31
  useNavigate,
32
32
  useOutletContext,
33
33
  useParams,
34
+ useResolvedPath,
34
35
  } from 'react-router';
35
36
  import { RouteChildPage } from '#components/route-child-page';
36
37
  import { BackButton } from '#components/back-button';
@@ -122,7 +123,10 @@ function RoleEditor(): ReactElement {
122
123
  : canUpdate &&
123
124
  (!request.data?.data.protection ||
124
125
  request.data.data.protection.allow.includes('update'));
125
- const closeTo = { pathname: '/settings/roles', search: location.search };
126
+ const closeTo = {
127
+ pathname: useResolvedPath('..').pathname,
128
+ search: location.search,
129
+ };
126
130
  const save = async (event: React.FormEvent<HTMLFormElement>) => {
127
131
  event.preventDefault();
128
132
  if (options.error || !catalog || (!creating && request.error)) return;
@@ -174,6 +174,11 @@ export function RoleSelection({
174
174
  </ComboboxList>
175
175
  </ComboboxContent>
176
176
  </Combobox>
177
+ {scope.key === 'app' && selected.length === 0 && (
178
+ <p className='text-sm text-muted-foreground'>
179
+ {t('settings.noRoleAccess')}
180
+ </p>
181
+ )}
177
182
  </div>
178
183
  );
179
184
  }
@@ -223,7 +228,14 @@ function UserEditor(): ReactElement {
223
228
  password: '',
224
229
  };
225
230
  const [roleDraft, setRoleDraft] = useState<Record<string, UserRoleValue>>({});
226
- const roles = { ...(initial?.roleScopes ?? {}), ...roleDraft };
231
+ const defaults = creating
232
+ ? Object.fromEntries(
233
+ (options.data?.data.roleScopes ?? [])
234
+ .filter((scope) => scope.defaultValue !== undefined)
235
+ .map((scope) => [scope.key, scope.defaultValue!]),
236
+ )
237
+ : {};
238
+ const roles = { ...defaults, ...(initial?.roleScopes ?? {}), ...roleDraft };
227
239
  const changedRoles = Object.fromEntries(
228
240
  Object.entries(roleDraft).filter(
229
241
  ([key, value]) =>
@@ -360,7 +360,7 @@ export default function UsersPage(): ReactElement {
360
360
  },
361
361
  );
362
362
  })
363
- .join(', ') || '—',
363
+ .join(', ') || t('settings.noAssignedRoles'),
364
364
  },
365
365
  {
366
366
  id: 'actions',
@@ -4,6 +4,7 @@ import {
4
4
  createContext,
5
5
  useContext,
6
6
  useMemo,
7
+ useState,
7
8
  type PropsWithChildren,
8
9
  type ReactElement,
9
10
  } from 'react';
@@ -26,6 +27,15 @@ export interface RouteTrailEntry {
26
27
  readonly pathname: string;
27
28
  }
28
29
 
30
+ const RouteSearchContext = createContext<ReadonlyMap<string, string>>(
31
+ new Map(),
32
+ );
33
+
34
+ /** Queries belong to the page that was visited, never to a child currently covering it. */
35
+ export function useRouteSearches(): ReadonlyMap<string, string> {
36
+ return useContext(RouteSearchContext);
37
+ }
38
+
29
39
  const RouteTreeContext = createContext<readonly AppClientRegisteredRoute[]>([]);
30
40
  export interface RouteTreeProviderProps extends PropsWithChildren {
31
41
  readonly routes: readonly AppClientRegisteredRoute[];
@@ -42,9 +52,31 @@ export function RouteTreeProvider({
42
52
  children,
43
53
  routes,
44
54
  }: RouteTreeProviderProps): ReactElement {
55
+ const location = useLocation();
56
+ const pathname = location.pathname.replace(/\/+$/u, '') || '/';
57
+ const search = location.search;
58
+ const [visited, setVisited] = useState(() => ({
59
+ pathname,
60
+ search,
61
+ queries: new Map([[pathname, search]]),
62
+ }));
63
+ let queries = visited.queries;
64
+ if (visited.pathname !== pathname || visited.search !== search) {
65
+ // Keep only the active ancestry. A fresh visit (including an empty query) replaces its old state.
66
+ // Derive before rendering children so a detail never records its parameters against the parent.
67
+ queries = new Map(
68
+ [...queries].filter(([path]) =>
69
+ pathname.startsWith(`${path.replace(/\/$/u, '')}/`),
70
+ ),
71
+ );
72
+ queries.set(pathname, search);
73
+ setVisited({ pathname, search, queries });
74
+ }
45
75
  return (
46
76
  <RouteTreeContext.Provider value={routes}>
47
- {children}
77
+ <RouteSearchContext.Provider value={queries}>
78
+ {children}
79
+ </RouteSearchContext.Provider>
48
80
  </RouteTreeContext.Provider>
49
81
  );
50
82
  }
package/eslint.config.js CHANGED
@@ -7,5 +7,7 @@ export default createApplicationConfig({
7
7
  'client-old/**',
8
8
  'public/r/**',
9
9
  'storage/**',
10
+ // Internal workspace packages own their lint and TypeScript configurations.
11
+ 'packages/**',
10
12
  ],
11
13
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nocobase/app-template-default",
3
3
  "displayName": "Default Template",
4
- "version": "1.0.0-beta.80",
4
+ "version": "1.0.0-beta.82",
5
5
  "type": "module",
6
6
  "prettier": "@nocobase/dev-config/prettier",
7
7
  "engines": {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "nocobase": {
11
11
  "templateKind": "app",
12
- "defaultTemplateVersion": "1.0.0-beta.80"
12
+ "defaultTemplateVersion": "1.0.0-beta.82"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@ai-sdk/react": "4.0.131",
@@ -75,7 +75,7 @@
75
75
  "@types/jsdom": "^30.0.0",
76
76
  "@nocobase/app-client": "^4.0.0-beta.3",
77
77
  "@nocobase/app-plugin-template-print": "0.0.2-beta.2",
78
- "@nocobase/app-skills": "0.1.0-beta.50",
78
+ "@nocobase/app-skills": "0.1.0-beta.52",
79
79
  "@nocobase/app-testing": "0.1.0-beta.6",
80
80
  "@nocobase/app-websocket": "^0.1.0-beta.1",
81
81
  "@nocobase/dev-config": "0.1.0-beta.25",
@@ -113,7 +113,8 @@
113
113
  "AGENTS.md",
114
114
  ".agents/rules",
115
115
  "CLAUDE.md",
116
- "playwright.config.ts"
116
+ "playwright.config.ts",
117
+ "scripts/check-packages.mjs"
117
118
  ],
118
119
  "publishConfig": {
119
120
  "access": "public"
@@ -140,38 +141,38 @@
140
141
  "@oclif/core": "^4.14.0",
141
142
  "better-auth": "1.7.5",
142
143
  "hono": "^4.13.2",
143
- "@nocobase/app-plugin-agents": "^1.0.0-beta.25",
144
- "@nocobase/app-plugin-api-keys": "^1.0.0-beta.18",
145
- "@nocobase/app-plugin-authentication": "^2.0.0-beta.14",
146
- "@nocobase/app-plugin-authorization": "^1.0.0-beta.31",
147
- "@nocobase/app-plugin-database-explorer": "^1.0.0-beta.16",
148
- "@nocobase/app-plugin-foundation": "^0.1.0-beta.7",
144
+ "@nocobase/app-plugin-agents": "^1.0.0-beta.27",
145
+ "@nocobase/app-plugin-api-keys": "^1.0.0-beta.19",
146
+ "@nocobase/app-plugin-authentication": "^2.0.0-beta.15",
147
+ "@nocobase/app-plugin-authorization": "^1.0.0-beta.32",
148
+ "@nocobase/app-plugin-database-explorer": "^1.0.0-beta.17",
149
+ "@nocobase/app-plugin-file": "^1.0.0-beta.27",
150
+ "@nocobase/app-plugin-foundation": "^0.1.0-beta.9",
149
151
  "@nocobase/app-plugin-i18n": "^1.0.0-beta.18",
150
- "@nocobase/app-plugin-file": "^1.0.0-beta.26",
151
- "@nocobase/app-plugin-knowledge": "^0.1.0-beta.12",
152
+ "@nocobase/app-plugin-knowledge": "^0.1.0-beta.13",
152
153
  "@nocobase/app-plugin-mail": "^1.0.0-beta.17",
153
- "@nocobase/app-plugin-notification": "^2.0.0-beta.0",
154
+ "@nocobase/app-plugin-notification": "^2.0.0-beta.1",
154
155
  "@nocobase/app-plugin-notification-in-app": "^2.0.0-beta.0",
155
156
  "@nocobase/app-plugin-notification-providers": "^1.0.0-beta.15",
156
157
  "@nocobase/app-plugin-scheduler": "^1.0.0-beta.20",
157
- "@nocobase/app-plugin-users": "^2.0.0-beta.9",
158
+ "@nocobase/app-plugin-users": "^2.0.0-beta.10",
158
159
  "@nocobase/app-server": "^3.0.0-beta.3",
159
160
  "@nocobase/config": "^0.1.0-beta.3",
160
161
  "@nocobase/db": "^1.0.0-beta.22",
161
162
  "@nocobase/db-sqlite": "^0.1.0-beta.6",
162
163
  "@nocobase/service-provider": "^0.0.2-beta.2",
163
164
  "@nocobase/snowflake": "^1.0.0-beta.4",
164
- "@nocobase/app-plugin-authz-sharing-rules": "^1.0.0-beta.15",
165
- "@nocobase/app-plugin-authz-default-access": "^1.0.0-beta.15",
166
- "@nocobase/app-plugin-authz-restriction-rules": "^1.0.0-beta.14",
165
+ "@nocobase/app-plugin-authz-default-access": "^1.0.0-beta.16",
166
+ "@nocobase/app-plugin-authz-sharing-rules": "^1.0.0-beta.16",
167
+ "@nocobase/app-plugin-authz-restriction-rules": "^1.0.0-beta.15",
167
168
  "@nocobase/authorization": "^1.0.0-beta.15",
168
169
  "@nocobase/queue": "^0.1.0-beta.9",
169
170
  "@nocobase/jobs": "^0.1.0-beta.4",
170
171
  "@nocobase/lifecycle": "^0.1.0-beta.3",
171
172
  "@nocobase/i18n": "^2.0.0-beta.1",
172
- "@nocobase/repository-input": "^0.1.0-beta.3",
173
173
  "@nocobase/caching": "^0.1.0-beta.3",
174
- "@nocobase/app-cli": "^1.0.0-beta.27",
174
+ "@nocobase/repository-input": "^0.1.0-beta.3",
175
+ "@nocobase/app-cli": "^1.0.0-beta.28",
175
176
  "@nocobase/realtime": "^0.1.0-beta.3"
176
177
  },
177
178
  "description": "NocoBase 3 application template with the standard product capabilities.",
@@ -188,7 +189,7 @@
188
189
  "dev": "nocobase dev",
189
190
  "build": "nocobase build",
190
191
  "start": "nocobase start",
191
- "check": "pnpm lint && pnpm format:check && pnpm typecheck && pnpm test && pnpm build",
192
+ "check": "pnpm check:packages && pnpm lint && pnpm format:check && pnpm typecheck && pnpm test && pnpm build",
192
193
  "fix": "pnpm lint:fix && pnpm format",
193
194
  "typecheck": "tsc --noEmit && tsc -p tsconfig.node.json --noEmit && tsc -p tsconfig.server.json --noEmit",
194
195
  "lint": "eslint . --max-warnings 0",
@@ -198,6 +199,7 @@
198
199
  "test": "vitest run",
199
200
  "test:watch": "vitest",
200
201
  "test:coverage": "vitest run --coverage",
201
- "test:e2e": "playwright test --pass-with-no-tests"
202
+ "test:e2e": "playwright test --pass-with-no-tests",
203
+ "check:packages": "node scripts/check-packages.mjs"
202
204
  }
203
205
  }
@@ -0,0 +1,46 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ // Ask pnpm for exactly the packages the following check selects, respecting workspace exclusions.
6
+ const root = path.resolve(import.meta.dirname, '..');
7
+ const filter = './packages/**';
8
+ const listed = spawnSync(
9
+ 'pnpm',
10
+ ['--filter', filter, 'list', '--depth', '-1', '--json'],
11
+ {
12
+ cwd: root,
13
+ encoding: 'utf8',
14
+ },
15
+ );
16
+ if (listed.error) throw listed.error;
17
+ if (listed.status !== 0) {
18
+ process.stderr.write(listed.stderr);
19
+ process.exit(listed.status ?? 1);
20
+ }
21
+ const packages = JSON.parse(listed.stdout || '[]');
22
+ const missing = packages.filter(({ path: directory }) => {
23
+ const manifest = JSON.parse(
24
+ readFileSync(path.join(directory, 'package.json'), 'utf8'),
25
+ );
26
+ return (
27
+ typeof manifest.scripts?.check !== 'string' ||
28
+ !manifest.scripts.check.trim()
29
+ );
30
+ });
31
+ if (missing.length > 0) {
32
+ console.error(
33
+ 'Every internal package must provide a non-empty check script:',
34
+ );
35
+ for (const entry of missing)
36
+ console.error(` ${path.relative(root, entry.path)}`);
37
+ process.exit(1);
38
+ }
39
+ if (packages.length > 0) {
40
+ const result = spawnSync('pnpm', ['--filter', filter, 'check'], {
41
+ cwd: root,
42
+ stdio: 'inherit',
43
+ });
44
+ if (result.error) throw result.error;
45
+ process.exitCode = result.status ?? 1;
46
+ }
@@ -5,18 +5,12 @@
5
5
  // nothing, in this repository and in a generated application alike. Reading the directory first and resolving each
6
6
  // match to its real path gets past that, because `readdirSync` sees the symlinks and `realpathSync` resolves them.
7
7
  //
8
- // Client runtime files and canonical Registry source are both searched. A workspace plugin exposes TypeScript sources,
9
- // while an installed one may expose build output plus application-owned Registry recipes. Missing directories are skipped.
8
+ // Scan plugin runtime source in a workspace and compiled output in installed packages. UI Library items installed
9
+ // into the application are already covered by its own client source. Missing directories are skipped.
10
10
  import { existsSync, globSync, readdirSync, realpathSync } from 'node:fs';
11
11
  import path from 'node:path';
12
12
 
13
- const SOURCE_DIRECTORIES = [
14
- 'client',
15
- 'dist/client',
16
- 'src',
17
- 'dist/src',
18
- 'registry',
19
- ];
13
+ const SOURCE_DIRECTORIES = ['client', 'dist/client', 'src', 'dist/src'];
20
14
  const SCANNED_FILES = '**/*.{js,jsx,ts,tsx}';
21
15
 
22
16
  /** Every scannable directory a workspace or installed `@nocobase` package exposes. */