@nocobase/app-template-default 1.0.0-beta.58 → 1.0.0-beta.60
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/AGENTS.md +28 -25
- package/README.MD +7 -57
- package/client/agent-annotations-host.ts +8 -2
- package/client/app.ts +0 -2
- package/client/components/brand-loading-mark.tsx +46 -0
- package/client/components/brand-mark-geometry.ts +7 -0
- package/client/components/brand-spinner.tsx +22 -0
- package/client/components/brand-watermark.tsx +32 -0
- package/client/components/loading.tsx +4 -3
- package/client/components/route-overlay.tsx +15 -4
- package/client/components/status-badge.tsx +29 -0
- package/client/components/ui/alert-dialog.tsx +186 -0
- package/client/components/ui/card.tsx +102 -0
- package/client/components/ui/dialog.tsx +2 -2
- package/client/components/ui/radio-group.tsx +35 -0
- package/client/components/ui/spinner.tsx +2 -20
- package/client/components/ui/toast.tsx +3 -2
- package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +3 -3
- package/client/extensions/nocobase-inbox/inbox-item.tsx +4 -3
- package/client/layouts/app-layout.tsx +8 -28
- package/client/layouts/components/app-sidebar.tsx +4 -4
- package/client/layouts/components/header-actions.tsx +3 -36
- package/client/layouts/index.ts +0 -1
- package/client/lib/toaster.ts +1 -1
- package/client/locales/en-US.ts +8 -164
- package/client/locales/system/en-US.ts +344 -0
- package/client/locales/system/zh-CN.ts +320 -0
- package/client/locales/zh-CN.ts +3 -155
- package/client/pages/auth/login.tsx +1 -0
- package/client/pages/auth/shared.tsx +9 -9
- package/client/pages/home/capabilities.ts +52 -0
- package/client/pages/home/index.tsx +190 -0
- package/client/pages/home/prompt-dialog.tsx +122 -0
- package/client/pages/home/use-copy.ts +38 -0
- package/client/plugins.ts +2 -2
- package/client/routes.ts +5 -11
- package/client/routing/app-router.tsx +5 -55
- package/client/routing/route-navigation.ts +1 -1
- package/client/styles.css +244 -0
- package/client/theme/theme-settings.tsx +22 -20
- package/client/theme/themes/compact.css +34 -16
- package/client/theme/themes/default.css +34 -16
- package/eslint.config.js +0 -13
- package/index.html +48 -16
- package/package.json +23 -27
- package/tests/components/app-layout-routes.test.tsx +1 -1
- package/tests/components/home.test.tsx +174 -0
- package/tests/components/inbox.test.tsx +0 -19
- package/tests/components/layout-i18n.test.tsx +5 -92
- package/tests/components/primitive-labels.test.tsx +61 -1
- package/tests/fixtures/theme-tokens.html +1 -1
- package/tests/fixtures/theme-tokens.tsx +7 -0
- package/tests/logic/app-server.test.ts +0 -15
- package/tests/logic/client-plugin-registry.test.ts +0 -21
- package/tests/logic/client-routes.test.ts +2 -6
- package/tests/logic/client-shell.test.tsx +11 -107
- package/tests/logic/client-theme.test.tsx +17 -1
- package/tests/logic/config.test.ts +0 -1
- package/tests/logic/plugin-commands.test.ts +1 -1
- package/tsconfig.json +1 -4
- package/tsconfig.server.json +1 -6
- package/vitest.config.ts +1 -8
- package/client/components/inbox-badge.ts +0 -49
- package/client/components/inbox-button.tsx +0 -132
- package/client/components/inbox-header-button.tsx +0 -25
- package/client/layouts/components/surface-empty.tsx +0 -35
- package/client/layouts/settings-layout.tsx +0 -172
- package/client/pages/home.tsx +0 -16
- package/server/config/workflow.ts +0 -24
- package/tests/logic/client-settings.test.tsx +0 -841
- package/tests/logic/dev-routes-production-build.test.ts +0 -168
- package/tests/logic/plugin-page-authz.test.ts +0 -53
|
@@ -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,53 +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.
|
|
12
|
-
const PAGE_GRANT_PLUGINS = [
|
|
13
|
-
'@nocobase/app-plugin-users',
|
|
14
|
-
'@nocobase/app-plugin-notification',
|
|
15
|
-
'@nocobase/app-plugin-api-keys',
|
|
16
|
-
'@nocobase/app-plugin-database-explorer',
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
describe('plugin page authorization', () => {
|
|
20
|
-
it('offers a page grant for each listed plugin', () => {
|
|
21
|
-
const resolved = resolveAppClientContributions(
|
|
22
|
-
clientPlugins.plugins.map((plugin) => ({
|
|
23
|
-
packageName: plugin.packageName,
|
|
24
|
-
source: 'plugin' as const,
|
|
25
|
-
routes: plugin.routes,
|
|
26
|
-
})),
|
|
27
|
-
);
|
|
28
|
-
const pages = entryPages([
|
|
29
|
-
...resolved.routes,
|
|
30
|
-
...resolved.settingsRouteTree,
|
|
31
|
-
]).filter((route) => PAGE_GRANT_PLUGINS.includes(route.packageName));
|
|
32
|
-
|
|
33
|
-
// A page may check another resource type; each plugin still offers a page grant.
|
|
34
|
-
const granted = pages.filter(
|
|
35
|
-
(route) =>
|
|
36
|
-
typeof route.authz === 'object' &&
|
|
37
|
-
route.authz.resource.type === 'page' &&
|
|
38
|
-
route.authz.action === 'access',
|
|
39
|
-
);
|
|
40
|
-
expect(new Set(granted.map((route) => route.packageName))).toEqual(
|
|
41
|
-
new Set(PAGE_GRANT_PLUGINS),
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
/** Pages without a page above them: nested pages share their parent's check. */
|
|
47
|
-
function entryPages(
|
|
48
|
-
routes: readonly AppClientRegisteredRoute[],
|
|
49
|
-
): AppClientRegisteredRoute[] {
|
|
50
|
-
return routes.flatMap((route) =>
|
|
51
|
-
route.componentLoader ? [route] : entryPages(route.children ?? []),
|
|
52
|
-
);
|
|
53
|
-
}
|