sh-ui-cli 0.39.0 → 0.41.0
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 +32 -0
- package/data/registry/react/components/accordion/index.tsx +24 -10
- package/data/registry/react/components/accordion/styles.css +20 -3
- package/data/registry/react/components/numeric-input/index.tsx +150 -0
- package/data/registry/react/components/numeric-input/styles.css +56 -0
- package/data/registry/react/registry.json +16 -0
- package/data/summaries/react.json +2 -1
- package/package.json +1 -1
- package/src/create/generator.js +28 -0
- package/templates/flutter-standalone/gitignore +47 -0
- package/templates/monorepo/gitignore +44 -0
- package/templates/nextjs-standalone/gitignore +42 -0
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$description": "sh-ui 릴리즈 노트 단일 소스. docs(React)와 showcase(Flutter)가 함께 읽는다. 새 릴리즈마다 맨 앞에 추가.",
|
|
4
4
|
"versions": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.41.0",
|
|
7
|
+
"date": "2026-04-30",
|
|
8
|
+
"title": "sh-ui create — git 자동 초기화 + .gitignore 자동 생성",
|
|
9
|
+
"type": "minor",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**`sh-ui create` 직후 `git init` 자동 실행** — 별도 명령 없이 바로 첫 커밋 가능한 상태. git 미설치 등 실패 시는 조용히 스킵 (스캐폴드 자체는 성공)",
|
|
12
|
+
"**`.gitignore` 자동 생성** — Next.js standalone / monorepo / Flutter 각 템플릿에 맞는 ignore 패턴 동봉. 모노레포는 `.turbo` `.vercel`, Flutter 는 `.dart_tool/` `**/ios/Flutter/.last_build_id` 등 플랫폼 표준 패턴 + `.claude/settings.local.json` 공통",
|
|
13
|
+
"**npm strip 우회** — npm publish 가 패키지 내 `.gitignore` 를 자동 strip 하던 이슈를 템플릿 파일명 `gitignore` (no-dot) → 스캐폴드 시 `.gitignore` 로 리네이밍하는 방식으로 해결",
|
|
14
|
+
"dry-run 모드는 git init / 파일 쓰기 모두 우회 (기존 동작 유지)"
|
|
15
|
+
],
|
|
16
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.41.0"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"version": "0.40.0",
|
|
20
|
+
"date": "2026-04-29",
|
|
21
|
+
"title": "NumericInput 컴포넌트 + 토큰 편집기 UX 대폭 개선 (Accordion size, ShadowBuilder, XY 패드)",
|
|
22
|
+
"type": "minor",
|
|
23
|
+
"highlights": [
|
|
24
|
+
"**NumericInput** — 정식 sh-ui 컴포넌트 신설. 슬라이더 동반·토큰 편집기 같은 컴팩트 컨텍스트용 숫자 입력. type=text + inputMode=decimal + buffer state — Chrome 의 type=number select() 미지원 회피, '-'/'1.' 같은 transient 입력 허용, ArrowUp/Down step. NumberInput(폼·금액용) 과 책임 분리. `npx sh-ui-cli add numeric-input`",
|
|
25
|
+
"**Accordion size prop** — `size: 'sm' | 'md'`. sm 은 padding 8/4 + font 12px + chevron 12px 로 좁은 사이드바·다중 섹션에 적합. 페이지별 CSS override 대신 컴포넌트 레벨 variant 로 승격",
|
|
26
|
+
"**ShadowBuilder** (playground) — raw CSS `0 4px 12px rgba(...)` 직접 입력 → Figma 스타일 시각 편집기. X/Y/Blur/Spread NumericInput + ColorPicker + alpha %",
|
|
27
|
+
"**XY 패드 드래그** — ShadowBuilder 의 미리보기 박스가 ColorPicker 색공간처럼 클릭/드래그 가능. pointer 위치 → X/Y offset (1:1 px, ±50 클램프). pointer capture + touch-action: none 으로 모바일 터치 드래그도 지원",
|
|
28
|
+
"**advanced 모드 8 섹션** native details → sh-ui Accordion size=sm 으로 교체. chevron 인디케이터로 클릭 가능 신호 명확. 내보내기 섹션도 동일 패턴",
|
|
29
|
+
"**Accordion trigger hover** underline → background var(--background-muted) 틴트, disabled 가드(`:not([disabled]):not([data-disabled])`) — disabled 항목은 hover 무효과",
|
|
30
|
+
"**슬라이더 동반 키보드 입력** — 8 카테고리 31 슬라이더 + 그라데이션 9 슬라이더 옆 값 표시를 NumericInput 으로 교체. 드래그 + 키보드 입력 둘 다 지원. Slider docs 의 ControlledDemo 도 양방향 동기화로 업그레이드",
|
|
31
|
+
"**초기화 3분기** — '전체 초기화' / 'Light 초기화' / 'Dark 초기화'. 이전엔 모드별 한 버튼 (색만 reset) 또는 전체 한 버튼만 — 모드 색만 빠르게 되돌리는 use case + 모든 카테고리 nuke 둘 다 지원",
|
|
32
|
+
"**encodeTheme UTF-8 안전** — shadow/ease/gradient 가 자유 string 이라 한글 IME 입력 시 btoa(Latin1-only) InvalidCharacterError 회귀. TextEncoder UTF-8 바이트 → byte 별 String.fromCharCode → btoa 패턴으로 교체",
|
|
33
|
+
"**docs 페이지** — /components/numeric-input 신규 (server component + _demos 분리), Accordion size 예시 + API Reference 보강, '스타일 커스터마이즈' 에 hover 효과 override 안내"
|
|
34
|
+
],
|
|
35
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.40.0"
|
|
36
|
+
},
|
|
5
37
|
{
|
|
6
38
|
"version": "0.39.0",
|
|
7
39
|
"date": "2026-04-29",
|
|
@@ -8,16 +8,30 @@ function cx(...args: (string | undefined | false)[]) {
|
|
|
8
8
|
|
|
9
9
|
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
10
10
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
export type AccordionSize = "sm" | "md";
|
|
12
|
+
|
|
13
|
+
type AccordionProps = WithStringClassName<
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof BaseAccordion.Root>
|
|
15
|
+
> & {
|
|
16
|
+
/**
|
|
17
|
+
* 트리거 + chevron + content 의 패딩·폰트 크기 묶음.
|
|
18
|
+
* - `md` (기본) — padding 16/4, font 15px, chevron 16px
|
|
19
|
+
* - `sm` — padding 8/4, font 12px, chevron 12px. 좁은 사이드바·다중 섹션에 적합.
|
|
20
|
+
* @default "md"
|
|
21
|
+
*/
|
|
22
|
+
size?: AccordionSize;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Accordion = React.forwardRef<HTMLDivElement, AccordionProps>(
|
|
26
|
+
({ className, size = "md", ...props }, ref) => (
|
|
27
|
+
<BaseAccordion.Root
|
|
28
|
+
ref={ref}
|
|
29
|
+
className={cx("sh-ui-accordion", className)}
|
|
30
|
+
data-size={size}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
),
|
|
34
|
+
);
|
|
21
35
|
Accordion.displayName = "Accordion";
|
|
22
36
|
|
|
23
37
|
export const AccordionItem = React.forwardRef<
|
|
@@ -32,12 +32,13 @@
|
|
|
32
32
|
line-height: 1.4;
|
|
33
33
|
text-align: left;
|
|
34
34
|
cursor: pointer;
|
|
35
|
+
transition: background-color var(--duration-fast) var(--ease-standard);
|
|
35
36
|
-webkit-tap-highlight-color: transparent;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
/* hover는 enabled 일 때만. background tint — 다른 hover 효과를 원하면 className/style 로 override. */
|
|
40
|
+
.sh-ui-accordion__trigger:not([disabled]):not([data-disabled]):hover {
|
|
41
|
+
background: var(--background-muted);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
.sh-ui-accordion__trigger:focus-visible {
|
|
@@ -86,6 +87,22 @@
|
|
|
86
87
|
color: var(--foreground-muted);
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/* size="sm" — 좁은 사이드바·다중 섹션에 적합한 컴팩트 변형. */
|
|
91
|
+
.sh-ui-accordion[data-size="sm"] .sh-ui-accordion__trigger {
|
|
92
|
+
padding: var(--space-2) var(--space-1);
|
|
93
|
+
font-size: var(--text-xs);
|
|
94
|
+
line-height: 1.2;
|
|
95
|
+
}
|
|
96
|
+
.sh-ui-accordion[data-size="sm"] .sh-ui-accordion__chevron {
|
|
97
|
+
width: 12px;
|
|
98
|
+
height: 12px;
|
|
99
|
+
}
|
|
100
|
+
.sh-ui-accordion[data-size="sm"] .sh-ui-accordion__content {
|
|
101
|
+
padding: 0 var(--space-1) var(--space-2);
|
|
102
|
+
font-size: var(--text-xs);
|
|
103
|
+
line-height: 1.5;
|
|
104
|
+
}
|
|
105
|
+
|
|
89
106
|
@media (prefers-reduced-motion: reduce) {
|
|
90
107
|
.sh-ui-accordion__panel,
|
|
91
108
|
.sh-ui-accordion__chevron {
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import "./styles.css";
|
|
5
|
+
|
|
6
|
+
function cx(...args: (string | undefined | null | false)[]) {
|
|
7
|
+
return args.filter(Boolean).join(" ");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface NumericInputProps
|
|
11
|
+
extends Omit<
|
|
12
|
+
React.InputHTMLAttributes<HTMLInputElement>,
|
|
13
|
+
"value" | "defaultValue" | "onChange" | "type" | "min" | "max" | "step"
|
|
14
|
+
> {
|
|
15
|
+
/** 제어 모드 값. */
|
|
16
|
+
value?: number;
|
|
17
|
+
/** 비제어 모드 초기값. */
|
|
18
|
+
defaultValue?: number;
|
|
19
|
+
/** 값 변경 콜백. min/max 범위로 자동 clamp 된 값이 전달된다. */
|
|
20
|
+
onValueChange?: (value: number) => void;
|
|
21
|
+
/** 허용 최솟값. 입력값이 이보다 작으면 자동 clamp. */
|
|
22
|
+
min?: number;
|
|
23
|
+
/** 허용 최댓값. 입력값이 이보다 크면 자동 clamp. */
|
|
24
|
+
max?: number;
|
|
25
|
+
/** 화살표 키 step 폭. 디폴트 1. */
|
|
26
|
+
step?: number;
|
|
27
|
+
/** 값 우측에 부착할 단위 표시 (px / ms / % / ° 등). */
|
|
28
|
+
unit?: React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 슬라이더 동반·토큰 편집 등 컴팩트 컨텍스트에 적합한 숫자 입력.
|
|
33
|
+
*
|
|
34
|
+
* 구현 특이점:
|
|
35
|
+
* - `type="text"` + `inputMode="decimal"` — type=number 가 Chrome 에서 select() 와
|
|
36
|
+
* selectionStart/End 를 지원하지 않아 "0 위에 2 타이핑 → 02" 회귀가 발생함.
|
|
37
|
+
* text 로 바꾸고 우리가 직접 숫자 검증/클램프.
|
|
38
|
+
* - 내부 buffer state — "-", "1.", "" 같이 입력 중간 transient 상태 허용. 유효한
|
|
39
|
+
* 숫자가 되는 순간 onValueChange 즉시 호출. 포커스 잃을 때 정규화.
|
|
40
|
+
* - focus 시 setTimeout(0) → select() — mouseup 의 커서 재배치 이후에 selection
|
|
41
|
+
* 적용되도록.
|
|
42
|
+
* - ArrowUp/Down 으로 step 조정, Enter 로 blur(commit).
|
|
43
|
+
*
|
|
44
|
+
* 일반 폼 입력에는 `Input` / `NumberInput` 사용 권장.
|
|
45
|
+
*/
|
|
46
|
+
export const NumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
|
47
|
+
(
|
|
48
|
+
{
|
|
49
|
+
value,
|
|
50
|
+
defaultValue,
|
|
51
|
+
onValueChange,
|
|
52
|
+
min,
|
|
53
|
+
max,
|
|
54
|
+
step = 1,
|
|
55
|
+
unit,
|
|
56
|
+
className,
|
|
57
|
+
onFocus,
|
|
58
|
+
onBlur,
|
|
59
|
+
onKeyDown,
|
|
60
|
+
...props
|
|
61
|
+
},
|
|
62
|
+
ref,
|
|
63
|
+
) => {
|
|
64
|
+
const isControlled = value !== undefined;
|
|
65
|
+
const [internal, setInternal] = React.useState<number>(defaultValue ?? 0);
|
|
66
|
+
const current = isControlled ? value! : internal;
|
|
67
|
+
|
|
68
|
+
const [buffer, setBuffer] = React.useState<string>(() => String(current));
|
|
69
|
+
const focusedRef = React.useRef(false);
|
|
70
|
+
|
|
71
|
+
// 포커스 잡지 않은 동안 외부 value 변경되면 buffer 동기화.
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
if (!focusedRef.current) setBuffer(String(current));
|
|
74
|
+
}, [current]);
|
|
75
|
+
|
|
76
|
+
const clamp = (n: number) => {
|
|
77
|
+
let v = n;
|
|
78
|
+
if (min !== undefined && v < min) v = min;
|
|
79
|
+
if (max !== undefined && v > max) v = max;
|
|
80
|
+
return v;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const commit = (n: number): number => {
|
|
84
|
+
const c = clamp(n);
|
|
85
|
+
if (!isControlled) setInternal(c);
|
|
86
|
+
onValueChange?.(c);
|
|
87
|
+
return c;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<span className="sh-ui-numeric-input">
|
|
92
|
+
<input
|
|
93
|
+
ref={ref}
|
|
94
|
+
type="text"
|
|
95
|
+
inputMode="decimal"
|
|
96
|
+
className={cx("sh-ui-numeric-input__input", className)}
|
|
97
|
+
value={buffer}
|
|
98
|
+
onChange={(e) => {
|
|
99
|
+
const raw = e.target.value;
|
|
100
|
+
setBuffer(raw);
|
|
101
|
+
// 입력 중간 상태("", "-", ".", "-.") 는 commit 안 함 — 사용자 타이핑 흐름 유지.
|
|
102
|
+
if (raw === "" || raw === "-" || raw === "." || raw === "-.") return;
|
|
103
|
+
const n = Number(raw);
|
|
104
|
+
if (Number.isFinite(n)) commit(n);
|
|
105
|
+
}}
|
|
106
|
+
onFocus={(e) => {
|
|
107
|
+
focusedRef.current = true;
|
|
108
|
+
const t = e.currentTarget;
|
|
109
|
+
// setTimeout 0 로 미뤄야 mouseup 의 커서 재배치 이후에 select 가 적용됨.
|
|
110
|
+
setTimeout(() => t.select(), 0);
|
|
111
|
+
onFocus?.(e);
|
|
112
|
+
}}
|
|
113
|
+
onBlur={(e) => {
|
|
114
|
+
focusedRef.current = false;
|
|
115
|
+
const n = Number(buffer);
|
|
116
|
+
if (buffer !== "" && Number.isFinite(n)) {
|
|
117
|
+
const c = commit(n);
|
|
118
|
+
setBuffer(String(c));
|
|
119
|
+
} else {
|
|
120
|
+
// 비어있거나 NaN — 마지막 유효 값으로 복원
|
|
121
|
+
setBuffer(String(current));
|
|
122
|
+
}
|
|
123
|
+
onBlur?.(e);
|
|
124
|
+
}}
|
|
125
|
+
onKeyDown={(e) => {
|
|
126
|
+
if (e.key === "ArrowUp") {
|
|
127
|
+
e.preventDefault();
|
|
128
|
+
const next = commit(current + step);
|
|
129
|
+
setBuffer(String(next));
|
|
130
|
+
} else if (e.key === "ArrowDown") {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
const next = commit(current - step);
|
|
133
|
+
setBuffer(String(next));
|
|
134
|
+
} else if (e.key === "Enter") {
|
|
135
|
+
e.currentTarget.blur();
|
|
136
|
+
}
|
|
137
|
+
onKeyDown?.(e);
|
|
138
|
+
}}
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
{unit !== undefined && unit !== "" && (
|
|
142
|
+
<span className="sh-ui-numeric-input__unit" aria-hidden>
|
|
143
|
+
{unit}
|
|
144
|
+
</span>
|
|
145
|
+
)}
|
|
146
|
+
</span>
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
NumericInput.displayName = "NumericInput";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* NumericInput — 컴팩트 monospace 숫자 입력. 슬라이더 동반·토큰 편집기 같은
|
|
2
|
+
좁은 영역용. 일반 폼 입력은 Input / NumberInput 사용. */
|
|
3
|
+
|
|
4
|
+
.sh-ui-numeric-input {
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
align-items: baseline;
|
|
7
|
+
gap: 2px;
|
|
8
|
+
min-width: 3rem;
|
|
9
|
+
justify-content: flex-end;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.sh-ui-numeric-input__input {
|
|
13
|
+
width: 2.5rem;
|
|
14
|
+
padding: 2px 4px;
|
|
15
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
16
|
+
font-size: var(--text-xs);
|
|
17
|
+
line-height: 1.2;
|
|
18
|
+
text-align: right;
|
|
19
|
+
border: 1px solid transparent;
|
|
20
|
+
border-radius: calc(var(--radius) - 4px);
|
|
21
|
+
background: transparent;
|
|
22
|
+
color: var(--foreground);
|
|
23
|
+
appearance: textfield;
|
|
24
|
+
-moz-appearance: textfield;
|
|
25
|
+
transition: border-color var(--duration-fast) var(--ease-standard),
|
|
26
|
+
background-color var(--duration-fast) var(--ease-standard);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* WebKit 의 스피너 버튼 숨김 — 컴팩트 영역에 노이즈. 키보드 step 은 유지됨. */
|
|
30
|
+
.sh-ui-numeric-input__input::-webkit-inner-spin-button,
|
|
31
|
+
.sh-ui-numeric-input__input::-webkit-outer-spin-button {
|
|
32
|
+
-webkit-appearance: none;
|
|
33
|
+
margin: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sh-ui-numeric-input__input:hover:not(:disabled):not(:focus) {
|
|
37
|
+
border-color: var(--border);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.sh-ui-numeric-input__input:focus,
|
|
41
|
+
.sh-ui-numeric-input__input:focus-visible {
|
|
42
|
+
outline: none;
|
|
43
|
+
border-color: var(--foreground);
|
|
44
|
+
background: var(--background);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sh-ui-numeric-input__input:disabled {
|
|
48
|
+
cursor: not-allowed;
|
|
49
|
+
opacity: var(--opacity-disabled);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sh-ui-numeric-input__unit {
|
|
53
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
54
|
+
font-size: var(--text-xs);
|
|
55
|
+
color: var(--foreground-muted);
|
|
56
|
+
}
|
|
@@ -49,6 +49,22 @@
|
|
|
49
49
|
"dependencies": [],
|
|
50
50
|
"registryDependencies": []
|
|
51
51
|
},
|
|
52
|
+
"numeric-input": {
|
|
53
|
+
"name": "numeric-input",
|
|
54
|
+
"type": "component",
|
|
55
|
+
"files": [
|
|
56
|
+
{
|
|
57
|
+
"src": "components/numeric-input/index.tsx",
|
|
58
|
+
"dest": "{components}/numeric-input/index.tsx"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"src": "components/numeric-input/styles.css",
|
|
62
|
+
"dest": "{components}/numeric-input/styles.css"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"dependencies": [],
|
|
66
|
+
"registryDependencies": []
|
|
67
|
+
},
|
|
52
68
|
"file-upload": {
|
|
53
69
|
"name": "file-upload",
|
|
54
70
|
"type": "component",
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
"summaries": {
|
|
4
4
|
"button": "기본 버튼 — variant(primary/secondary/ghost/danger/link) + size(sm/md/lg).",
|
|
5
5
|
"card": "카드 컨테이너 — compound (Card.Header / Card.Body / Card.Footer / Card.Title / Card.Description).",
|
|
6
|
-
"input": "단일 행 텍스트 입력 — hasError 지원.",
|
|
6
|
+
"input": "단일 행 텍스트 입력 — hasError 지원. 같은 모듈에 NumberInput / PasswordInput / PhoneInput / BusinessNumberInput 변형 포함.",
|
|
7
|
+
"numeric-input": "슬라이더 동반·토큰 편집기용 컴팩트 숫자 입력 — onChange 즉시 min/max clamp, focus select-all, 단위(px/ms/%/° 등) suffix. 일반 폼 입력은 NumberInput 권장.",
|
|
7
8
|
"textarea": "여러 행 텍스트 입력 — rows, autoResize.",
|
|
8
9
|
"label": "폼 레이블 — htmlFor로 입력과 연결.",
|
|
9
10
|
"checkbox": "체크박스 — indeterminate 지원 (Base UI).",
|
package/package.json
CHANGED
package/src/create/generator.js
CHANGED
|
@@ -115,6 +115,7 @@ export async function createProject(options = {}) {
|
|
|
115
115
|
|
|
116
116
|
if (platform === 'flutter') {
|
|
117
117
|
await generateFlutter(targetDir, projectName, theme);
|
|
118
|
+
await finalizeProject(targetDir, { dryRun: options.dryRun });
|
|
118
119
|
console.log(`\n✅ ${projectName} Flutter 프로젝트가 생성되었습니다!`);
|
|
119
120
|
console.log(`\n cd ${projectName}`);
|
|
120
121
|
console.log(' flutter pub get');
|
|
@@ -144,6 +145,8 @@ export async function createProject(options = {}) {
|
|
|
144
145
|
await generateMonorepo(targetDir, projectName, plugins, { yes: options.yes, theme });
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
await finalizeProject(targetDir, { dryRun: options.dryRun });
|
|
149
|
+
|
|
147
150
|
if (options.dryRun) {
|
|
148
151
|
const files = await listAllFiles(targetDir);
|
|
149
152
|
console.log(`\n[DRY RUN] ${projectName} 스캐폴드 시 작성될 파일 (${files.length}개):\n`);
|
|
@@ -416,6 +419,31 @@ async function generateApp(targetDir, appName, port, plugins) {
|
|
|
416
419
|
|
|
417
420
|
// ─── Helpers ───
|
|
418
421
|
|
|
422
|
+
/**
|
|
423
|
+
* 스캐폴드 마무리 — `gitignore` 파일을 `.gitignore` 로 되돌리고 `git init` 실행.
|
|
424
|
+
*
|
|
425
|
+
* 왜 이름을 우회하는가: npm publish 는 패키지 안의 `.gitignore` 를 자동으로
|
|
426
|
+
* strip 한다(없으면 `.npmignore` fallback 으로 사용). 사용자에게 도착하지 않으니
|
|
427
|
+
* 템플릿엔 `gitignore` 로 두고 복사 직후 dot-prefix 를 붙인다.
|
|
428
|
+
*
|
|
429
|
+
* git init 은 dry-run 에서는 스킵하고, 실패해도(git 미설치 등) 조용히 넘어간다.
|
|
430
|
+
*/
|
|
431
|
+
async function finalizeProject(targetDir, { dryRun = false } = {}) {
|
|
432
|
+
const noDot = path.join(targetDir, 'gitignore');
|
|
433
|
+
const withDot = path.join(targetDir, '.gitignore');
|
|
434
|
+
if (await fs.pathExists(noDot)) {
|
|
435
|
+
await fs.move(noDot, withDot, { overwrite: true });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (dryRun) return;
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
execSync('git init -q', { cwd: targetDir, stdio: 'ignore' });
|
|
442
|
+
} catch {
|
|
443
|
+
// git 미설치 / 권한 문제 — 스캐폴드 자체는 성공이므로 조용히 넘어간다.
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
419
447
|
async function replaceInAllFiles(dir, search, replace) {
|
|
420
448
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
421
449
|
for (const entry of entries) {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Miscellaneous
|
|
2
|
+
*.class
|
|
3
|
+
*.log
|
|
4
|
+
*.pyc
|
|
5
|
+
*.swp
|
|
6
|
+
.DS_Store
|
|
7
|
+
.atom/
|
|
8
|
+
.build/
|
|
9
|
+
.buildlog/
|
|
10
|
+
.history
|
|
11
|
+
.svn/
|
|
12
|
+
.swiftpm/
|
|
13
|
+
migrate_working_dir/
|
|
14
|
+
|
|
15
|
+
# IntelliJ related
|
|
16
|
+
*.iml
|
|
17
|
+
*.ipr
|
|
18
|
+
*.iws
|
|
19
|
+
.idea/
|
|
20
|
+
|
|
21
|
+
# VS Code
|
|
22
|
+
#.vscode/
|
|
23
|
+
|
|
24
|
+
# Flutter/Dart/Pub related
|
|
25
|
+
**/doc/api/
|
|
26
|
+
**/ios/Flutter/.last_build_id
|
|
27
|
+
.dart_tool/
|
|
28
|
+
.flutter-plugins
|
|
29
|
+
.flutter-plugins-dependencies
|
|
30
|
+
.pub-cache/
|
|
31
|
+
.pub/
|
|
32
|
+
/build/
|
|
33
|
+
/coverage/
|
|
34
|
+
|
|
35
|
+
# Symbolication
|
|
36
|
+
app.*.symbols
|
|
37
|
+
|
|
38
|
+
# Obfuscation
|
|
39
|
+
app.*.map.json
|
|
40
|
+
|
|
41
|
+
# Android build artifacts
|
|
42
|
+
/android/app/debug
|
|
43
|
+
/android/app/profile
|
|
44
|
+
/android/app/release
|
|
45
|
+
|
|
46
|
+
# Claude Code (개인 설정 제외, rules/는 공유)
|
|
47
|
+
.claude/settings.local.json
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# Dependencies
|
|
4
|
+
node_modules
|
|
5
|
+
.pnp
|
|
6
|
+
.pnp.js
|
|
7
|
+
|
|
8
|
+
# Local env files
|
|
9
|
+
.env
|
|
10
|
+
.env.local
|
|
11
|
+
.env.development.local
|
|
12
|
+
.env.test.local
|
|
13
|
+
.env.production.local
|
|
14
|
+
|
|
15
|
+
# Testing
|
|
16
|
+
coverage
|
|
17
|
+
|
|
18
|
+
# Turbo
|
|
19
|
+
.turbo
|
|
20
|
+
|
|
21
|
+
# Vercel
|
|
22
|
+
.vercel
|
|
23
|
+
|
|
24
|
+
# Build Outputs
|
|
25
|
+
.next/
|
|
26
|
+
out/
|
|
27
|
+
build
|
|
28
|
+
dist
|
|
29
|
+
|
|
30
|
+
# Debug
|
|
31
|
+
npm-debug.log*
|
|
32
|
+
|
|
33
|
+
# Sentry
|
|
34
|
+
.env.sentry-build-plugin
|
|
35
|
+
|
|
36
|
+
# Misc
|
|
37
|
+
.DS_Store
|
|
38
|
+
*.pem
|
|
39
|
+
|
|
40
|
+
# IDE
|
|
41
|
+
.idea/
|
|
42
|
+
|
|
43
|
+
# Claude Code (개인 설정 제외, rules/는 공유)
|
|
44
|
+
.claude/settings.local.json
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# Dependencies
|
|
4
|
+
node_modules
|
|
5
|
+
.pnp
|
|
6
|
+
.pnp.js
|
|
7
|
+
|
|
8
|
+
# Local env files
|
|
9
|
+
.env
|
|
10
|
+
.env.local
|
|
11
|
+
.env.development.local
|
|
12
|
+
.env.test.local
|
|
13
|
+
.env.production.local
|
|
14
|
+
|
|
15
|
+
# Testing
|
|
16
|
+
coverage
|
|
17
|
+
|
|
18
|
+
# Vercel
|
|
19
|
+
.vercel
|
|
20
|
+
|
|
21
|
+
# Build Outputs
|
|
22
|
+
.next/
|
|
23
|
+
out/
|
|
24
|
+
build
|
|
25
|
+
dist
|
|
26
|
+
|
|
27
|
+
# Debug
|
|
28
|
+
npm-debug.log*
|
|
29
|
+
|
|
30
|
+
# Sentry
|
|
31
|
+
.env.sentry-build-plugin
|
|
32
|
+
|
|
33
|
+
# Misc
|
|
34
|
+
.DS_Store
|
|
35
|
+
*.pem
|
|
36
|
+
|
|
37
|
+
# IDE
|
|
38
|
+
.idea/
|
|
39
|
+
.vscode/
|
|
40
|
+
|
|
41
|
+
# Claude Code (개인 설정 제외, rules/는 공유)
|
|
42
|
+
.claude/settings.local.json
|