@qlover/create-app 0.7.7 → 0.7.8

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 (46) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/next-app/build/generateLocales.ts +1 -1
  5. package/dist/templates/next-app/config/IOCIdentifier.ts +15 -2
  6. package/dist/templates/next-app/config/Identifier/common.error.ts +7 -0
  7. package/dist/templates/next-app/package.json +1 -1
  8. package/dist/templates/next-app/public/locales/{en/common.json → en.json} +2 -1
  9. package/dist/templates/next-app/public/locales/{zh/common.json → zh.json} +2 -1
  10. package/dist/templates/next-app/src/app/[locale]/layout.tsx +8 -20
  11. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +6 -7
  12. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +24 -11
  13. package/dist/templates/next-app/src/app/[locale]/page.tsx +14 -1
  14. package/dist/templates/next-app/src/base/cases/DialogHandler.ts +92 -0
  15. package/dist/templates/next-app/src/base/cases/NavigateBridge.ts +16 -0
  16. package/dist/templates/next-app/src/base/cases/PageParams.ts +74 -0
  17. package/dist/templates/next-app/src/base/cases/RouterService.ts +35 -0
  18. package/dist/templates/next-app/src/base/cases/ServerAuth.ts +17 -0
  19. package/dist/templates/next-app/src/base/cases/ServerErrorHandler.ts +27 -0
  20. package/dist/templates/next-app/src/base/port/IOCInterface.ts +24 -0
  21. package/dist/templates/next-app/src/base/port/ParamsHandlerInterface.ts +11 -0
  22. package/dist/templates/next-app/src/base/port/RouterInterface.ts +11 -0
  23. package/dist/templates/next-app/src/base/port/ServerAuthInterface.ts +3 -0
  24. package/dist/templates/next-app/src/base/port/ServerInterface.ts +12 -0
  25. package/dist/templates/next-app/src/base/types/PageProps.ts +9 -0
  26. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +2 -39
  27. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +78 -0
  28. package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +37 -0
  29. package/dist/templates/next-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +20 -23
  30. package/dist/templates/next-app/src/core/globals.ts +3 -0
  31. package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +52 -0
  32. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +63 -0
  33. package/dist/templates/next-app/src/i18n/request.ts +1 -2
  34. package/dist/templates/next-app/src/i18n/routing.ts +3 -3
  35. package/dist/templates/next-app/src/middleware.ts +6 -3
  36. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +9 -4
  37. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +13 -1
  38. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +5 -0
  39. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +34 -0
  40. package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +16 -3
  41. package/dist/templates/next-app/src/uikit/context/IOCContext.ts +9 -2
  42. package/package.json +1 -1
  43. package/dist/templates/next-app/plugins/eslint-plugin-testid.mjs +0 -94
  44. package/dist/templates/next-app/plugins/generateLocalesPlugin.ts +0 -33
  45. package/dist/templates/next-app/src/core/IOC.ts +0 -58
  46. package/dist/templates/next-app/src/server/getServerI18n.ts +0 -26
@@ -3,6 +3,8 @@ import '@ant-design/v5-patch-for-react-19';
3
3
  import { AntdRegistry } from '@ant-design/nextjs-registry';
4
4
  import { AntdThemeProvider } from '@brain-toolkit/antd-theme-override/react';
5
5
  import { ThemeProvider } from 'next-themes';
6
+ import { IOCIdentifier } from '@config/IOCIdentifier';
7
+ import { clientIOC } from '@/core/clientIoc/ClientIOC';
6
8
  import { BootstrapsProvider } from './BootstrapsProvider';
7
9
  import type { CommonThemeConfig } from '@config/theme';
8
10
 
@@ -23,10 +25,13 @@ export function ComboProvider(props: {
23
25
  }) {
24
26
  const { themeConfig, children } = props;
25
27
 
28
+ const IOC = clientIOC.create();
29
+
26
30
  return (
27
31
  <AntdThemeProvider
28
32
  data-testid="ComboProvider"
29
33
  theme={themeConfig.antdTheme}
34
+ staticApi={IOC(IOCIdentifier.DialogHandler)}
30
35
  >
31
36
  <ThemeProvider
32
37
  themes={themeConfig.supportedThemes as unknown as string[]}
@@ -0,0 +1,34 @@
1
+ import { Button } from 'antd';
2
+ import { useCallback } from 'react';
3
+ import {
4
+ AUTH_LOGOUT_DIALOG_CONTENT,
5
+ AUTH_LOGOUT_DIALOG_TITLE
6
+ } from '@config/Identifier';
7
+ import { IOCIdentifier } from '@config/IOCIdentifier';
8
+ import { useI18nInterface } from '../hook/useI18nInterface';
9
+ import { useIOC } from '../hook/useIOC';
10
+ import type { PageI18nInterface } from '@config/i18n';
11
+
12
+ export function LogoutButton() {
13
+ const IOC = useIOC();
14
+ const tt = useI18nInterface({
15
+ title: AUTH_LOGOUT_DIALOG_TITLE,
16
+ content: AUTH_LOGOUT_DIALOG_CONTENT
17
+ } as PageI18nInterface);
18
+
19
+ const onClick = useCallback(() => {
20
+ IOC(IOCIdentifier.DialogHandler).confirm({
21
+ title: tt.title,
22
+ content: tt.content,
23
+ onOk: () => {
24
+ IOC(IOCIdentifier.UserServiceInterface).logout();
25
+ }
26
+ });
27
+ }, [tt, IOC]);
28
+
29
+ return (
30
+ <Button data-testid="LogoutButton" danger onClick={onClick}>
31
+ {tt.title}
32
+ </Button>
33
+ );
34
+ }
@@ -45,6 +45,21 @@ export function ThemeSwitcher() {
45
45
  const { theme, resolvedTheme, setTheme } = useTheme();
46
46
  const mounted = useMountedClient();
47
47
 
48
+ // 如果组件未挂载,返回空的 Select 以避免闪烁
49
+ if (!mounted) {
50
+ return (
51
+ <Select
52
+ data-testid="ThemeSwitcher"
53
+ loading
54
+ value="system"
55
+ options={[]}
56
+ style={{ width: 120 }}
57
+ className="min-w-40 max-w-full"
58
+ disabled
59
+ />
60
+ );
61
+ }
62
+
48
63
  const themeOptions = ['system', ...supportedThemes!].map((themeName) => {
49
64
  const { i18nkey, colors, icons } = colorMap[themeName] || colorMap.light;
50
65
  const [currentColor, normalColor] = colors;
@@ -74,13 +89,11 @@ export function ThemeSwitcher() {
74
89
  return (
75
90
  <Select
76
91
  data-testid="ThemeSwitcher"
77
- loading={!mounted}
78
- value={mounted ? theme : themeOptions[0]?.key}
92
+ value={theme}
79
93
  onChange={setTheme}
80
94
  options={themeOptions}
81
95
  style={{ width: 120 }}
82
96
  className="min-w-40 max-w-full"
83
- disabled={!mounted}
84
97
  />
85
98
  );
86
99
  }
@@ -1,6 +1,13 @@
1
1
  'use client';
2
2
 
3
3
  import { createContext } from 'react';
4
- import type { IOC } from '@/core/IOC';
4
+ import type { IOCIdentifierMap } from '@config/IOCIdentifier';
5
+ import type {
6
+ IOCContainerInterface,
7
+ IOCFunctionInterface
8
+ } from '@qlover/corekit-bridge';
5
9
 
6
- export const IOCContext = createContext<typeof IOC | null>(null);
10
+ export const IOCContext = createContext<IOCFunctionInterface<
11
+ IOCIdentifierMap,
12
+ IOCContainerInterface
13
+ > | null>(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qlover/create-app",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
4
4
  "description": "Create a new app with a single command",
5
5
  "private": false,
6
6
  "type": "module",
@@ -1,94 +0,0 @@
1
- export const eslintPluginTestId = {
2
- rules: {
3
- 'require-root-testid': {
4
- meta: {
5
- type: 'problem',
6
- docs: {
7
- description:
8
- 'Enforce data-testid attribute on root elements of TSX components',
9
- category: 'Best Practices',
10
- recommended: true
11
- },
12
- fixable: 'code',
13
- schema: []
14
- },
15
- create(context) {
16
- return {
17
- JSXElement(node) {
18
- // 检查是否是组件的根元素
19
- const isRootElement = (node) => {
20
- const parent = node.parent;
21
- if (!parent) return false;
22
-
23
- // 如果父节点是 return 语句或者是箭头函数的主体,说明这是根元素
24
- if (
25
- parent.type === 'ReturnStatement' ||
26
- (parent.type === 'ArrowFunctionExpression' &&
27
- parent.body === node)
28
- ) {
29
- return true;
30
- }
31
-
32
- return false;
33
- };
34
-
35
- if (isRootElement(node)) {
36
- const hasTestId = node.openingElement.attributes.some(
37
- (attr) =>
38
- attr.type === 'JSXAttribute' &&
39
- attr.name.name === 'data-testid'
40
- );
41
-
42
- if (!hasTestId) {
43
- context.report({
44
- node: node.openingElement,
45
- message:
46
- 'Root element of a component must have a data-testid attribute',
47
- fix(fixer) {
48
- // 获取组件名称作为 testid 的默认值
49
- let componentName = '';
50
- let current = node;
51
- while (current) {
52
- if (
53
- current.type === 'FunctionDeclaration' ||
54
- current.type === 'VariableDeclarator'
55
- ) {
56
- componentName = current.id?.name || '';
57
- break;
58
- }
59
- current = current.parent;
60
- }
61
-
62
- const sourceCode = context.getSourceCode();
63
- const openingElement = node.openingElement;
64
- const tagToken = sourceCode.getFirstToken(openingElement);
65
- const hasAttributes = openingElement.attributes.length > 0;
66
-
67
- if (hasAttributes) {
68
- // 如果有其他属性,在第一个属性前添加
69
- const firstAttribute = openingElement.attributes[0];
70
- const indent = sourceCode
71
- .getText()
72
- .slice(0, firstAttribute.range[0])
73
- .match(/\s*$/)[0];
74
- return fixer.insertTextBefore(
75
- firstAttribute,
76
- `data-testid='${componentName || 'component'}'${indent}`
77
- );
78
- } else {
79
- // 如果没有其他属性,在标签名后面添加
80
- return fixer.insertTextAfter(
81
- tagToken,
82
- ` data-testid='${componentName || 'component'}'`
83
- );
84
- }
85
- }
86
- });
87
- }
88
- }
89
- }
90
- };
91
- }
92
- }
93
- }
94
- };
@@ -1,33 +0,0 @@
1
- import { generateLocales } from '../build/generateLocales';
2
- import type { NextConfig } from 'next';
3
-
4
- export function withGenerateLocales(nextConfig: NextConfig = {}) {
5
- return {
6
- ...nextConfig,
7
- onDevelopmentStart: async () => {
8
- try {
9
- await generateLocales();
10
- console.log('✅ Locales generated successfully');
11
- } catch (error) {
12
- console.error('❌ Failed to generate locales:', error);
13
- }
14
- },
15
- webpack: (config, options) => {
16
- const { dev, isServer } = options;
17
-
18
- // 在生产构建开始时生成本地化文件
19
- if (!dev && isServer) {
20
- generateLocales().catch((error) => {
21
- console.error('❌ Failed to generate locales:', error);
22
- });
23
- }
24
-
25
- // 如果原配置有 webpack 配置,则调用它
26
- if (typeof nextConfig.webpack === 'function') {
27
- return nextConfig.webpack(config, options);
28
- }
29
-
30
- return config;
31
- }
32
- };
33
- }
@@ -1,58 +0,0 @@
1
- // ! dont't import tsx, only ts file
2
- import type { InversifyContainer } from '@/base/cases/InversifyContainer';
3
- import { BootstrapClient } from './bootstraps/BootstrapClient';
4
- import type { IOCRegisterInterface } from '@qlover/corekit-bridge';
5
-
6
- /**
7
- * IOC register options
8
- */
9
- export type IocRegisterOptions = {
10
- /**
11
- * The app config
12
- */
13
- appConfig: Record<string, unknown>;
14
- };
15
-
16
- /**
17
- * IOC container
18
- *
19
- * This is a alias of IOCContainerInterface, use it without care about the implementation.
20
- *
21
- * Need to achieve the effect: when the implementation class on the right side of the equal sign changes, the IOCContainer will change automatically
22
- */
23
- export type IOCContainer = InversifyContainer;
24
-
25
- /**
26
- * IOC register interface.
27
- *
28
- * This is shortcut interface, implement this interface, you can use any IOC container.
29
- *
30
- * Need to achieve the effect: when the implementation class on the right side of the equal sign changes, the IOCContainer will change automatically
31
- */
32
- export type IOCRegister = IOCRegisterInterface<
33
- IOCContainer,
34
- IocRegisterOptions
35
- >;
36
-
37
- /**
38
- * IOC function
39
- *
40
- * This is the only and main exported content of the file
41
- *
42
- * @example use A class
43
- * ```ts
44
- * const userService = IOC(UserService);
45
- * ```
46
- *
47
- * @example use A string identifier
48
- *
49
- * string identifier is shortcut for `IOCIdentifierMap` type, string key of `IOCIdentifier`
50
- *
51
- * ```ts
52
- * const logger = IOC('Logger'); // Logger instance
53
- *
54
- * // or
55
- * const logger = IOC(IOCIdentifier.Logger);
56
- * ```
57
- */
58
- export const IOC = BootstrapClient.createSingletonIOC();
@@ -1,26 +0,0 @@
1
- 'use server';
2
- import { getTranslations } from 'next-intl/server';
3
- import type { PageI18nInterface } from '@config/i18n/PageI18nInterface';
4
-
5
- export async function getServerI18n<T extends PageI18nInterface>(params: {
6
- locale: string;
7
- namespace?: string;
8
- i18nInterface: T;
9
- }): Promise<T> {
10
- // Load translation messages from the HomePage namespace
11
- const t = await getTranslations({
12
- locale: params.locale,
13
- namespace: params.namespace
14
- });
15
-
16
- const result = Object.fromEntries(
17
- Object.entries(params.i18nInterface).map(([key, value]) => {
18
- if (typeof value === 'string') {
19
- return [key, t(value)];
20
- }
21
- return [key, value];
22
- })
23
- ) as T;
24
-
25
- return result;
26
- }