@nocobase/app-template-default 1.0.0-beta.57 → 1.0.0-beta.59

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 (69) hide show
  1. package/AGENTS.md +26 -21
  2. package/README.MD +7 -53
  3. package/cli/plugins.ts +1 -7
  4. package/client/agent-annotations-host.ts +8 -2
  5. package/client/app.ts +0 -2
  6. package/client/components/brand-loading-mark.tsx +46 -0
  7. package/client/components/brand-mark-geometry.ts +7 -0
  8. package/client/components/brand-spinner.tsx +22 -0
  9. package/client/components/brand-watermark.tsx +32 -0
  10. package/client/components/loading.tsx +4 -3
  11. package/client/components/route-overlay.tsx +15 -4
  12. package/client/components/status-badge.tsx +29 -0
  13. package/client/components/ui/alert-dialog.tsx +186 -0
  14. package/client/components/ui/card.tsx +102 -0
  15. package/client/components/ui/dialog.tsx +2 -2
  16. package/client/components/ui/radio-group.tsx +35 -0
  17. package/client/components/ui/spinner.tsx +2 -20
  18. package/client/components/ui/toast.tsx +3 -2
  19. package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +3 -3
  20. package/client/extensions/nocobase-inbox/inbox-item.tsx +4 -3
  21. package/client/layouts/app-layout.tsx +8 -28
  22. package/client/layouts/components/app-sidebar.tsx +4 -4
  23. package/client/layouts/components/header-actions.tsx +3 -36
  24. package/client/layouts/index.ts +0 -1
  25. package/client/lib/toaster.ts +1 -1
  26. package/client/locales/en-US.ts +31 -17
  27. package/client/locales/zh-CN.ts +24 -14
  28. package/client/pages/auth/login.tsx +1 -0
  29. package/client/pages/auth/shared.tsx +9 -9
  30. package/client/pages/home.tsx +80 -7
  31. package/client/plugins.ts +2 -6
  32. package/client/routes.ts +4 -10
  33. package/client/routing/app-router.tsx +5 -55
  34. package/client/routing/route-navigation.ts +1 -1
  35. package/client/styles.css +244 -0
  36. package/client/theme/theme-settings.tsx +22 -20
  37. package/client/theme/themes/compact.css +34 -16
  38. package/client/theme/themes/default.css +34 -16
  39. package/config.example.yml +0 -16
  40. package/eslint.config.js +0 -13
  41. package/index.html +48 -16
  42. package/package.json +24 -30
  43. package/server/config/index.ts +0 -6
  44. package/server/plugins.ts +0 -4
  45. package/tests/components/app-layout-routes.test.tsx +1 -1
  46. package/tests/components/inbox.test.tsx +0 -19
  47. package/tests/components/layout-i18n.test.tsx +5 -92
  48. package/tests/components/primitive-labels.test.tsx +61 -1
  49. package/tests/fixtures/theme-tokens.html +1 -1
  50. package/tests/fixtures/theme-tokens.tsx +7 -0
  51. package/tests/logic/app-server.test.ts +0 -15
  52. package/tests/logic/client-plugin-registry.test.ts +0 -21
  53. package/tests/logic/client-routes.test.ts +2 -6
  54. package/tests/logic/client-shell.test.tsx +11 -107
  55. package/tests/logic/client-theme.test.tsx +17 -1
  56. package/tests/logic/config.test.ts +0 -1
  57. package/tests/logic/plugin-commands.test.ts +1 -1
  58. package/tsconfig.json +1 -4
  59. package/tsconfig.server.json +1 -6
  60. package/client/components/inbox-badge.ts +0 -49
  61. package/client/components/inbox-button.tsx +0 -132
  62. package/client/components/inbox-header-button.tsx +0 -25
  63. package/client/layouts/components/surface-empty.tsx +0 -35
  64. package/client/layouts/settings-layout.tsx +0 -172
  65. package/server/config/ai.ts +0 -27
  66. package/server/config/workflow.ts +0 -24
  67. package/tests/logic/client-settings.test.tsx +0 -841
  68. package/tests/logic/dev-routes-production-build.test.ts +0 -168
  69. package/tests/logic/plugin-page-authz.test.ts +0 -76
@@ -1,168 +0,0 @@
1
- // @vitest-environment node
2
- // Vite's esbuild dependency asserts that `new TextEncoder().encode('')` yields a real Uint8Array, which jsdom's
3
- // globals break. This file drives an actual production build, so it runs under Node rather than the shared jsdom
4
- // environment the React tests use.
5
- import {
6
- mkdtemp,
7
- mkdir,
8
- readFile,
9
- readdir,
10
- rm,
11
- writeFile,
12
- } from 'node:fs/promises';
13
- import { readFileSync } from 'node:fs';
14
- import { findPackageJSON } from 'node:module';
15
- import { tmpdir } from 'node:os';
16
- import path from 'node:path';
17
- import { build } from 'vite';
18
- import { afterAll, beforeAll, describe, expect, it } from 'vitest';
19
-
20
- /**
21
- * The guarantee under test is a build-time one: nothing a plugin declares through `defineDevRoutes()` may reach a
22
- * production bundle. A unit test cannot show that — only a real production build can, because the guarantee comes
23
- * from Vite replacing `import.meta.env.PROD` with `true` and then dropping the branch that becomes unreachable.
24
- *
25
- * The fixture builds against the real `@nocobase/app-client` helpers rather than a stand-in, so the test fails if the
26
- * guard inside `defineDevRoutes()` is ever weakened.
27
- */
28
-
29
- const appClientPackagePath = findPackageJSON(
30
- '@nocobase/app-client',
31
- import.meta.url,
32
- );
33
- if (!appClientPackagePath) {
34
- throw new Error('Cannot resolve @nocobase/app-client/package.json');
35
- }
36
- const appClientPackage = JSON.parse(
37
- readFileSync(appClientPackagePath, 'utf8'),
38
- ) as { exports?: { './plugins'?: { import?: string } } };
39
- const appClientPluginsExport = appClientPackage.exports?.['./plugins']?.import;
40
- if (!appClientPluginsExport) {
41
- throw new Error('@nocobase/app-client does not export ./plugins');
42
- }
43
- const appClientPlugins = path.resolve(
44
- path.dirname(appClientPackagePath),
45
- appClientPluginsExport,
46
- );
47
-
48
- const DEV_PAGE_MARKER = 'dev_page_marker_a7f3c1';
49
- const DEV_ONLY_DEPENDENCY_MARKER = 'dev_only_dependency_marker_a7f3c1';
50
- const SETTINGS_PAGE_MARKER = 'settings_page_marker_a7f3c1';
51
-
52
- let workspace: string;
53
- let bundle: string;
54
-
55
- async function readBundle(outputDirectory: string): Promise<string> {
56
- const entries = await readdir(outputDirectory, {
57
- recursive: true,
58
- withFileTypes: true,
59
- });
60
- const files = entries.filter((entry) => entry.isFile());
61
- const contents = await Promise.all(
62
- files.map((entry) =>
63
- readFile(path.join(entry.parentPath, entry.name), 'utf8'),
64
- ),
65
- );
66
- return contents.join('\n');
67
- }
68
-
69
- beforeAll(async () => {
70
- workspace = await mkdtemp(path.join(tmpdir(), 'nocobase-dev-routes-'));
71
- const source = path.join(workspace, 'src');
72
- await mkdir(source, { recursive: true });
73
-
74
- await writeFile(
75
- path.join(source, 'dev-only-dependency.ts'),
76
- `export const devOnlyDependency = '${DEV_ONLY_DEPENDENCY_MARKER}';\n`,
77
- );
78
- await writeFile(
79
- path.join(source, 'dev-page.tsx'),
80
- `import { devOnlyDependency } from './dev-only-dependency.js';\n\n` +
81
- `export default function DevPage() {\n` +
82
- ` return '${DEV_PAGE_MARKER}' + devOnlyDependency;\n` +
83
- `}\n`,
84
- );
85
- await writeFile(
86
- path.join(source, 'settings-page.tsx'),
87
- `export default function SettingsPage() {\n` +
88
- ` return '${SETTINGS_PAGE_MARKER}';\n` +
89
- `}\n`,
90
- );
91
- // The entry mirrors how a plugin declares both surfaces side by side, so the settings page acts as the control: it
92
- // must survive exactly the build that drops the dev page.
93
- await writeFile(
94
- path.join(source, 'main.ts'),
95
- `import { defineDevRoutes, defineSettingsRoutes } from '@nocobase/app-client/plugins';\n\n` +
96
- `const routes = [\n` +
97
- ` defineSettingsRoutes([\n` +
98
- ` {\n` +
99
- ` name: 'settings-page',\n` +
100
- ` path: '/settings-page',\n` +
101
- ` navigation: { title: 'Settings page' },\n` +
102
- ` componentLoader: () => import('./settings-page.js'),\n` +
103
- ` },\n` +
104
- ` ]),\n` +
105
- ` defineDevRoutes([\n` +
106
- ` {\n` +
107
- ` name: 'dev-page',\n` +
108
- ` path: '/dev-page',\n` +
109
- ` navigation: { title: 'Dev page' },\n` +
110
- ` componentLoader: () => import('./dev-page.js'),\n` +
111
- ` },\n` +
112
- ` ]),\n` +
113
- `];\n\n` +
114
- `globalThis.__routes = routes;\n`,
115
- );
116
-
117
- // Vite decides `import.meta.env.PROD` from NODE_ENV, not from the build mode. Vitest sets NODE_ENV=test, under
118
- // which Vite treats even `mode: 'production'` as a non-production build and leaves the dev branch in place — so the
119
- // variable has to be set for the duration of the build, or this test would assert against a development bundle.
120
- const previousNodeEnv = process.env.NODE_ENV;
121
- process.env.NODE_ENV = 'production';
122
- try {
123
- await build({
124
- root: workspace,
125
- logLevel: 'silent',
126
- resolve: {
127
- alias: [
128
- {
129
- find: '@nocobase/app-client/plugins',
130
- replacement: appClientPlugins,
131
- },
132
- ],
133
- },
134
- build: {
135
- outDir: 'dist',
136
- rollupOptions: { input: path.join(source, 'main.ts') },
137
- },
138
- });
139
- } finally {
140
- if (previousNodeEnv === undefined) {
141
- delete process.env.NODE_ENV;
142
- } else {
143
- process.env.NODE_ENV = previousNodeEnv;
144
- }
145
- }
146
-
147
- bundle = await readBundle(path.join(workspace, 'dist'));
148
- }, 120_000);
149
-
150
- afterAll(async () => {
151
- if (workspace) {
152
- await rm(workspace, { recursive: true, force: true });
153
- }
154
- });
155
-
156
- describe('dev routes in a production build', () => {
157
- it('builds the settings page that acts as the control', () => {
158
- expect(bundle).toContain(SETTINGS_PAGE_MARKER);
159
- });
160
-
161
- it('drops the dev page component', () => {
162
- expect(bundle).not.toContain(DEV_PAGE_MARKER);
163
- });
164
-
165
- it('drops a module only a dev page imports', () => {
166
- expect(bundle).not.toContain(DEV_ONLY_DEPENDENCY_MARKER);
167
- });
168
- });
@@ -1,76 +0,0 @@
1
- // @vitest-environment node
2
-
3
- import {
4
- resolveAppClientContributions,
5
- type AppClientRegisteredRoute,
6
- } from '@nocobase/app-client/plugins';
7
- import { describe, expect, it } from 'vitest';
8
-
9
- import clientPlugins from '../../client/plugins.js';
10
-
11
- // The plugins whose pages the permission workspace offers as page grants. The AI employee settings are granted under
12
- // System management instead, one settings item per page.
13
- const PAGE_GRANT_PLUGINS = [
14
- '@nocobase/app-plugin-users',
15
- '@nocobase/app-plugin-notification',
16
- '@nocobase/app-plugin-api-keys',
17
- '@nocobase/app-plugin-database-explorer',
18
- ];
19
-
20
- describe('plugin page authorization', () => {
21
- it('offers a page grant for each listed plugin', () => {
22
- const resolved = resolveAppClientContributions(
23
- clientPlugins.plugins.map((plugin) => ({
24
- packageName: plugin.packageName,
25
- source: 'plugin' as const,
26
- routes: plugin.routes,
27
- })),
28
- );
29
- const pages = entryPages([
30
- ...resolved.routes,
31
- ...resolved.settingsRouteTree,
32
- ]).filter((route) => PAGE_GRANT_PLUGINS.includes(route.packageName));
33
-
34
- // A page may check another resource type; each plugin still offers a page grant.
35
- const granted = pages.filter(
36
- (route) =>
37
- typeof route.authz === 'object' &&
38
- route.authz.resource.type === 'page' &&
39
- route.authz.action === 'access',
40
- );
41
- expect(new Set(granted.map((route) => route.packageName))).toEqual(
42
- new Set(PAGE_GRANT_PLUGINS),
43
- );
44
- });
45
-
46
- it('checks a System management settings item on every AI employee settings page', () => {
47
- const resolved = resolveAppClientContributions(
48
- clientPlugins.plugins.map((plugin) => ({
49
- packageName: plugin.packageName,
50
- source: 'plugin' as const,
51
- routes: plugin.routes,
52
- })),
53
- );
54
- const pages = entryPages(resolved.settingsRouteTree).filter(
55
- (route) => route.packageName === '@nocobase/app-plugin-ai-employee',
56
- );
57
- expect(pages.length).toBeGreaterThan(0);
58
- for (const page of pages)
59
- expect({ id: page.id, authz: page.authz }).toMatchObject({
60
- id: page.id,
61
- authz: {
62
- resource: { type: 'settings', id: expect.stringMatching(/^ai\./) },
63
- action: 'read',
64
- },
65
- });
66
- });
67
- });
68
-
69
- /** Pages without a page above them: nested pages share their parent's check. */
70
- function entryPages(
71
- routes: readonly AppClientRegisteredRoute[],
72
- ): AppClientRegisteredRoute[] {
73
- return routes.flatMap((route) =>
74
- route.componentLoader ? [route] : entryPages(route.children ?? []),
75
- );
76
- }