sh-ui-cli 0.22.2 → 0.23.1

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 (111) hide show
  1. package/README.md +19 -2
  2. package/bin/sh-ui.mjs +7 -0
  3. package/data/changelog/versions.json +26 -0
  4. package/package.json +13 -2
  5. package/src/create/cli-args.js +63 -0
  6. package/src/create/generator.js +542 -0
  7. package/src/create/index.mjs +68 -0
  8. package/src/create/plugins/index.js +17 -0
  9. package/src/create/plugins/nextIntl.js +197 -0
  10. package/src/create/plugins/sentry.js +689 -0
  11. package/src/create/theme/decode.js +66 -0
  12. package/src/create/theme/inject.js +111 -0
  13. package/src/mcp.mjs +81 -27
  14. package/src/paths.mjs +5 -0
  15. package/templates/flutter-standalone/README.md +34 -0
  16. package/templates/flutter-standalone/analysis_options.yaml +1 -0
  17. package/templates/flutter-standalone/lib/main.dart +103 -0
  18. package/templates/flutter-standalone/lib/sh_ui/foundation/sh_ui_tokens.dart +389 -0
  19. package/templates/flutter-standalone/pubspec.yaml +20 -0
  20. package/templates/flutter-standalone/sh-ui.config.json +15 -0
  21. package/templates/monorepo/.dockerignore +7 -0
  22. package/templates/monorepo/.eslintrc.js +8 -0
  23. package/templates/monorepo/.prettierrc +17 -0
  24. package/templates/monorepo/README.md +103 -0
  25. package/templates/monorepo/package.json +24 -0
  26. package/templates/monorepo/packages/eslint-config/base.js +31 -0
  27. package/templates/monorepo/packages/eslint-config/fsd.js +119 -0
  28. package/templates/monorepo/packages/eslint-config/next.js +65 -0
  29. package/templates/monorepo/packages/eslint-config/package.json +31 -0
  30. package/templates/monorepo/packages/eslint-config/react-internal.js +36 -0
  31. package/templates/monorepo/packages/typescript-config/base.json +20 -0
  32. package/templates/monorepo/packages/typescript-config/nextjs.json +13 -0
  33. package/templates/monorepo/packages/typescript-config/package.json +5 -0
  34. package/templates/monorepo/packages/typescript-config/react-library.json +8 -0
  35. package/templates/monorepo/packages/ui/ui-apps/.gitkeep +0 -0
  36. package/templates/monorepo/packages/ui/ui-core/eslint.config.js +3 -0
  37. package/templates/monorepo/packages/ui/ui-core/package.json +23 -0
  38. package/templates/monorepo/packages/ui/ui-core/src/lib/utils.ts +6 -0
  39. package/templates/monorepo/packages/ui/ui-core/tsconfig.json +11 -0
  40. package/templates/monorepo/pnpm-workspace.yaml +5 -0
  41. package/templates/monorepo/tsconfig.json +3 -0
  42. package/templates/monorepo/turbo.json +26 -0
  43. package/templates/nextjs-app/.env.example +2 -0
  44. package/templates/nextjs-app/Dockerfile +11 -0
  45. package/templates/nextjs-app/README.md +64 -0
  46. package/templates/nextjs-app/app/layout.tsx +22 -0
  47. package/templates/nextjs-app/app/page.tsx +7 -0
  48. package/templates/nextjs-app/eslint.config.js +10 -0
  49. package/templates/nextjs-app/next.config.ts +12 -0
  50. package/templates/nextjs-app/package.json +45 -0
  51. package/templates/nextjs-app/postcss.config.mjs +1 -0
  52. package/templates/nextjs-app/src/app/layouts/.gitkeep +0 -0
  53. package/templates/nextjs-app/src/app/providers/GlobalProvider/index.tsx +23 -0
  54. package/templates/nextjs-app/src/app/providers/index.tsx +1 -0
  55. package/templates/nextjs-app/src/app/providers/tanstack/QueryClientProvider.tsx +27 -0
  56. package/templates/nextjs-app/src/app/providers/tanstack/TanstackDevtoolsProvider.tsx +13 -0
  57. package/templates/nextjs-app/src/app/providers/theme/ThemeProviders.tsx +12 -0
  58. package/templates/nextjs-app/src/entities/.gitkeep +0 -0
  59. package/templates/nextjs-app/src/features/.gitkeep +0 -0
  60. package/templates/nextjs-app/src/shared/api/.gitkeep +0 -0
  61. package/templates/nextjs-app/src/shared/config/.gitkeep +0 -0
  62. package/templates/nextjs-app/src/shared/hooks/.gitkeep +0 -0
  63. package/templates/nextjs-app/src/shared/lib/.gitkeep +0 -0
  64. package/templates/nextjs-app/src/shared/model/.gitkeep +0 -0
  65. package/templates/nextjs-app/src/shared/ui/.gitkeep +0 -0
  66. package/templates/nextjs-app/src/views/.gitkeep +0 -0
  67. package/templates/nextjs-app/src/widgets/.gitkeep +0 -0
  68. package/templates/nextjs-app/tsconfig.json +23 -0
  69. package/templates/nextjs-app/vitest.config.ts +15 -0
  70. package/templates/nextjs-app/vitest.setup.ts +1 -0
  71. package/templates/nextjs-standalone/.env.example +2 -0
  72. package/templates/nextjs-standalone/.prettierrc +17 -0
  73. package/templates/nextjs-standalone/README.md +77 -0
  74. package/templates/nextjs-standalone/app/globals.css +33 -0
  75. package/templates/nextjs-standalone/app/layout.tsx +22 -0
  76. package/templates/nextjs-standalone/app/page.tsx +7 -0
  77. package/templates/nextjs-standalone/eslint.config.js +162 -0
  78. package/templates/nextjs-standalone/next.config.ts +10 -0
  79. package/templates/nextjs-standalone/package.json +66 -0
  80. package/templates/nextjs-standalone/postcss.config.mjs +5 -0
  81. package/templates/nextjs-standalone/sh-ui.config.json +19 -0
  82. package/templates/nextjs-standalone/src/app/layouts/.gitkeep +0 -0
  83. package/templates/nextjs-standalone/src/app/providers/GlobalProvider/index.tsx +23 -0
  84. package/templates/nextjs-standalone/src/app/providers/index.tsx +1 -0
  85. package/templates/nextjs-standalone/src/app/providers/tanstack/QueryClientProvider.tsx +27 -0
  86. package/templates/nextjs-standalone/src/app/providers/tanstack/TanstackDevtoolsProvider.tsx +13 -0
  87. package/templates/nextjs-standalone/src/app/providers/theme/ThemeProviders.tsx +12 -0
  88. package/templates/nextjs-standalone/src/entities/.gitkeep +0 -0
  89. package/templates/nextjs-standalone/src/features/.gitkeep +0 -0
  90. package/templates/nextjs-standalone/src/shared/api/.gitkeep +0 -0
  91. package/templates/nextjs-standalone/src/shared/config/.gitkeep +0 -0
  92. package/templates/nextjs-standalone/src/shared/hooks/.gitkeep +0 -0
  93. package/templates/nextjs-standalone/src/shared/lib/utils.ts +6 -0
  94. package/templates/nextjs-standalone/src/shared/model/.gitkeep +0 -0
  95. package/templates/nextjs-standalone/src/shared/styles/tokens.css +95 -0
  96. package/templates/nextjs-standalone/src/shared/ui/.gitkeep +0 -0
  97. package/templates/nextjs-standalone/src/views/.gitkeep +0 -0
  98. package/templates/nextjs-standalone/src/widgets/.gitkeep +0 -0
  99. package/templates/nextjs-standalone/tsconfig.json +39 -0
  100. package/templates/nextjs-standalone/vitest.config.ts +15 -0
  101. package/templates/nextjs-standalone/vitest.setup.ts +1 -0
  102. package/templates/ui-app-template/eslint.config.js +3 -0
  103. package/templates/ui-app-template/package.json +38 -0
  104. package/templates/ui-app-template/postcss.config.mjs +5 -0
  105. package/templates/ui-app-template/sh-ui.config.json +14 -0
  106. package/templates/ui-app-template/src/components/.gitkeep +0 -0
  107. package/templates/ui-app-template/src/hooks/.gitkeep +0 -0
  108. package/templates/ui-app-template/src/lib/.gitkeep +0 -0
  109. package/templates/ui-app-template/src/styles/globals.css +37 -0
  110. package/templates/ui-app-template/src/styles/tokens.css +95 -0
  111. package/templates/ui-app-template/tsconfig.json +11 -0
@@ -0,0 +1,119 @@
1
+ import boundaries from "eslint-plugin-boundaries"
2
+ import importX from "eslint-plugin-import-x"
3
+ import checkFile from "eslint-plugin-check-file"
4
+ import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript"
5
+
6
+ /**
7
+ * FSD (Feature-Sliced Design) ESLint configuration.
8
+ * boundaries: layer import direction + public API enforcement
9
+ * import-x: import group ordering
10
+ * check-file: file/folder naming convention enforcement
11
+ *
12
+ * @type {import("eslint").Linter.Config[]}
13
+ */
14
+ export const fsdConfig = [
15
+ // ── boundaries plugin ──
16
+ {
17
+ plugins: {
18
+ boundaries,
19
+ },
20
+ settings: {
21
+ "import/resolver": {
22
+ typescript: {
23
+ alwaysTryTypes: true,
24
+ },
25
+ },
26
+ "boundaries/elements": [
27
+ { type: "app", pattern: ["src/app"], mode: "folder" },
28
+ { type: "shared", pattern: ["src/shared/*"], mode: "folder" },
29
+ { type: "entity", pattern: ["src/entities/*"], mode: "folder" },
30
+ { type: "feature", pattern: ["src/features/*"], mode: "folder" },
31
+ { type: "widget", pattern: ["src/widgets/*"], mode: "folder" },
32
+ { type: "view", pattern: ["src/views/*"], mode: "folder" },
33
+ ],
34
+ "boundaries/ignore": ["**/*.test.*", "**/*.spec.*"],
35
+ },
36
+ rules: {
37
+ "boundaries/element-types": [
38
+ "warn",
39
+ {
40
+ default: "disallow",
41
+ rules: [
42
+ { from: "app", allow: ["view", "widget", "feature", "entity", "shared"] },
43
+ { from: "view", allow: ["widget", "feature", "entity", "shared"] },
44
+ { from: "widget", allow: ["feature", "entity", "shared"] },
45
+ { from: "feature", allow: ["entity", "shared"] },
46
+ { from: "entity", allow: ["shared"] },
47
+ { from: "shared", allow: ["shared"] },
48
+ ],
49
+ },
50
+ ],
51
+ "boundaries/entry-point": [
52
+ "warn",
53
+ {
54
+ default: "disallow",
55
+ rules: [
56
+ { target: "shared", allow: "**" },
57
+ { target: "app", allow: "**" },
58
+ {
59
+ target: ["entity", "widget", "view"],
60
+ allow: "index.{ts,tsx}",
61
+ },
62
+ {
63
+ target: ["feature"],
64
+ allow: ["index.{ts,tsx}", "*/index.{ts,tsx}"],
65
+ },
66
+ ],
67
+ },
68
+ ],
69
+ },
70
+ },
71
+
72
+ // ── check-file plugin (naming conventions) ──
73
+ {
74
+ plugins: {
75
+ "check-file": checkFile,
76
+ },
77
+ rules: {
78
+ "check-file/filename-naming-convention": [
79
+ "error",
80
+ {
81
+ "**/src/**/*.tsx": "PASCAL_CASE",
82
+ "**/src/**/*.ts": "CAMEL_CASE",
83
+ },
84
+ {
85
+ ignoreMiddleExtensions: true,
86
+ },
87
+ ],
88
+ },
89
+ },
90
+ {
91
+ files: [
92
+ "**/index.tsx", "**/index.ts",
93
+ "**/layout.tsx", "**/page.tsx",
94
+ "**/error.tsx", "**/not-found.tsx",
95
+ "**/routing.ts", "**/navigation.ts", "**/request.ts",
96
+ ],
97
+ rules: {
98
+ "check-file/filename-naming-convention": "off",
99
+ },
100
+ },
101
+
102
+ // ── import-x plugin ──
103
+ {
104
+ plugins: {
105
+ "import-x": importX,
106
+ },
107
+ settings: {
108
+ "import-x/resolver-next": [
109
+ createTypeScriptImportResolver({
110
+ alwaysTryTypes: true,
111
+ }),
112
+ ],
113
+ },
114
+ rules: {
115
+ "import-x/order": "off",
116
+ "import-x/no-unresolved": "off",
117
+ },
118
+ },
119
+ ]
@@ -0,0 +1,65 @@
1
+ import js from "@eslint/js"
2
+ import pluginNext from "@next/eslint-plugin-next"
3
+ import checkFile from "eslint-plugin-check-file"
4
+ import eslintConfigPrettier from "eslint-config-prettier"
5
+ import pluginReact from "eslint-plugin-react"
6
+ import pluginReactHooks from "eslint-plugin-react-hooks"
7
+ import globals from "globals"
8
+ import tseslint from "typescript-eslint"
9
+
10
+ import { config as baseConfig } from "./base.js"
11
+
12
+ export const nextJsConfig = [
13
+ ...baseConfig,
14
+ js.configs.recommended,
15
+ eslintConfigPrettier,
16
+ ...tseslint.configs.recommended,
17
+ {
18
+ ...pluginReact.configs.flat.recommended,
19
+ languageOptions: {
20
+ ...pluginReact.configs.flat.recommended.languageOptions,
21
+ globals: {
22
+ ...globals.serviceworker,
23
+ },
24
+ },
25
+ },
26
+ {
27
+ plugins: {
28
+ "@next/next": pluginNext,
29
+ },
30
+ rules: {
31
+ ...pluginNext.configs.recommended.rules,
32
+ ...pluginNext.configs["core-web-vitals"].rules,
33
+ },
34
+ },
35
+ {
36
+ plugins: {
37
+ "react-hooks": pluginReactHooks,
38
+ },
39
+ settings: { react: { version: "detect" } },
40
+ rules: {
41
+ ...pluginReactHooks.configs.recommended.rules,
42
+ "react/react-in-jsx-scope": "off",
43
+ "react/prop-types": "off",
44
+ "react/function-component-definition": [
45
+ "warn",
46
+ {
47
+ namedComponents: "function-declaration",
48
+ unnamedComponents: "arrow-function",
49
+ },
50
+ ],
51
+ },
52
+ },
53
+ {
54
+ plugins: {
55
+ "check-file": checkFile,
56
+ },
57
+ rules: {
58
+ "check-file/filename-naming-convention": [
59
+ "warn",
60
+ { "src/**/*.{ts,tsx}": "CAMEL_CASE" },
61
+ { ignoreMiddleExtensions: true },
62
+ ],
63
+ },
64
+ },
65
+ ]
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@workspace/eslint-config",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "private": true,
6
+ "exports": {
7
+ "./base": "./base.js",
8
+ "./next-js": "./next.js",
9
+ "./react-internal": "./react-internal.js",
10
+ "./fsd": "./fsd.js"
11
+ },
12
+ "devDependencies": {
13
+ "@eslint/js": "^9.39.2",
14
+ "@next/eslint-plugin-next": "^16.1.6",
15
+ "@typescript-eslint/eslint-plugin": "^8.54.0",
16
+ "@typescript-eslint/parser": "^8.54.0",
17
+ "eslint": "^9.39.2",
18
+ "eslint-config-prettier": "^10.1.8",
19
+ "eslint-import-resolver-typescript": "^4.4.4",
20
+ "eslint-plugin-boundaries": "^5.4.0",
21
+ "eslint-plugin-check-file": "^3.3.1",
22
+ "eslint-plugin-import-x": "^4.16.1",
23
+ "eslint-plugin-only-warn": "^1.1.0",
24
+ "eslint-plugin-react": "^7.37.5",
25
+ "eslint-plugin-react-hooks": "^7.0.1",
26
+ "eslint-plugin-turbo": "^2.8.1",
27
+ "globals": "^17.2.0",
28
+ "typescript": "^5.9.3",
29
+ "typescript-eslint": "^8.54.0"
30
+ }
31
+ }
@@ -0,0 +1,36 @@
1
+ import js from "@eslint/js"
2
+ import eslintConfigPrettier from "eslint-config-prettier"
3
+ import pluginReact from "eslint-plugin-react"
4
+ import pluginReactHooks from "eslint-plugin-react-hooks"
5
+ import globals from "globals"
6
+ import tseslint from "typescript-eslint"
7
+
8
+ import { config as baseConfig } from "./base.js"
9
+
10
+ export const config = [
11
+ ...baseConfig,
12
+ js.configs.recommended,
13
+ eslintConfigPrettier,
14
+ ...tseslint.configs.recommended,
15
+ pluginReact.configs.flat.recommended,
16
+ {
17
+ languageOptions: {
18
+ ...pluginReact.configs.flat.recommended.languageOptions,
19
+ globals: {
20
+ ...globals.serviceworker,
21
+ ...globals.browser,
22
+ },
23
+ },
24
+ },
25
+ {
26
+ plugins: {
27
+ "react-hooks": pluginReactHooks,
28
+ },
29
+ settings: { react: { version: "detect" } },
30
+ rules: {
31
+ ...pluginReactHooks.configs.recommended.rules,
32
+ "react/react-in-jsx-scope": "off",
33
+ "react/prop-types": "off",
34
+ },
35
+ },
36
+ ]
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "display": "Default",
4
+ "compilerOptions": {
5
+ "declaration": true,
6
+ "declarationMap": true,
7
+ "esModuleInterop": true,
8
+ "incremental": false,
9
+ "isolatedModules": true,
10
+ "lib": ["es2022", "DOM", "DOM.Iterable"],
11
+ "module": "NodeNext",
12
+ "moduleDetection": "force",
13
+ "moduleResolution": "NodeNext",
14
+ "noUncheckedIndexedAccess": true,
15
+ "resolveJsonModule": true,
16
+ "skipLibCheck": true,
17
+ "strict": true,
18
+ "target": "ES2022"
19
+ }
20
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "display": "Next.js",
4
+ "extends": "./base.json",
5
+ "compilerOptions": {
6
+ "plugins": [{ "name": "next" }],
7
+ "module": "ESNext",
8
+ "moduleResolution": "Bundler",
9
+ "allowJs": true,
10
+ "jsx": "preserve",
11
+ "noEmit": true
12
+ }
13
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@workspace/typescript-config",
3
+ "version": "0.0.0",
4
+ "private": true
5
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "display": "React Library",
4
+ "extends": "./base.json",
5
+ "compilerOptions": {
6
+ "jsx": "react-jsx"
7
+ }
8
+ }
@@ -0,0 +1,3 @@
1
+ import { config } from "@workspace/eslint-config/react-internal"
2
+
3
+ export default config
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@workspace/ui-core",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "private": true,
6
+ "scripts": {
7
+ "lint": "eslint . --max-warnings 0"
8
+ },
9
+ "dependencies": {
10
+ "class-variance-authority": "^0.7.1",
11
+ "clsx": "^2.1.1",
12
+ "tailwind-merge": "^3.5.0"
13
+ },
14
+ "devDependencies": {
15
+ "@workspace/eslint-config": "workspace:*",
16
+ "@workspace/typescript-config": "workspace:*",
17
+ "eslint": "^9.39.2",
18
+ "typescript": "^5.9.3"
19
+ },
20
+ "exports": {
21
+ "./lib/*": "./src/lib/*.ts"
22
+ }
23
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "@workspace/typescript-config/react-library.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "@workspace/ui-core/*": ["./src/*"]
7
+ }
8
+ },
9
+ "include": ["."],
10
+ "exclude": ["node_modules", "dist"]
11
+ }
@@ -0,0 +1,5 @@
1
+ packages:
2
+ - "apps/*"
3
+ - "packages/*"
4
+ - "packages/ui/ui-core"
5
+ - "packages/ui/ui-apps/*"
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "@workspace/typescript-config/base.json"
3
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://turbo.build/schema.json",
3
+ "ui": "tui",
4
+ "globalEnv": [
5
+ "NODE_ENV",
6
+ "API_URL",
7
+ "CI"
8
+ ],
9
+ "tasks": {
10
+ "build": {
11
+ "dependsOn": ["^build"],
12
+ "inputs": ["$TURBO_DEFAULT$", ".env*"],
13
+ "outputs": [".next/**", "!.next/cache/**"]
14
+ },
15
+ "lint": {
16
+ "dependsOn": ["^lint"]
17
+ },
18
+ "typecheck": {
19
+ "dependsOn": ["^typecheck"]
20
+ },
21
+ "dev": {
22
+ "cache": false,
23
+ "persistent": true
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,2 @@
1
+ # API
2
+ API_URL=http://localhost:8080
@@ -0,0 +1,11 @@
1
+ FROM node:22-alpine
2
+
3
+ COPY . /app
4
+
5
+ WORKDIR /app/apps/app-name
6
+
7
+ EXPOSE 3000
8
+ ENV PORT=3000
9
+ ENV HOSTNAME="0.0.0.0"
10
+
11
+ CMD ["node", "server.js"]
@@ -0,0 +1,64 @@
1
+ # Next.js App Template (모노레포용)
2
+
3
+ 모노레포 `apps/` 하위에 배치되는 Next.js 앱 템플릿.
4
+ UI 컴포넌트는 `@workspace/ui-{name}` 패키지를 참조하며, sh-ui 설정을 직접 갖지 않음.
5
+
6
+ ## 기술 스택
7
+
8
+ - **Next.js 16** (App Router, React Compiler, standalone 출력)
9
+ - **React 19**
10
+ - **TypeScript 5.9**
11
+ - **@workspace/ui-{name}** (sh-ui 컴포넌트 패키지)
12
+ - **TanStack React Query** + **Axios**
13
+ - **Zustand**
14
+ - **next-themes** + **Sonner**
15
+ - **Zod**
16
+ - **Vitest** + **Testing Library**
17
+ - **Docker** 지원
18
+
19
+ ## 프로젝트 구조
20
+
21
+ ```
22
+ ├── app/ # Next.js App Router
23
+ │ ├── layout.tsx # 루트 레이아웃 (@workspace/ui-{name}/globals.css import)
24
+ │ └── page.tsx
25
+ ├── src/
26
+ │ ├── app/
27
+ │ │ └── providers/ # QueryClient, Theme, Toaster
28
+ │ ├── shared/ # FSD: 공유 유틸, 설정, 타입
29
+ │ ├── entities/
30
+ │ ├── features/
31
+ │ ├── views/
32
+ │ └── widgets/
33
+ ├── eslint.config.js # @workspace/eslint-config 사용
34
+ ├── tsconfig.json # @workspace/typescript-config 확장
35
+ ├── postcss.config.mjs # @workspace/ui-{name}/postcss.config 재사용
36
+ ├── next.config.ts
37
+ ├── vitest.config.ts
38
+ ├── Dockerfile
39
+ └── .env.example
40
+ ```
41
+
42
+ ## 워크스페이스 의존성
43
+
44
+ ```
45
+ apps/{name}/
46
+ ├── @workspace/ui-{name} → packages/ui/ui-apps/ui-{name} (sh-ui 컴포넌트 + 테마)
47
+ ├── @workspace/ui-core → packages/ui/ui-core (cn 등 유틸)
48
+ ├── @workspace/eslint-config → packages/eslint-config
49
+ └── @workspace/typescript-config → packages/typescript-config
50
+ ```
51
+
52
+ ## sh-ui 컴포넌트 추가
53
+
54
+ 모노레포 루트에서:
55
+
56
+ ```bash
57
+ # 모든 ui 패키지에 추가 (대화형)
58
+ npx sh-ui-create add-component button
59
+
60
+ # 이 앱의 ui 패키지에만 추가
61
+ npx sh-ui-create add-component button --app {name}
62
+ ```
63
+
64
+ 각 `ui-{app}/` 패키지의 `sh-ui.config.json` 경로 설정에 따라 `src/components/` 로 복사됨.
@@ -0,0 +1,22 @@
1
+ import type { Metadata } from 'next';
2
+ import '@workspace/ui-app-name/globals.css';
3
+ import { GlobalProvider } from '@/src/app/providers';
4
+
5
+ export const metadata: Metadata = {
6
+ title: 'App Name',
7
+ description: 'App Description',
8
+ };
9
+
10
+ export default function RootLayout({
11
+ children,
12
+ }: {
13
+ children: React.ReactNode;
14
+ }) {
15
+ return (
16
+ <html lang='ko' suppressHydrationWarning>
17
+ <body>
18
+ <GlobalProvider>{children}</GlobalProvider>
19
+ </body>
20
+ </html>
21
+ );
22
+ }
@@ -0,0 +1,7 @@
1
+ export default function Home() {
2
+ return (
3
+ <main className='flex min-h-screen flex-col items-center justify-center'>
4
+ <h1 className='text-4xl font-bold'>Hello World</h1>
5
+ </main>
6
+ );
7
+ }
@@ -0,0 +1,10 @@
1
+ import { nextJsConfig } from "@workspace/eslint-config/next-js"
2
+ import { fsdConfig } from "@workspace/eslint-config/fsd"
3
+
4
+ export default [
5
+ {
6
+ ignores: [".next/**", "dist/**", "node_modules/**"],
7
+ },
8
+ ...nextJsConfig,
9
+ ...fsdConfig,
10
+ ]
@@ -0,0 +1,12 @@
1
+ import type { NextConfig } from 'next';
2
+
3
+ const nextConfig: NextConfig = {
4
+ reactCompiler: true,
5
+ transpilePackages: ['@workspace/ui'],
6
+ output: 'standalone',
7
+ images: {
8
+ unoptimized: process.env.NODE_ENV === 'development',
9
+ },
10
+ };
11
+
12
+ export default nextConfig;
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "app-name",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "next dev -p 3000 --turbopack",
8
+ "build": "next build",
9
+ "start": "next start",
10
+ "lint": "eslint .",
11
+ "lint:fix": "eslint . --fix",
12
+ "typecheck": "tsc --noEmit",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest"
15
+ },
16
+ "dependencies": {
17
+ "@tanstack/react-query": "^5.90.21",
18
+ "@workspace/ui-app-name": "workspace:*",
19
+ "axios": "^1.13.6",
20
+ "lucide-react": "^0.563.0",
21
+ "next": "16.1.6",
22
+ "next-themes": "^0.4.6",
23
+ "react": "^19.2.4",
24
+ "react-dom": "^19.2.4",
25
+ "server-only": "^0.0.1",
26
+ "sonner": "^2.0.7",
27
+ "zod": "^4.3.6",
28
+ "zustand": "^5.0.11"
29
+ },
30
+ "devDependencies": {
31
+ "@tailwindcss/postcss": "^4.1.18",
32
+ "@tanstack/react-query-devtools": "^5.91.3",
33
+ "@testing-library/jest-dom": "^6.9.1",
34
+ "@types/node": "^25.1.0",
35
+ "@types/react": "^19.2.10",
36
+ "@types/react-dom": "^19.2.3",
37
+ "@workspace/eslint-config": "workspace:^",
38
+ "@workspace/typescript-config": "workspace:*",
39
+ "babel-plugin-react-compiler": "^1.0.0",
40
+ "eslint": "^9.39.2",
41
+ "jsdom": "^29.0.0",
42
+ "typescript": "^5.9.3",
43
+ "vitest": "^4.1.0"
44
+ }
45
+ }
@@ -0,0 +1 @@
1
+ export { default } from "@workspace/ui-app-name/postcss.config";
File without changes
@@ -0,0 +1,23 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Toaster } from 'sonner';
3
+
4
+ import { QueryClientProvider } from '../tanstack/QueryClientProvider';
5
+ import { TanstackDevtoolsProvider } from '../tanstack/TanstackDevtoolsProvider';
6
+ import { ThemeProviders } from '../theme/ThemeProviders';
7
+
8
+ interface GlobalProviderProps {
9
+ children: ReactNode;
10
+ }
11
+
12
+ export function GlobalProvider({ children }: GlobalProviderProps) {
13
+ return (
14
+ <ThemeProviders>
15
+ <QueryClientProvider>
16
+ <TanstackDevtoolsProvider>
17
+ <Toaster />
18
+ {children}
19
+ </TanstackDevtoolsProvider>
20
+ </QueryClientProvider>
21
+ </ThemeProviders>
22
+ );
23
+ }
@@ -0,0 +1 @@
1
+ export { GlobalProvider } from './GlobalProvider';
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+
3
+ import {
4
+ QueryClient,
5
+ QueryClientProvider as TanstackQueryClientProvider,
6
+ } from '@tanstack/react-query';
7
+ import { useState, type ReactNode } from 'react';
8
+
9
+ export function QueryClientProvider({ children }: { children: ReactNode }) {
10
+ const [queryClient] = useState(
11
+ () =>
12
+ new QueryClient({
13
+ defaultOptions: {
14
+ queries: {
15
+ staleTime: 60 * 1000,
16
+ retry: 1,
17
+ },
18
+ },
19
+ }),
20
+ );
21
+
22
+ return (
23
+ <TanstackQueryClientProvider client={queryClient}>
24
+ {children}
25
+ </TanstackQueryClientProvider>
26
+ );
27
+ }
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+
3
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
4
+ import type { ReactNode } from 'react';
5
+
6
+ export function TanstackDevtoolsProvider({ children }: { children: ReactNode }) {
7
+ return (
8
+ <>
9
+ {children}
10
+ <ReactQueryDevtools initialIsOpen={false} />
11
+ </>
12
+ );
13
+ }
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+
3
+ import { ThemeProvider } from 'next-themes';
4
+ import type { ReactNode } from 'react';
5
+
6
+ export function ThemeProviders({ children }: { children: ReactNode }) {
7
+ return (
8
+ <ThemeProvider attribute='class' defaultTheme='system' enableSystem>
9
+ {children}
10
+ </ThemeProvider>
11
+ );
12
+ }