sh-ui-cli 0.98.0 → 0.109.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.
- package/data/changelog/versions.json +169 -0
- package/data/registry/react/components/scroll-area/index.module.tsx +71 -0
- package/data/registry/react/components/scroll-area/index.tailwind.tsx +54 -0
- package/data/registry/react/components/scroll-area/index.tsx +67 -0
- package/data/registry/react/components/scroll-area/styles.css +64 -0
- package/data/registry/react/components/scroll-area/styles.module.css +64 -0
- package/data/registry/react/components/sheet/index.module.tsx +93 -0
- package/data/registry/react/components/sheet/index.tailwind.tsx +120 -0
- package/data/registry/react/components/sheet/index.tsx +121 -0
- package/data/registry/react/components/sheet/styles.css +183 -0
- package/data/registry/react/components/sheet/styles.module.css +171 -0
- package/data/registry/react/registry.json +94 -0
- package/data/registry/react/tokens-used.json +86 -1
- package/data/summaries/react.json +3 -1
- package/data/tokens/src/primitives.json +8 -0
- package/data/tokens/src/semantic.json +36 -10
- package/package.json +1 -1
- package/src/add.mjs +39 -14
- package/src/create/cli-args.js +18 -5
- package/src/create/generator.js +116 -4
- package/src/create/index.mjs +3 -0
- package/src/create/plugins/nextIntl.js +3 -0
- package/src/create/theme/decode.js +3 -0
- package/src/create/theme/presets.js +45 -8
- package/src/css-bundle.mjs +60 -0
- package/src/mcp.mjs +47 -3
- package/src/remove.mjs +10 -1
- package/src/theme-extract.mjs +1 -0
- package/templates/nextjs-standalone/_arch/flat/app/globals.css +16 -4
- package/templates/nextjs-standalone/_arch/flat/lib/styles/tokens.css +35 -4
- package/templates/nextjs-standalone/_arch/fsd/src/shared/styles/tokens.css +35 -4
- package/templates/nextjs-standalone/_arch/mes/app/globals.css +16 -4
- package/templates/nextjs-standalone/_arch/mes/src/lib/styles/tokens.css +33 -2
- package/templates/nextjs-standalone/app/globals.css +16 -4
- package/templates/ui-app-template/src/styles/globals.css +16 -4
- package/templates/ui-app-template/src/styles/tokens.css +35 -4
- package/templates/vite-standalone/_arch/flat/src/lib/styles/globals.css +16 -0
- package/templates/vite-standalone/_arch/flat/src/lib/styles/tokens.css +35 -4
- package/templates/vite-standalone/_arch/fsd/src/shared/styles/globals.css +16 -0
- package/templates/vite-standalone/_arch/fsd/src/shared/styles/tokens.css +35 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$description": "컴포넌트별 토큰 의존성 (var(--*) 추출). build-registry-tokens.mjs 가 자동 생성.",
|
|
3
|
-
"$generated": "2026-05-
|
|
3
|
+
"$generated": "2026-05-21T07:02:56.611Z",
|
|
4
4
|
"components": {
|
|
5
5
|
"button": {
|
|
6
6
|
"plain": [
|
|
@@ -1977,6 +1977,91 @@
|
|
|
1977
1977
|
"css-modules": [],
|
|
1978
1978
|
"vanilla-extract": []
|
|
1979
1979
|
},
|
|
1980
|
+
"scroll-area": {
|
|
1981
|
+
"plain": [
|
|
1982
|
+
"--border-strong",
|
|
1983
|
+
"--border-width-strong",
|
|
1984
|
+
"--foreground-muted",
|
|
1985
|
+
"--ring"
|
|
1986
|
+
],
|
|
1987
|
+
"tailwind": [
|
|
1988
|
+
"--border-width-strong"
|
|
1989
|
+
],
|
|
1990
|
+
"css-modules": [
|
|
1991
|
+
"--border-strong",
|
|
1992
|
+
"--border-width-strong",
|
|
1993
|
+
"--foreground-muted",
|
|
1994
|
+
"--ring"
|
|
1995
|
+
],
|
|
1996
|
+
"vanilla-extract": []
|
|
1997
|
+
},
|
|
1998
|
+
"sheet": {
|
|
1999
|
+
"plain": [
|
|
2000
|
+
"--background",
|
|
2001
|
+
"--background-subtle",
|
|
2002
|
+
"--border",
|
|
2003
|
+
"--border-width-strong",
|
|
2004
|
+
"--control-sm",
|
|
2005
|
+
"--duration-fast",
|
|
2006
|
+
"--duration-slow",
|
|
2007
|
+
"--foreground",
|
|
2008
|
+
"--foreground-muted",
|
|
2009
|
+
"--radius",
|
|
2010
|
+
"--ring",
|
|
2011
|
+
"--shadow-xl",
|
|
2012
|
+
"--space-1",
|
|
2013
|
+
"--space-2",
|
|
2014
|
+
"--space-3",
|
|
2015
|
+
"--space-4",
|
|
2016
|
+
"--space-6",
|
|
2017
|
+
"--text-lg",
|
|
2018
|
+
"--text-sm",
|
|
2019
|
+
"--weight-semibold",
|
|
2020
|
+
"--z-modal",
|
|
2021
|
+
"--z-overlay"
|
|
2022
|
+
],
|
|
2023
|
+
"tailwind": [
|
|
2024
|
+
"--border-width-strong",
|
|
2025
|
+
"--control-sm",
|
|
2026
|
+
"--duration-fast",
|
|
2027
|
+
"--duration-slow",
|
|
2028
|
+
"--radius",
|
|
2029
|
+
"--shadow-xl",
|
|
2030
|
+
"--space-1",
|
|
2031
|
+
"--space-2",
|
|
2032
|
+
"--space-4",
|
|
2033
|
+
"--space-6",
|
|
2034
|
+
"--text-lg",
|
|
2035
|
+
"--text-sm",
|
|
2036
|
+
"--z-modal",
|
|
2037
|
+
"--z-overlay"
|
|
2038
|
+
],
|
|
2039
|
+
"css-modules": [
|
|
2040
|
+
"--background",
|
|
2041
|
+
"--background-subtle",
|
|
2042
|
+
"--border",
|
|
2043
|
+
"--border-width-strong",
|
|
2044
|
+
"--control-sm",
|
|
2045
|
+
"--duration-fast",
|
|
2046
|
+
"--duration-slow",
|
|
2047
|
+
"--foreground",
|
|
2048
|
+
"--foreground-muted",
|
|
2049
|
+
"--radius",
|
|
2050
|
+
"--ring",
|
|
2051
|
+
"--shadow-xl",
|
|
2052
|
+
"--space-1",
|
|
2053
|
+
"--space-2",
|
|
2054
|
+
"--space-3",
|
|
2055
|
+
"--space-4",
|
|
2056
|
+
"--space-6",
|
|
2057
|
+
"--text-lg",
|
|
2058
|
+
"--text-sm",
|
|
2059
|
+
"--weight-semibold",
|
|
2060
|
+
"--z-modal",
|
|
2061
|
+
"--z-overlay"
|
|
2062
|
+
],
|
|
2063
|
+
"vanilla-extract": []
|
|
2064
|
+
},
|
|
1980
2065
|
"utils": {
|
|
1981
2066
|
"plain": [],
|
|
1982
2067
|
"tailwind": [],
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
"form-yup": "yup 스키마 → sh-ui Form 의 Standard Schema 어댑터. `yupSchema(yupObject)` 로 감싸 Form 의 `validation` prop 에 전달. yup 자체는 peerDependency.",
|
|
56
56
|
"form-rhf": "React Hook Form 인스턴스 → sh-ui FormStore 어댑터. `adaptReactHookForm(rhfInstance, config)` — RHF 가 state owner, sh-ui 가 메타(steps/sections) 관리. react-hook-form 은 peerDependency.",
|
|
57
57
|
"form-tanstack": "TanStack Form 인스턴스 → sh-ui FormStore 어댑터. `adaptTanstackForm(tanstackInstance, config)` — TanStack Form 이 state owner. @tanstack/react-form 은 peerDependency.",
|
|
58
|
-
"calendar": "내부 캘린더 위젯 (DatePicker 가 사용). single / multiple / range 모드. CalendarMessages 로 a11y 텍스트 override. 일반적으로 직접 사용보다 DatePicker / DateRangePicker 권장."
|
|
58
|
+
"calendar": "내부 캘린더 위젯 (DatePicker 가 사용). single / multiple / range 모드. CalendarMessages 로 a11y 텍스트 override. 일반적으로 직접 사용보다 DatePicker / DateRangePicker 권장.",
|
|
59
|
+
"scroll-area": "커스텀 스크롤 컨테이너 — composite export ScrollArea (Base UI). 내부에서 viewport + scrollbar + thumb + corner 를 자동 구성하며 OS-native 스크롤바를 대체한다. orientation: \"vertical\" | \"horizontal\" | \"both\" (기본 vertical). 외부 height/width 가 정해진 컨테이너 안에서 사용. viewportClassName 으로 viewport 의 패딩/레이아웃 분리 적용. 스크롤바는 hover/scrolling 시 fade in, prefers-reduced-motion 존중.",
|
|
60
|
+
"sheet": "화면 가장자리에서 슬라이드 인 하는 side drawer — separate exports: Sheet / SheetTrigger / SheetClose / SheetContent / SheetTitle / SheetDescription / SheetHeader / SheetFooter / SheetCloseX (Base UI Drawer 래핑, 포커스 트랩). 글로벌 알림함 · 작업 큐 · 보조 패널 같은 사이드바 무관 모달 시트용. 사이드바 인근 detail 패널은 SidebarPanel, 중앙 강제 모달은 Dialog 권장. SheetContent 의 side: \"right\" | \"left\" | \"top\" | \"bottom\" (기본 right) 으로 진입 방향 지정. SheetTrigger·SheetClose 는 자체 button — 다른 엘리먼트 슬롯은 `render` prop. ESC/바깥 클릭/포커스 복귀 자동, prefers-reduced-motion 시 transform 트랜지션 제거."
|
|
59
61
|
}
|
|
60
62
|
}
|
|
@@ -94,6 +94,14 @@
|
|
|
94
94
|
"bold": { "$value": 700, "$type": "fontWeight" }
|
|
95
95
|
},
|
|
96
96
|
|
|
97
|
+
"letterSpacing": {
|
|
98
|
+
"tighter": { "$value": "-0.04em", "$type": "dimension" },
|
|
99
|
+
"tight": { "$value": "-0.02em", "$type": "dimension" },
|
|
100
|
+
"normal": { "$value": "0", "$type": "dimension" },
|
|
101
|
+
"wide": { "$value": "0.02em", "$type": "dimension" },
|
|
102
|
+
"wider": { "$value": "0.04em", "$type": "dimension" }
|
|
103
|
+
},
|
|
104
|
+
|
|
97
105
|
"shadow": {
|
|
98
106
|
"sm": { "$value": "0 1px 2px rgba(0, 0, 0, 0.08)", "$type": "shadow" },
|
|
99
107
|
"md": { "$value": "0 4px 12px rgba(0, 0, 0, 0.12)", "$type": "shadow" },
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"light": {
|
|
5
5
|
"background": {
|
|
6
6
|
"default": { "$value": "{color.white}", "$type": "color" },
|
|
7
|
-
"subtle": { "$value": "{color.{base}.
|
|
8
|
-
"muted": { "$value": "{color.{base}.
|
|
7
|
+
"subtle": { "$value": "{color.{base}.100}", "$type": "color" },
|
|
8
|
+
"muted": { "$value": "{color.{base}.200}", "$type": "color" },
|
|
9
9
|
"inverse": { "$value": "{color.{base}.950}", "$type": "color" }
|
|
10
10
|
},
|
|
11
11
|
"foreground": {
|
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
"foreground": { "$value": "{color.{base}.50}", "$type": "color" },
|
|
24
24
|
"hover": { "$value": "{color.{base}.800}", "$type": "color" }
|
|
25
25
|
},
|
|
26
|
+
"accent": {
|
|
27
|
+
"default": { "$value": "{color.{base}.900}", "$type": "color" },
|
|
28
|
+
"foreground": { "$value": "{color.{base}.50}", "$type": "color" },
|
|
29
|
+
"hover": { "$value": "{color.{base}.800}", "$type": "color" }
|
|
30
|
+
},
|
|
26
31
|
"danger": {
|
|
27
32
|
"default": { "$value": "{color.red.600}", "$type": "color" },
|
|
28
33
|
"foreground": { "$value": "{color.white}", "$type": "color" },
|
|
@@ -30,10 +35,10 @@
|
|
|
30
35
|
},
|
|
31
36
|
"ring": { "$value": "{color.{base}.400}", "$type": "color" },
|
|
32
37
|
"sidebar": {
|
|
33
|
-
"bg": { "$value": "{color.{base}.
|
|
38
|
+
"bg": { "$value": "{color.{base}.100}", "$type": "color" },
|
|
34
39
|
"fg": { "$value": "{color.{base}.950}", "$type": "color" },
|
|
35
40
|
"border": { "$value": "{color.{base}.200}", "$type": "color" },
|
|
36
|
-
"accent": { "$value": "{color.{base}.
|
|
41
|
+
"accent": { "$value": "{color.{base}.200}", "$type": "color" },
|
|
37
42
|
"accent-fg": { "$value": "{color.{base}.950}", "$type": "color" }
|
|
38
43
|
}
|
|
39
44
|
},
|
|
@@ -60,6 +65,11 @@
|
|
|
60
65
|
"foreground": { "$value": "{color.{base}.900}", "$type": "color" },
|
|
61
66
|
"hover": { "$value": "{color.{base}.200}", "$type": "color" }
|
|
62
67
|
},
|
|
68
|
+
"accent": {
|
|
69
|
+
"default": { "$value": "{color.{base}.50}", "$type": "color" },
|
|
70
|
+
"foreground": { "$value": "{color.{base}.900}", "$type": "color" },
|
|
71
|
+
"hover": { "$value": "{color.{base}.200}", "$type": "color" }
|
|
72
|
+
},
|
|
63
73
|
"danger": {
|
|
64
74
|
"default": { "$value": "{color.red.600}", "$type": "color" },
|
|
65
75
|
"foreground": { "$value": "{color.white}", "$type": "color" },
|
|
@@ -76,7 +86,12 @@
|
|
|
76
86
|
},
|
|
77
87
|
|
|
78
88
|
"radius": {
|
|
79
|
-
"default": { "$value": "{radius.{radius}}", "$type": "dimension" }
|
|
89
|
+
"default": { "$value": "{radius.{radius}}", "$type": "dimension" },
|
|
90
|
+
"sm": { "$value": "{radius.sm}", "$type": "dimension" },
|
|
91
|
+
"md": { "$value": "{radius.md}", "$type": "dimension" },
|
|
92
|
+
"lg": { "$value": "{radius.lg}", "$type": "dimension" },
|
|
93
|
+
"xl": { "$value": "{radius.xl}", "$type": "dimension" },
|
|
94
|
+
"full": { "$value": "{radius.full}", "$type": "dimension" }
|
|
80
95
|
},
|
|
81
96
|
|
|
82
97
|
"space": {
|
|
@@ -112,11 +127,22 @@
|
|
|
112
127
|
},
|
|
113
128
|
|
|
114
129
|
"shadow": {
|
|
115
|
-
"sm":
|
|
116
|
-
"md":
|
|
117
|
-
"lg":
|
|
118
|
-
"xl":
|
|
119
|
-
"menu":
|
|
130
|
+
"sm": { "$value": "{shadow.sm}", "$type": "shadow" },
|
|
131
|
+
"md": { "$value": "{shadow.md}", "$type": "shadow" },
|
|
132
|
+
"lg": { "$value": "{shadow.lg}", "$type": "shadow" },
|
|
133
|
+
"xl": { "$value": "{shadow.xl}", "$type": "shadow" },
|
|
134
|
+
"menu": { "$value": "{shadow.lg}", "$type": "shadow" },
|
|
135
|
+
"popover": { "$value": "{shadow.lg}", "$type": "shadow" },
|
|
136
|
+
"modal": { "$value": "{shadow.xl}", "$type": "shadow" },
|
|
137
|
+
"toast": { "$value": "{shadow.md}", "$type": "shadow" }
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
"tracking": {
|
|
141
|
+
"tighter": { "$value": "{letterSpacing.tighter}", "$type": "dimension" },
|
|
142
|
+
"tight": { "$value": "{letterSpacing.tight}", "$type": "dimension" },
|
|
143
|
+
"normal": { "$value": "{letterSpacing.normal}", "$type": "dimension" },
|
|
144
|
+
"wide": { "$value": "{letterSpacing.wide}", "$type": "dimension" },
|
|
145
|
+
"wider": { "$value": "{letterSpacing.wider}", "$type": "dimension" }
|
|
120
146
|
},
|
|
121
147
|
|
|
122
148
|
"duration": {
|
package/package.json
CHANGED
package/src/add.mjs
CHANGED
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
stripStylesImport,
|
|
17
17
|
isStyleFile,
|
|
18
18
|
isTsxFile,
|
|
19
|
+
hasCrossComponentImport,
|
|
20
|
+
rewriteCrossComponentImports,
|
|
19
21
|
} from "./css-bundle.mjs";
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -96,26 +98,49 @@ function resolveDest(template, config) {
|
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
/**
|
|
99
|
-
* registry source 안의 placeholder 를 사용자 config 값으로 치환.
|
|
100
|
-
* 현재 지원: `@SH_UI_UTILS@` → `aliases.utils` (예: `@/src/shared/lib/utils`).
|
|
101
|
+
* registry source 안의 placeholder / 상대 import 를 사용자 config 값으로 치환.
|
|
101
102
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
103
|
+
* 1) `@SH_UI_UTILS@` → `aliases.utils` (예: `@/src/shared/lib/utils`).
|
|
104
|
+
* registry 컴포넌트는 cn 유틸을 `import { cn } from "@SH_UI_UTILS@"` 로
|
|
105
|
+
* import 한다 — add 시점에 사용자 프로젝트 alias 로 치환해 module resolution 동작.
|
|
106
|
+
* 2) 크로스컴포넌트 상대 import (`from "../popover"` /
|
|
107
|
+
* `from "../popover/index.tsx"` / `from "../form/types"`) →
|
|
108
|
+
* `aliases.components` 경유 (예: `@workspace/ui-core/components/popover`).
|
|
109
|
+
* 상대경로/명시적 `.tsx` 확장자가 그대로 emit 되면 NodeNext 소비자가 깨지므로
|
|
110
|
+
* (TS5097 → `allowImportingTsExtensions`+`noEmit` 강요), utils 와 동일하게
|
|
111
|
+
* add 시점에 alias 로 정규화한다. 변환 규칙은 css-bundle.mjs 의
|
|
112
|
+
* rewriteCrossComponentImports 참조 (remove.mjs 가 동일 함수로 대칭 replay).
|
|
104
113
|
*
|
|
105
|
-
*
|
|
106
|
-
* 추가 후 import 깨진 것을 발견하기 전에
|
|
114
|
+
* 해당 alias 가 미설정인데 placeholder/상대 import 가 등장하면 친절 에러로
|
|
115
|
+
* 안내 — 사용자가 매 컴포넌트 추가 후 import 깨진 것을 발견하기 전에 잡는다.
|
|
107
116
|
*/
|
|
108
117
|
function substitutePlaceholders(content, config, srcRel) {
|
|
118
|
+
let out = content;
|
|
119
|
+
|
|
109
120
|
const PLACEHOLDER = "@SH_UI_UTILS@";
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
if (out.includes(PLACEHOLDER)) {
|
|
122
|
+
const alias = config.aliases?.utils;
|
|
123
|
+
if (!alias) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`${srcRel} 가 cn 유틸을 import 합니다. sh-ui.config.json 에 aliases.utils 를 설정하세요.\n` +
|
|
126
|
+
` 예: "aliases": { "utils": "@/src/lib/utils" }`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
out = out.replaceAll(PLACEHOLDER, alias);
|
|
117
130
|
}
|
|
118
|
-
|
|
131
|
+
|
|
132
|
+
if (hasCrossComponentImport(out)) {
|
|
133
|
+
const componentsAlias = config.aliases?.components;
|
|
134
|
+
if (!componentsAlias) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`${srcRel} 가 다른 sh-ui 컴포넌트를 import 합니다. sh-ui.config.json 에 aliases.components 를 설정하세요.\n` +
|
|
137
|
+
` 예: "aliases": { "components": "@/src/components" }`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
out = rewriteCrossComponentImports(out, componentsAlias);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return out;
|
|
119
144
|
}
|
|
120
145
|
|
|
121
146
|
async function ensureDir(filePath) {
|
package/src/create/cli-args.js
CHANGED
|
@@ -13,8 +13,12 @@ const VALID_STRUCTURES = CREATE_STRUCTURES;
|
|
|
13
13
|
const VALID_PLUGINS = allPlugins.map((p) => p.name);
|
|
14
14
|
const VALID_ARCHES = allArchitectures.map((a) => a.name);
|
|
15
15
|
|
|
16
|
-
const VALUE_FLAGS = ['platform', 'structure', 'plugins', 'theme', 'app', 'css', 'arch', 'port', 'i18n', 'locales'];
|
|
17
|
-
|
|
16
|
+
const VALUE_FLAGS = ['platform', 'structure', 'plugins', 'theme', 'app', 'css', 'arch', 'port', 'i18n', 'locales', 'locale'];
|
|
17
|
+
|
|
18
|
+
// `locale` (단수) 은 한국어/일본어 같은 사용자 지역 디폴트 가정 (폰트 등) 을 활성화하는
|
|
19
|
+
// 옵션. `locales` (복수) 와 다르다: locales 는 i18n 활성화 시 생성할 locale 코드 목록.
|
|
20
|
+
const VALID_LOCALES = ['default', 'ko'];
|
|
21
|
+
const BOOL_FLAGS = ['yes', 'help', 'dry-run', 'no-git-init', 'git-init'];
|
|
18
22
|
|
|
19
23
|
const SUBCOMMANDS = ['add-app', 'add-component'];
|
|
20
24
|
|
|
@@ -40,9 +44,11 @@ export const parseArgs = (argv) => {
|
|
|
40
44
|
}
|
|
41
45
|
const name = arg.slice(2);
|
|
42
46
|
if (BOOL_FLAGS.includes(name)) {
|
|
43
|
-
// dry-run
|
|
44
|
-
|
|
45
|
-
flags
|
|
47
|
+
// dry-run → dryRun. --no-git-init → gitInit:false. --git-init → gitInit:true.
|
|
48
|
+
if (name === 'dry-run') flags.dryRun = true;
|
|
49
|
+
else if (name === 'no-git-init') flags.gitInit = false;
|
|
50
|
+
else if (name === 'git-init') flags.gitInit = true;
|
|
51
|
+
else flags[name] = true;
|
|
46
52
|
continue;
|
|
47
53
|
}
|
|
48
54
|
if (!VALUE_FLAGS.includes(name)) {
|
|
@@ -79,6 +85,13 @@ export const parseArgs = (argv) => {
|
|
|
79
85
|
if (name === 'i18n' && !I18N_LIBRARIES.includes(value)) {
|
|
80
86
|
throw new Error(`--i18n 은 ${I18N_LIBRARIES.join('/')} 중 하나여야 함 (받은 값: ${value})`);
|
|
81
87
|
}
|
|
88
|
+
if (name === 'locale' && !VALID_LOCALES.includes(value)) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`--locale 은 ${VALID_LOCALES.join('/')} 중 하나여야 함 (받은 값: ${value}). ` +
|
|
91
|
+
`'ko' 선택 시 Pretendard 폰트가 globals.css 에 자동 적용됩니다. ` +
|
|
92
|
+
`(주의: --locales (복수) 는 i18n 활성화 시 생성할 locale 코드 목록 — 다른 의미).`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
82
95
|
if (name === 'css' && !CSS_FRAMEWORKS_SUPPORTED.includes(value)) {
|
|
83
96
|
// planned 값은 '곧 옵니다' 신호로 분기 — 사용자 의도가 더 명확히 전달.
|
|
84
97
|
if (CSS_FRAMEWORKS_PLANNED.includes(value)) {
|
package/src/create/generator.js
CHANGED
|
@@ -320,7 +320,7 @@ export async function createProject(options = {}) {
|
|
|
320
320
|
|
|
321
321
|
if (platform === 'flutter') {
|
|
322
322
|
await generateFlutter(targetDir, projectName, theme, cssFramework, themeBase);
|
|
323
|
-
await finalizeProject(targetDir, { dryRun: options.dryRun });
|
|
323
|
+
await finalizeProject(targetDir, { dryRun: options.dryRun, gitInit: options.gitInit, locale: options.locale });
|
|
324
324
|
console.log(`\n✅ ${projectName} Flutter 프로젝트가 생성되었습니다!`);
|
|
325
325
|
console.log(`\n cd ${projectName}`);
|
|
326
326
|
console.log(' flutter pub get');
|
|
@@ -355,7 +355,7 @@ export async function createProject(options = {}) {
|
|
|
355
355
|
});
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
-
await finalizeProject(targetDir, { dryRun: options.dryRun });
|
|
358
|
+
await finalizeProject(targetDir, { dryRun: options.dryRun, gitInit: options.gitInit, locale: options.locale });
|
|
359
359
|
|
|
360
360
|
if (options.dryRun) {
|
|
361
361
|
const files = await listAllFiles(targetDir);
|
|
@@ -401,7 +401,7 @@ export async function createProject(options = {}) {
|
|
|
401
401
|
});
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
-
await finalizeProject(targetDir, { dryRun: options.dryRun });
|
|
404
|
+
await finalizeProject(targetDir, { dryRun: options.dryRun, gitInit: options.gitInit, locale: options.locale });
|
|
405
405
|
|
|
406
406
|
if (options.dryRun) {
|
|
407
407
|
const files = await listAllFiles(targetDir);
|
|
@@ -1650,15 +1650,38 @@ async function stripTailwindFromPrettier(prettierPath) {
|
|
|
1650
1650
|
* strip 한다(없으면 `.npmignore` fallback 으로 사용). 사용자에게 도착하지 않으니
|
|
1651
1651
|
* 템플릿엔 `gitignore` 로 두고 복사 직후 dot-prefix 를 붙인다.
|
|
1652
1652
|
*
|
|
1653
|
+
* gitInit 옵션:
|
|
1654
|
+
* - undefined (auto): parent 가 이미 git tree 안이면 스킵, 아니면 init. nested .git
|
|
1655
|
+
* 충돌 방지 — 기존 monorepo / 사용자 작업 트리 안에서 호출 시 안전.
|
|
1656
|
+
* - true: 무조건 init (parent 가 git tree 안이어도). nested 가 의도된 경우.
|
|
1657
|
+
* - false: 무조건 스킵.
|
|
1658
|
+
*
|
|
1653
1659
|
* git init 은 dry-run 에서는 스킵하고, 실패해도(git 미설치 등) 조용히 넘어간다.
|
|
1654
1660
|
*/
|
|
1655
|
-
async function finalizeProject(targetDir, { dryRun = false } = {}) {
|
|
1661
|
+
async function finalizeProject(targetDir, { dryRun = false, gitInit, locale } = {}) {
|
|
1656
1662
|
// 모노레포 / sub-app 까지 모든 `gitignore` 를 `.gitignore` 로 rename.
|
|
1657
1663
|
// root 만 처리하면 apps/<name>/gitignore 가 그대로 남아 node_modules/dist 가 staged 된다 (v0.93.0 버그).
|
|
1658
1664
|
await renameAllGitignoreRecursive(targetDir);
|
|
1659
1665
|
|
|
1666
|
+
// locale 후처리 — 한국어면 globals.css 들에 Pretendard 자동 적용 (Aifice 피드백 3.1).
|
|
1667
|
+
// dryRun 이면 skip — globals.css 가 디스크에 안 써졌을 수 있다.
|
|
1668
|
+
if (!dryRun && locale === 'ko') {
|
|
1669
|
+
await injectLocaleFont(targetDir, 'ko');
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1660
1672
|
if (dryRun) return;
|
|
1661
1673
|
|
|
1674
|
+
const decision = resolveGitInit(targetDir, gitInit);
|
|
1675
|
+
if (!decision.init) {
|
|
1676
|
+
if (decision.reason === 'nested') {
|
|
1677
|
+
console.log(
|
|
1678
|
+
`\n ℹ 이미 git tree 안이라 .git 초기화를 스킵했습니다 (parent: ${decision.parentRepo}).\n` +
|
|
1679
|
+
` nested git repo 가 의도된 경우 --git-init 으로 강제할 수 있습니다.`,
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1662
1685
|
try {
|
|
1663
1686
|
execSync('git init -q', { cwd: targetDir, stdio: 'ignore' });
|
|
1664
1687
|
} catch {
|
|
@@ -1666,6 +1689,95 @@ async function finalizeProject(targetDir, { dryRun = false } = {}) {
|
|
|
1666
1689
|
}
|
|
1667
1690
|
}
|
|
1668
1691
|
|
|
1692
|
+
/**
|
|
1693
|
+
* `locale` 옵션 후처리 — locale=ko 면 스캐폴드된 모든 globals.css 에 Pretendard 폰트 적용.
|
|
1694
|
+
*
|
|
1695
|
+
* 한국어 사용자가 sh-ui 를 init 한 직후 "Pretendard 로 교체" 가 거의 100% 첫 작업이라
|
|
1696
|
+
* (Aifice 피드백 3.1), 이를 옵션 하나로 자동화. 적용 방식:
|
|
1697
|
+
* - external-imports 마커 안에 Pretendard CDN @import 라인 prepend (Tailwind import 보다 먼저)
|
|
1698
|
+
* - 파일 끝에 `body { font-family: 'Pretendard Variable', ... }` rule 추가
|
|
1699
|
+
*
|
|
1700
|
+
* idempotent: 이미 'Pretendard Variable' 문자열이 있으면 skip.
|
|
1701
|
+
* monorepo 도 안전 — targetDir 하위 모든 globals.css 를 재귀 스캔 (node_modules / .git / _arch 제외).
|
|
1702
|
+
* Flutter 는 globals.css 가 없어 자동 no-op.
|
|
1703
|
+
*/
|
|
1704
|
+
async function injectLocaleFont(targetDir, locale) {
|
|
1705
|
+
if (locale !== 'ko') return;
|
|
1706
|
+
|
|
1707
|
+
const PRETENDARD_IMPORT =
|
|
1708
|
+
"@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.css');\n";
|
|
1709
|
+
const FONT_FAMILY_RULE =
|
|
1710
|
+
"\n/* sh-ui:locale=ko — Pretendard 기본 적용. 사용자 정의로 override 가능. */\n" +
|
|
1711
|
+
"body { font-family: 'Pretendard Variable', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif; }\n";
|
|
1712
|
+
const END_MARKER = '/* sh-ui:external-imports-end */';
|
|
1713
|
+
|
|
1714
|
+
const targets = [];
|
|
1715
|
+
async function walk(dir) {
|
|
1716
|
+
let entries;
|
|
1717
|
+
try {
|
|
1718
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1719
|
+
} catch {
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
for (const e of entries) {
|
|
1723
|
+
const full = path.join(dir, e.name);
|
|
1724
|
+
if (e.isDirectory()) {
|
|
1725
|
+
if (['node_modules', '.git', '.next', 'dist', '_arch'].includes(e.name)) continue;
|
|
1726
|
+
await walk(full);
|
|
1727
|
+
} else if (e.isFile() && e.name === 'globals.css') {
|
|
1728
|
+
targets.push(full);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
await walk(targetDir);
|
|
1733
|
+
|
|
1734
|
+
for (const abs of targets) {
|
|
1735
|
+
let css = await fs.readFile(abs, 'utf-8');
|
|
1736
|
+
if (css.includes("'Pretendard Variable'")) continue;
|
|
1737
|
+
if (!css.includes(END_MARKER)) continue; // 우리 마커 없는 파일은 안전상 건드리지 않음
|
|
1738
|
+
css = css.replace(END_MARKER, `${PRETENDARD_IMPORT}${END_MARKER}`);
|
|
1739
|
+
css += FONT_FAMILY_RULE;
|
|
1740
|
+
await fs.writeFile(abs, css);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
/**
|
|
1745
|
+
* git init 실행 여부 결정. auto 모드는 parent 가 git tree 안이면 스킵 — nested .git
|
|
1746
|
+
* 충돌 방지. 명시 override (true/false) 가 있으면 그대로 따른다.
|
|
1747
|
+
*
|
|
1748
|
+
* 감지: `git -C <parentDir> rev-parse --is-inside-work-tree` 출력이 "true" 면 안.
|
|
1749
|
+
* git 미설치 / 권한 문제 등으로 명령이 실패하면 트리 밖으로 간주 (안전 디폴트: init 시도).
|
|
1750
|
+
*/
|
|
1751
|
+
function resolveGitInit(targetDir, override) {
|
|
1752
|
+
if (override === false) return { init: false, reason: 'explicit-skip' };
|
|
1753
|
+
if (override === true) return { init: true, reason: 'explicit-force' };
|
|
1754
|
+
|
|
1755
|
+
const parentDir = path.dirname(targetDir);
|
|
1756
|
+
try {
|
|
1757
|
+
const result = execSync('git rev-parse --is-inside-work-tree', {
|
|
1758
|
+
cwd: parentDir,
|
|
1759
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1760
|
+
})
|
|
1761
|
+
.toString()
|
|
1762
|
+
.trim();
|
|
1763
|
+
if (result === 'true') {
|
|
1764
|
+
let parentRepo = parentDir;
|
|
1765
|
+
try {
|
|
1766
|
+
parentRepo = execSync('git rev-parse --show-toplevel', {
|
|
1767
|
+
cwd: parentDir,
|
|
1768
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1769
|
+
})
|
|
1770
|
+
.toString()
|
|
1771
|
+
.trim() || parentDir;
|
|
1772
|
+
} catch {}
|
|
1773
|
+
return { init: false, reason: 'nested', parentRepo };
|
|
1774
|
+
}
|
|
1775
|
+
} catch {
|
|
1776
|
+
// parent 가 git tree 밖 (또는 git 미설치) — 안전 디폴트: init 시도.
|
|
1777
|
+
}
|
|
1778
|
+
return { init: true, reason: 'auto' };
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1669
1781
|
/**
|
|
1670
1782
|
* CLAUDE.md 의 `{{PLATFORM_APP_DESCRIPTION}}` 치환용 문장 — AI 에이전트에게 어떤 플랫폼인지
|
|
1671
1783
|
* 정확히 전달해서 잘못된 컨벤션 (예: vite 프로젝트에 App Router 가정) 적용 방지.
|
package/src/create/index.mjs
CHANGED
|
@@ -30,8 +30,11 @@ export const HELP_TEXT = `sh-ui create — sh-ui 프로젝트 스캐폴드 (Next
|
|
|
30
30
|
--css <${CSS_FRAMEWORKS_SUPPORTED.join('|')}> CSS 프레임워크. base 파일까지 분기 emit (tailwind/plain/css-modules)
|
|
31
31
|
--i18n <react-i18next|none> vite 전용 — react-i18next 셋업 emit (i18n config + I18nProvider). 기본 none (v0.92.0+)
|
|
32
32
|
--locales <ko,en> i18n 활성화 시 생성할 locale 코드 (comma-separated). 기본 'ko,en'
|
|
33
|
+
--locale <default|ko> 사용자 지역 디폴트 가정 — 'ko' 선택 시 globals.css 에 Pretendard 자동 적용 (v0.103.0+)
|
|
33
34
|
--yes 디렉토리 덮어쓰기 + 모노레포 기본값 자동 채택
|
|
34
35
|
--dry-run 파일을 쓰지 않고 작성될 파일 목록만 출력
|
|
36
|
+
--no-git-init git init 스킵 (기존 git tree 안에서 호출 시 nested .git 충돌 방지). 기본 auto — parent 가 git tree 안이면 자동 스킵
|
|
37
|
+
--git-init git init 무조건 실행 (nested 가 의도된 경우)
|
|
35
38
|
-h, --help 이 도움말
|
|
36
39
|
|
|
37
40
|
예 (대화형):
|
|
@@ -231,6 +231,9 @@ export function RootLayout({
|
|
|
231
231
|
export const routing = defineRouting({
|
|
232
232
|
locales: ['ko', 'en'],
|
|
233
233
|
defaultLocale: 'ko',
|
|
234
|
+
// ko 는 prefix 없이 '/', en 만 '/en/...' — 한국 사용자 베이스 + 영문 보조 페어가 흔한 패턴.
|
|
235
|
+
// 모든 locale 에 prefix 강제하고 싶으면 'always', 절대 prefix 안 붙이면 'never'.
|
|
236
|
+
localePrefix: 'as-needed',
|
|
234
237
|
});
|
|
235
238
|
`,
|
|
236
239
|
|
|
@@ -24,6 +24,9 @@ const OPTIONAL_TOKEN_KEYS = [
|
|
|
24
24
|
// fallback 을 두지만, Tailwind @theme inline 의 --color-sidebar-* 가 :root 에서 해석되도록
|
|
25
25
|
// tokens.css 에도 끌어올린다.
|
|
26
26
|
'sidebar-bg', 'sidebar-fg', 'sidebar-border', 'sidebar-accent', 'sidebar-accent-fg',
|
|
27
|
+
// v0.100.0+ — accent 토큰. primary 와 의미 분리: primary = 브랜드 action color,
|
|
28
|
+
// accent = signature highlight (선택 상태·링크·hover bg 등). 디폴트는 primary 와 동일.
|
|
29
|
+
'accent', 'accent-foreground', 'accent-hover',
|
|
27
30
|
];
|
|
28
31
|
|
|
29
32
|
/**
|