sh-ui-cli 0.79.0 → 0.80.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 +27 -0
- package/data/registry/react/components/sidebar/index.tsx +1 -1
- package/package.json +6 -4
- package/src/api.d.ts +48 -0
- package/src/api.js +2 -0
- package/src/create/describeTemplate.js +274 -0
- package/src/create/templateManifest.js +328 -0
- package/src/create/theme/decode.js +5 -0
- package/src/create/theme/presets.js +44 -0
- package/src/mcp.mjs +46 -0
- package/templates/monorepo/packages/ui/ui-core/package.json +2 -1
- package/templates/monorepo/packages/ui/ui-core/sh-ui.config.json +2 -1
- package/templates/monorepo/packages/ui/ui-core/src/styles/.gitkeep +0 -0
- package/templates/nextjs-standalone/_arch/flat/lib/styles/tokens.css +34 -1
- package/templates/nextjs-standalone/_arch/fsd/src/shared/styles/tokens.css +34 -1
- package/templates/ui-app-template/src/styles/tokens.css +34 -1
|
@@ -2,6 +2,33 @@
|
|
|
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.80.1",
|
|
7
|
+
"date": "2026-05-12",
|
|
8
|
+
"title": "monorepo create_project P0 fix — paths.styles · sidebar 토큰 · sidebar import",
|
|
9
|
+
"type": "patch",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**A1/A2: `create_project --structure monorepo` ui-core 가 styles 경로 누락하던 결함 수정** — `sh-ui.config.json` 에 `paths.styles: \"src/styles\"`, `package.json` `exports` 에 `\"./styles/*\": \"./src/styles/*\"` 추가. 이전엔 직후 `add base focus-ring …` 호출이 `paths.styles 가 sh-ui.config.json에 없습니다` 로 즉시 실패.",
|
|
12
|
+
"**A3: 신규 프로젝트 tokens.css 에 sidebar/success/warning/info 토큰 emit** — sidebar 컴포넌트가 요구하는 `--sidebar-bg / --sidebar-fg / --sidebar-border / --sidebar-accent / --sidebar-accent-fg` 가 신규 프로젝트 tokens.css 에 빠져 sidebar 가 `@theme inline` 매핑에서 undefined 로 풀리던 결함 수정. presets(neutral · slate · rose · emerald · violet) light/dark 양쪽에 5+6 토큰 추가, `OPTIONAL_TOKEN_KEYS` 에 sidebar 키 등록. 베이스라인 3개 tokens.css 템플릿도 동기화.",
|
|
13
|
+
"**B1: registry/docs sidebar `.tsx` 확장자 import 제거** — `import { Popover } from \"../popover/index.tsx\"` 가 TS5097 (allowImportingTsExtensions) 로 docs typecheck 실패하던 결함 수정. 듀얼 카피본 양쪽 수정."
|
|
14
|
+
],
|
|
15
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.80.1"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"version": "0.80.0",
|
|
19
|
+
"date": "2026-05-12",
|
|
20
|
+
"title": "describeTemplate — 프로젝트 생성 전 파일 트리 미리보기",
|
|
21
|
+
"type": "minor",
|
|
22
|
+
"highlights": [
|
|
23
|
+
"**`sh-ui-cli/api` 에 `describeTemplate()` 추가** — 옵션 조합(platform/structure/arch/plugins/cssFramework/appName) 으로 실제 생성 없이 emit 될 파일 경로 트리를 사전 계산. 베이스 템플릿 + arch 오버레이 + `plugin.files` + cssFramework 분기 + `plugin.transforms` (이동/삭제) 까지 정확히 반영. fs 접근 없는 순수 함수라 브라우저 번들 가능.",
|
|
24
|
+
"**`/create` 페이지 헤더에 파일 미리보기 별도 다이얼로그** — `FolderSearch` 아이콘 + 별도 진입. 옵션 picker(`ProjectOptionsForm` 으로 추출) + 요약/상세 토글 + 출처별 lucide 아이콘 분류(base=Package, arch=Layers, sentry=ShieldAlert, intl=Languages, …) + hover/진행막대 시각화. 상세 트리는 폴더/파일 lucide 아이콘 + 컴팩트 indent.",
|
|
25
|
+
"**상세 트리에서 파일 클릭 → 내용 viewer** — `/api/template-content` route 가 lazy fetch. 베이스/arch 오버레이 디스크 파일은 readFile, plugin.files 는 plugin 모듈 호출 결과, cssFramework 동적 변종은 placeholder. 출처 라벨 표시.",
|
|
26
|
+
"**MCP `sh_ui_describe_template` 툴 노출** — \"플러그인 켜면 어떤 파일 추가돼?\" 류 질문에 IDE-내 에이전트가 실제 스캐폴드 없이 답할 수 있게.",
|
|
27
|
+
"**docs UI raw 컴포넌트 일소** — `/create` 헤더 / TokenEditor / ShowcasePicker / ShowcaseCanvas / ExportBlock / ShadowBuilder / GradientBuilder / 검색 다이얼로그 / examples 갤러리의 raw `<button>`·`<input>` 을 sh-ui `Button`/`Input` 으로 통일. 풀 커스텀 className 영역도 `Button + className` 조합으로 base 동작(hover/focus/active) 받으면서 디자인 유지.",
|
|
28
|
+
"**빌드 인프라** — `scripts/build-template-manifest.mjs` 가 `packages/cli/templates/` 를 스캔해 `src/create/templateManifest.js` 로 emit (pretest / prepublishOnly 에서 자동 실행). 템플릿 변경 시 drift 없음."
|
|
29
|
+
],
|
|
30
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.80.0"
|
|
31
|
+
},
|
|
5
32
|
{
|
|
6
33
|
"version": "0.79.0",
|
|
7
34
|
"date": "2026-05-12",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "@SH_UI_UTILS@";
|
|
5
5
|
import { ChevronRightIcon, PanelLeftIcon } from "lucide-react";
|
|
6
|
-
import { Popover, PopoverContent, PopoverTrigger } from "../popover
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
|
|
7
7
|
import "./styles.css";
|
|
8
8
|
|
|
9
9
|
const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sh-ui-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.1",
|
|
4
4
|
"description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -44,13 +44,15 @@
|
|
|
44
44
|
"./api": {
|
|
45
45
|
"types": "./src/api.d.ts",
|
|
46
46
|
"default": "./src/api.js"
|
|
47
|
-
}
|
|
47
|
+
},
|
|
48
|
+
"./package.json": "./package.json"
|
|
48
49
|
},
|
|
49
50
|
"scripts": {
|
|
50
51
|
"bundle-data": "node scripts/copy-data.mjs",
|
|
51
|
-
"
|
|
52
|
+
"build:manifest": "node scripts/build-template-manifest.mjs",
|
|
53
|
+
"pretest": "node scripts/build-template-manifest.mjs && node scripts/copy-data.mjs",
|
|
52
54
|
"test": "vitest run",
|
|
53
|
-
"prepublishOnly": "node scripts/copy-data.mjs && node --check bin/sh-ui.mjs"
|
|
55
|
+
"prepublishOnly": "node scripts/build-template-manifest.mjs && node scripts/copy-data.mjs && node --check bin/sh-ui.mjs"
|
|
54
56
|
},
|
|
55
57
|
"files": [
|
|
56
58
|
"bin",
|
package/src/api.d.ts
CHANGED
|
@@ -120,3 +120,51 @@ export interface ThemePreset {
|
|
|
120
120
|
|
|
121
121
|
export const THEME_PRESETS: Record<ThemePresetName, ThemePreset>;
|
|
122
122
|
export const THEME_PRESET_NAMES: readonly ThemePresetName[];
|
|
123
|
+
|
|
124
|
+
/* ─────── 템플릿 트리 미리보기 ─────── */
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 옵션 조합으로 프로젝트 생성 시 어떤 파일이 emit 되는지 사전 계산.
|
|
128
|
+
* fs 접근 없는 순수 함수 — 브라우저 사용 가능.
|
|
129
|
+
*/
|
|
130
|
+
export interface DescribeTemplateOptions {
|
|
131
|
+
platform?: CreatePlatform;
|
|
132
|
+
/** next 일 때만 의미. */
|
|
133
|
+
structure?: CreateStructure;
|
|
134
|
+
/** next 일 때 'fsd' | 'flat' | 'mes'. */
|
|
135
|
+
arch?: string;
|
|
136
|
+
/** 플러그인 name 배열. */
|
|
137
|
+
plugins?: string[];
|
|
138
|
+
cssFramework?: CssFrameworkSupported;
|
|
139
|
+
projectName?: string;
|
|
140
|
+
/** monorepo 첫 앱 이름. 기본 'web'. */
|
|
141
|
+
appName?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface DescribeTemplateGroup {
|
|
145
|
+
/** 'base' | 'arch' | `plugin-${name}` | 'css' | 'transform' | 'monorepo' | 'ui-app' | `app-${id}` */
|
|
146
|
+
id: string;
|
|
147
|
+
label: string;
|
|
148
|
+
paths: string[];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface DescribeTemplateResult {
|
|
152
|
+
/** 모든 파일 경로 (POSIX, 정렬). */
|
|
153
|
+
files: string[];
|
|
154
|
+
/** 출처별 분류 — 빈 그룹은 제외. */
|
|
155
|
+
groups: DescribeTemplateGroup[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function describeTemplate(
|
|
159
|
+
options?: DescribeTemplateOptions,
|
|
160
|
+
): DescribeTemplateResult;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 빌드 타임에 emit 되는 raw 템플릿 인덱스. describeTemplate() 가 내부적으로 사용.
|
|
164
|
+
* 외부 노출은 디버깅 / 마이그레이션 도구용.
|
|
165
|
+
*/
|
|
166
|
+
export interface TemplateManifestEntry {
|
|
167
|
+
base: string[];
|
|
168
|
+
arches?: Record<string, string[]>;
|
|
169
|
+
}
|
|
170
|
+
export const TEMPLATE_MANIFEST: Record<string, TemplateManifestEntry>;
|
package/src/api.js
CHANGED
|
@@ -31,3 +31,5 @@ export {
|
|
|
31
31
|
isKnownArch,
|
|
32
32
|
} from './create/architectures/index.js';
|
|
33
33
|
export { THEME_PRESETS, THEME_PRESET_NAMES } from './create/theme/presets.js';
|
|
34
|
+
export { describeTemplate } from './create/describeTemplate.js';
|
|
35
|
+
export { TEMPLATE_MANIFEST } from './create/templateManifest.js';
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* describeTemplate — "옵션 조합으로 프로젝트 만들면 어떤 파일이 생기는가" 를
|
|
3
|
+
* 실제 생성 없이 계산. apps/docs 의 CreateProjectDialog 가 옵션 토글 시 트리
|
|
4
|
+
* 미리보기를 그리는 데 사용. MCP 툴 `sh_ui_describe_template` 도 동일 함수.
|
|
5
|
+
*
|
|
6
|
+
* 입력: createProject() 가 받는 옵션의 부분집합 + appName(monorepo).
|
|
7
|
+
* 출력: { files: string[], groups: Group[] }
|
|
8
|
+
* - files: 모든 파일 경로(POSIX) 정렬
|
|
9
|
+
* - groups: 출처별 분류 — 'base'/'arch'/'plugin-*'/'css'/'transform' 등
|
|
10
|
+
*
|
|
11
|
+
* 정확도 계약:
|
|
12
|
+
* - 베이스 템플릿 + arch 오버레이 + plugin.files + cssFramework 분기 + plugin.transforms
|
|
13
|
+
* (move/delete) 까지 반영. plugin.transforms 의 replace 는 내용만 바꾸므로 트리 영향 X.
|
|
14
|
+
* - generator.js 가 emit 하는 *파일 경로* 와 1:1 일치하는 것을 목표. content 차이는 무시.
|
|
15
|
+
*
|
|
16
|
+
* 순수성: fs 접근 없음. templateManifest.js (build-template-manifest.mjs 가 emit) 와
|
|
17
|
+
* plugins/architectures 디스크립터만 사용. 브라우저 번들 가능.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { TEMPLATE_MANIFEST } from './templateManifest.js';
|
|
21
|
+
import {
|
|
22
|
+
getArchByName,
|
|
23
|
+
DEFAULT_ARCH,
|
|
24
|
+
isKnownArch,
|
|
25
|
+
} from './architectures/index.js';
|
|
26
|
+
import { getPluginsByNames } from './plugins/index.js';
|
|
27
|
+
import { CSS_FRAMEWORK_DEFAULT } from '../constants.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} DescribeOptions
|
|
31
|
+
* @property {'next'|'flutter'} [platform]
|
|
32
|
+
* @property {'standalone'|'monorepo'} [structure] next 일 때만 의미
|
|
33
|
+
* @property {string} [arch] next 일 때 'fsd'|'flat'|'mes'
|
|
34
|
+
* @property {string[]} [plugins] ['sentry', 'next-intl', 'auth-jwt']
|
|
35
|
+
* @property {'tailwind'|'plain'|'css-modules'} [cssFramework]
|
|
36
|
+
* @property {string} [projectName]
|
|
37
|
+
* @property {string} [appName] monorepo 첫 앱 이름. 기본 'web'
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {Object} Group
|
|
42
|
+
* @property {string} id 'base' | 'arch' | `plugin-${name}` | 'css' | 'transform' | 'monorepo' | 'ui-app' | `app-${id}`
|
|
43
|
+
* @property {string} label 사용자가 보는 한국어 라벨
|
|
44
|
+
* @property {string[]} paths 이 그룹에 귀속된 파일 경로 (정렬)
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {Object} DescribeResult
|
|
49
|
+
* @property {string[]} files 전체 파일 경로 정렬
|
|
50
|
+
* @property {Group[]} groups
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {DescribeOptions} [opts]
|
|
55
|
+
* @returns {DescribeResult}
|
|
56
|
+
*/
|
|
57
|
+
export function describeTemplate(opts = {}) {
|
|
58
|
+
const {
|
|
59
|
+
platform = 'next',
|
|
60
|
+
structure = 'standalone',
|
|
61
|
+
arch: archName = DEFAULT_ARCH,
|
|
62
|
+
plugins: pluginNames = [],
|
|
63
|
+
cssFramework = CSS_FRAMEWORK_DEFAULT,
|
|
64
|
+
appName: rawAppName = 'web',
|
|
65
|
+
} = opts;
|
|
66
|
+
|
|
67
|
+
if (platform === 'flutter') {
|
|
68
|
+
const base = TEMPLATE_MANIFEST['flutter-standalone'].base;
|
|
69
|
+
return finalize([
|
|
70
|
+
makeGroup('base', 'Flutter 베이스', base),
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// platform === 'next'
|
|
75
|
+
const safeArchName = isKnownArch(archName) ? archName : DEFAULT_ARCH;
|
|
76
|
+
const archObj = getArchByName(safeArchName);
|
|
77
|
+
const plugins = getPluginsByNames(pluginNames).sort(
|
|
78
|
+
(a, b) => (a.priority ?? 0) - (b.priority ?? 0),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (structure === 'standalone') {
|
|
82
|
+
const groups = buildNextGroups({
|
|
83
|
+
prefix: '',
|
|
84
|
+
templateKey: 'nextjs-standalone',
|
|
85
|
+
arch: archObj,
|
|
86
|
+
plugins,
|
|
87
|
+
cssFramework,
|
|
88
|
+
});
|
|
89
|
+
return finalize(groups);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// monorepo
|
|
93
|
+
const appName = rawAppName || 'web';
|
|
94
|
+
const groups = [];
|
|
95
|
+
|
|
96
|
+
groups.push(makeGroup(
|
|
97
|
+
'monorepo',
|
|
98
|
+
'모노레포 루트',
|
|
99
|
+
TEMPLATE_MANIFEST['monorepo'].base.slice(),
|
|
100
|
+
));
|
|
101
|
+
|
|
102
|
+
const appGroups = buildNextGroups({
|
|
103
|
+
prefix: `apps/${appName}/`,
|
|
104
|
+
templateKey: 'nextjs-app',
|
|
105
|
+
arch: archObj,
|
|
106
|
+
plugins,
|
|
107
|
+
cssFramework,
|
|
108
|
+
});
|
|
109
|
+
for (const g of appGroups) {
|
|
110
|
+
g.id = `app-${g.id}`;
|
|
111
|
+
g.label = `apps/${appName} — ${g.label}`;
|
|
112
|
+
groups.push(g);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
groups.push(makeGroup(
|
|
116
|
+
'ui-app',
|
|
117
|
+
`packages/ui/ui-apps/ui-${appName}`,
|
|
118
|
+
TEMPLATE_MANIFEST['ui-app-template'].base.map(
|
|
119
|
+
(p) => `packages/ui/ui-apps/ui-${appName}/${p}`,
|
|
120
|
+
),
|
|
121
|
+
));
|
|
122
|
+
|
|
123
|
+
return finalize(groups);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** next 베이스 + arch + plugin.files + css 분기 + plugin.transforms 적용. */
|
|
127
|
+
function buildNextGroups({ prefix, templateKey, arch, plugins, cssFramework }) {
|
|
128
|
+
const tpl = TEMPLATE_MANIFEST[templateKey];
|
|
129
|
+
if (!tpl) {
|
|
130
|
+
throw new Error(`describeTemplate: 알 수 없는 템플릿 키 '${templateKey}'`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const groups = [];
|
|
134
|
+
|
|
135
|
+
groups.push(makeGroup(
|
|
136
|
+
'base',
|
|
137
|
+
`${templateKey} 베이스`,
|
|
138
|
+
tpl.base.map((p) => prefix + p),
|
|
139
|
+
));
|
|
140
|
+
|
|
141
|
+
const archFiles = tpl.arches?.[arch.name] ?? [];
|
|
142
|
+
groups.push(makeGroup(
|
|
143
|
+
'arch',
|
|
144
|
+
`${arch.label} 오버레이`,
|
|
145
|
+
archFiles.map((p) => prefix + p),
|
|
146
|
+
));
|
|
147
|
+
|
|
148
|
+
// plugin.files — arch-aware. 정적이면 그대로, 함수면 호출.
|
|
149
|
+
for (const plugin of plugins) {
|
|
150
|
+
const filesField = resolveArchField(plugin.files, arch);
|
|
151
|
+
if (!filesField) continue;
|
|
152
|
+
const paths = Object.keys(filesField).map((p) => prefix + p);
|
|
153
|
+
if (paths.length === 0) continue;
|
|
154
|
+
groups.push(makeGroup(
|
|
155
|
+
`plugin-${plugin.name}`,
|
|
156
|
+
`+ ${plugin.label}`,
|
|
157
|
+
paths,
|
|
158
|
+
));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// cssFramework: css-modules 면 page.module.css / error.module.css 추가.
|
|
162
|
+
// generator.js applyCssFrameworkVariant 와 동일한 분기 — intl 활성 시 [locale]/, 아니면 app/.
|
|
163
|
+
if (cssFramework === 'css-modules') {
|
|
164
|
+
const intlActive = plugins.some((p) => p.name === 'next-intl');
|
|
165
|
+
const sentryActive = plugins.some((p) => p.name === 'sentry');
|
|
166
|
+
const cssPaths = [];
|
|
167
|
+
const pageDir = intlActive ? 'app/[locale]' : 'app';
|
|
168
|
+
cssPaths.push(`${prefix}${pageDir}/page.module.css`);
|
|
169
|
+
if (sentryActive) {
|
|
170
|
+
cssPaths.push(`${prefix}${pageDir}/error.module.css`);
|
|
171
|
+
}
|
|
172
|
+
groups.push(makeGroup(
|
|
173
|
+
'css',
|
|
174
|
+
'CSS: css-modules 변종',
|
|
175
|
+
cssPaths,
|
|
176
|
+
));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// plugin.transforms — move/delete 만 트리에 영향. replace 는 content-only.
|
|
180
|
+
const moves = [];
|
|
181
|
+
const deletes = [];
|
|
182
|
+
for (const plugin of plugins) {
|
|
183
|
+
const tr = resolveArchField(plugin.transforms, arch);
|
|
184
|
+
if (!tr) continue;
|
|
185
|
+
for (const t of tr) {
|
|
186
|
+
if (t.type === 'move') moves.push({ from: prefix + t.from, to: prefix + t.to });
|
|
187
|
+
if (t.type === 'delete') deletes.push(prefix + t.path);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (moves.length || deletes.length) {
|
|
192
|
+
const added = applyMovesAndDeletes(groups, moves, deletes);
|
|
193
|
+
if (added.length) {
|
|
194
|
+
groups.push(makeGroup(
|
|
195
|
+
'transform',
|
|
196
|
+
'플러그인 transform (이동/생성)',
|
|
197
|
+
added,
|
|
198
|
+
));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return groups;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** arch-aware 필드 resolve. 함수면 (arch) => value, 정적이면 그대로. */
|
|
206
|
+
function resolveArchField(field, arch) {
|
|
207
|
+
if (field == null) return null;
|
|
208
|
+
return typeof field === 'function' ? field(arch) : field;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 그룹 배열에서 moves/deletes 적용:
|
|
213
|
+
* - move from→to: 모든 그룹에서 from 제거, to 는 transform 그룹용으로 반환.
|
|
214
|
+
* - delete path: 모든 그룹에서 path 제거.
|
|
215
|
+
*
|
|
216
|
+
* @returns {string[]} transform 그룹에 추가될 새 경로
|
|
217
|
+
*/
|
|
218
|
+
function applyMovesAndDeletes(groups, moves, deletes) {
|
|
219
|
+
const added = [];
|
|
220
|
+
for (const { from, to } of moves) {
|
|
221
|
+
const removed = removeFromAllGroups(groups, from);
|
|
222
|
+
// 원본이 어디에도 없었다면 (예: sentry 비활성 상태에서 error.tsx 이동) — 새로
|
|
223
|
+
// 추가하지 않는다. generator.js 의 `if (await fs.pathExists(fromPath))` 시맨틱.
|
|
224
|
+
if (removed) added.push(to);
|
|
225
|
+
}
|
|
226
|
+
for (const p of deletes) {
|
|
227
|
+
removeFromAllGroups(groups, p);
|
|
228
|
+
}
|
|
229
|
+
return added;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function removeFromAllGroups(groups, path) {
|
|
233
|
+
let removed = false;
|
|
234
|
+
for (const g of groups) {
|
|
235
|
+
const idx = g.paths.indexOf(path);
|
|
236
|
+
if (idx !== -1) {
|
|
237
|
+
g.paths.splice(idx, 1);
|
|
238
|
+
removed = true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return removed;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function makeGroup(id, label, paths) {
|
|
245
|
+
return { id, label, paths: paths.slice() };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 후처리: 그룹 간 dedupe (같은 path 가 여러 그룹에 있으면 마지막 그룹이 소유),
|
|
250
|
+
* 그룹 안에서 path 정렬, 빈 그룹 제거, 전체 파일 목록 계산.
|
|
251
|
+
*/
|
|
252
|
+
function finalize(groups) {
|
|
253
|
+
const seen = new Set();
|
|
254
|
+
// 뒤에서 앞으로 — 후순위 그룹(plugins/css/transform) 이 dedup 우선권.
|
|
255
|
+
for (let i = groups.length - 1; i >= 0; i--) {
|
|
256
|
+
const kept = [];
|
|
257
|
+
for (const p of groups[i].paths) {
|
|
258
|
+
if (!seen.has(p)) {
|
|
259
|
+
seen.add(p);
|
|
260
|
+
kept.push(p);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
groups[i] = {
|
|
264
|
+
id: groups[i].id,
|
|
265
|
+
label: groups[i].label,
|
|
266
|
+
paths: kept.sort(),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const cleaned = groups.filter((g) => g.paths.length > 0);
|
|
270
|
+
const files = [];
|
|
271
|
+
for (const g of cleaned) files.push(...g.paths);
|
|
272
|
+
files.sort();
|
|
273
|
+
return { files, groups: cleaned };
|
|
274
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// AUTO-GENERATED by scripts/build-template-manifest.mjs — do not edit by hand.
|
|
2
|
+
// 템플릿 디렉토리(`packages/cli/templates/`) 의 파일 경로 인덱스.
|
|
3
|
+
// describeTemplate() 의 입력. 템플릿 변경 후에는 빌드 스크립트 재실행 필요.
|
|
4
|
+
|
|
5
|
+
export const TEMPLATE_MANIFEST = {
|
|
6
|
+
"flutter-standalone": {
|
|
7
|
+
"base": [
|
|
8
|
+
"README.md",
|
|
9
|
+
"analysis_options.yaml",
|
|
10
|
+
"gitignore",
|
|
11
|
+
"lib/main.dart",
|
|
12
|
+
"lib/sh_ui/foundation/sh_ui_tokens.dart",
|
|
13
|
+
"pubspec.yaml",
|
|
14
|
+
"sh-ui.config.json"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"monorepo": {
|
|
18
|
+
"base": [
|
|
19
|
+
".dockerignore",
|
|
20
|
+
".npmrc",
|
|
21
|
+
".prettierrc",
|
|
22
|
+
"CLAUDE.md",
|
|
23
|
+
"README.md",
|
|
24
|
+
"gitignore",
|
|
25
|
+
"package.json",
|
|
26
|
+
"packages/eslint-config/base.js",
|
|
27
|
+
"packages/eslint-config/flat.js",
|
|
28
|
+
"packages/eslint-config/fsd.js",
|
|
29
|
+
"packages/eslint-config/mes.js",
|
|
30
|
+
"packages/eslint-config/next.js",
|
|
31
|
+
"packages/eslint-config/package.json",
|
|
32
|
+
"packages/eslint-config/react-internal.js",
|
|
33
|
+
"packages/typescript-config/base.json",
|
|
34
|
+
"packages/typescript-config/nextjs.json",
|
|
35
|
+
"packages/typescript-config/package.json",
|
|
36
|
+
"packages/typescript-config/react-library.json",
|
|
37
|
+
"packages/ui/ui-apps/.gitkeep",
|
|
38
|
+
"packages/ui/ui-core/eslint.config.js",
|
|
39
|
+
"packages/ui/ui-core/package.json",
|
|
40
|
+
"packages/ui/ui-core/sh-ui.config.json",
|
|
41
|
+
"packages/ui/ui-core/src/components/.gitkeep",
|
|
42
|
+
"packages/ui/ui-core/src/hooks/.gitkeep",
|
|
43
|
+
"packages/ui/ui-core/src/lib/utils.ts",
|
|
44
|
+
"packages/ui/ui-core/src/styles/.gitkeep",
|
|
45
|
+
"packages/ui/ui-core/tsconfig.json",
|
|
46
|
+
"pnpm-workspace.yaml",
|
|
47
|
+
"tsconfig.json",
|
|
48
|
+
"turbo.json"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"nextjs-app": {
|
|
52
|
+
"base": [
|
|
53
|
+
".env.example",
|
|
54
|
+
"README.md",
|
|
55
|
+
"app/page.tsx",
|
|
56
|
+
"eslint.config.js",
|
|
57
|
+
"next.config.ts",
|
|
58
|
+
"package.json",
|
|
59
|
+
"postcss.config.mjs",
|
|
60
|
+
"vitest.config.ts",
|
|
61
|
+
"vitest.setup.ts"
|
|
62
|
+
],
|
|
63
|
+
"arches": {
|
|
64
|
+
"flat": [
|
|
65
|
+
"app/api/proxy/[...path]/route.ts",
|
|
66
|
+
"app/layout.tsx",
|
|
67
|
+
"components/common/.gitkeep",
|
|
68
|
+
"components/common/FallbackBoundary/index.tsx",
|
|
69
|
+
"components/common/PrefetchBoundary/index.tsx",
|
|
70
|
+
"components/layouts/RootLayout.tsx",
|
|
71
|
+
"components/providers/GlobalProvider/index.tsx",
|
|
72
|
+
"components/providers/index.tsx",
|
|
73
|
+
"components/providers/tanstack/QueryClientProvider.tsx",
|
|
74
|
+
"components/providers/tanstack/TanstackDevtoolsProvider.tsx",
|
|
75
|
+
"components/providers/theme/ThemeProvider.tsx",
|
|
76
|
+
"eslint.config.js",
|
|
77
|
+
"lib/api/.gitkeep",
|
|
78
|
+
"lib/api/apiTypes.ts",
|
|
79
|
+
"lib/api/clientFetch.ts",
|
|
80
|
+
"lib/api/error.ts",
|
|
81
|
+
"lib/api/errorMessages.ts",
|
|
82
|
+
"lib/api/http.ts",
|
|
83
|
+
"lib/api/observability.ts",
|
|
84
|
+
"lib/api/queryClient.ts",
|
|
85
|
+
"lib/api/serverFetch.ts",
|
|
86
|
+
"lib/config/.gitkeep",
|
|
87
|
+
"lib/hooks/.gitkeep",
|
|
88
|
+
"lib/hooks/useAppMutation.ts",
|
|
89
|
+
"lib/test/createTestQueryClient.ts",
|
|
90
|
+
"lib/test/index.ts",
|
|
91
|
+
"lib/test/renderWithProviders.tsx",
|
|
92
|
+
"lib/utils/.gitkeep",
|
|
93
|
+
"lib/utils/formatDate.ts",
|
|
94
|
+
"lib/utils/formatPrice.ts",
|
|
95
|
+
"lib/utils/getQueryClient.ts",
|
|
96
|
+
"tsconfig.json"
|
|
97
|
+
],
|
|
98
|
+
"fsd": [
|
|
99
|
+
"app/api/proxy/[...path]/route.ts",
|
|
100
|
+
"app/layout.tsx",
|
|
101
|
+
"src/app/layouts/RootLayout.tsx",
|
|
102
|
+
"src/app/providers/GlobalProvider/index.tsx",
|
|
103
|
+
"src/app/providers/index.tsx",
|
|
104
|
+
"src/app/providers/tanstack/QueryClientProvider.tsx",
|
|
105
|
+
"src/app/providers/tanstack/TanstackDevtoolsProvider.tsx",
|
|
106
|
+
"src/app/providers/theme/ThemeProvider.tsx",
|
|
107
|
+
"src/entities/.gitkeep",
|
|
108
|
+
"src/features/.gitkeep",
|
|
109
|
+
"src/shared/api/.gitkeep",
|
|
110
|
+
"src/shared/api/apiTypes.ts",
|
|
111
|
+
"src/shared/api/clientFetch.ts",
|
|
112
|
+
"src/shared/api/error.ts",
|
|
113
|
+
"src/shared/api/errorMessages.ts",
|
|
114
|
+
"src/shared/api/http.ts",
|
|
115
|
+
"src/shared/api/observability.ts",
|
|
116
|
+
"src/shared/api/queryClient.ts",
|
|
117
|
+
"src/shared/api/serverFetch.ts",
|
|
118
|
+
"src/shared/config/.gitkeep",
|
|
119
|
+
"src/shared/hooks/.gitkeep",
|
|
120
|
+
"src/shared/hooks/useAppMutation.ts",
|
|
121
|
+
"src/shared/lib/.gitkeep",
|
|
122
|
+
"src/shared/lib/formatDate.ts",
|
|
123
|
+
"src/shared/lib/formatPrice.ts",
|
|
124
|
+
"src/shared/lib/getQueryClient.ts",
|
|
125
|
+
"src/shared/model/.gitkeep",
|
|
126
|
+
"src/shared/test/createTestQueryClient.ts",
|
|
127
|
+
"src/shared/test/index.ts",
|
|
128
|
+
"src/shared/test/renderWithProviders.tsx",
|
|
129
|
+
"src/shared/ui/.gitkeep",
|
|
130
|
+
"src/shared/ui/FallbackBoundary/index.tsx",
|
|
131
|
+
"src/shared/ui/PrefetchBoundary/index.tsx",
|
|
132
|
+
"src/views/.gitkeep",
|
|
133
|
+
"src/widgets/.gitkeep",
|
|
134
|
+
"tsconfig.json"
|
|
135
|
+
],
|
|
136
|
+
"mes": [
|
|
137
|
+
"app/api/proxy/[...path]/route.ts",
|
|
138
|
+
"app/layout.tsx",
|
|
139
|
+
"app/sign-in/page.tsx",
|
|
140
|
+
"eslint.config.js",
|
|
141
|
+
"src/components/common/.gitkeep",
|
|
142
|
+
"src/components/common/FallbackBoundary/index.tsx",
|
|
143
|
+
"src/components/common/PrefetchBoundary/index.tsx",
|
|
144
|
+
"src/components/layouts/RootLayout.tsx",
|
|
145
|
+
"src/components/providers/GlobalProvider/index.tsx",
|
|
146
|
+
"src/components/providers/index.tsx",
|
|
147
|
+
"src/components/providers/tanstack/QueryClientProvider.tsx",
|
|
148
|
+
"src/components/providers/tanstack/TanstackDevtoolsProvider.tsx",
|
|
149
|
+
"src/components/providers/theme/ThemeProvider.tsx",
|
|
150
|
+
"src/hooks/.gitkeep",
|
|
151
|
+
"src/hooks/useAppMutation.ts",
|
|
152
|
+
"src/lib/api/.gitkeep",
|
|
153
|
+
"src/lib/api/apiTypes.ts",
|
|
154
|
+
"src/lib/api/clientFetch.ts",
|
|
155
|
+
"src/lib/api/error.ts",
|
|
156
|
+
"src/lib/api/errorMessages.ts",
|
|
157
|
+
"src/lib/api/http.ts",
|
|
158
|
+
"src/lib/api/observability.ts",
|
|
159
|
+
"src/lib/api/queryClient.ts",
|
|
160
|
+
"src/lib/api/serverFetch.ts",
|
|
161
|
+
"src/lib/config/.gitkeep",
|
|
162
|
+
"src/lib/test/createTestQueryClient.ts",
|
|
163
|
+
"src/lib/test/index.ts",
|
|
164
|
+
"src/lib/test/renderWithProviders.tsx",
|
|
165
|
+
"src/lib/utils/.gitkeep",
|
|
166
|
+
"src/lib/utils/formatDate.ts",
|
|
167
|
+
"src/lib/utils/formatPrice.ts",
|
|
168
|
+
"src/lib/utils/getQueryClient.ts",
|
|
169
|
+
"src/pages/sign-in/api.ts",
|
|
170
|
+
"src/pages/sign-in/components/.gitkeep",
|
|
171
|
+
"src/pages/sign-in/hooks.ts",
|
|
172
|
+
"src/pages/sign-in/index.tsx",
|
|
173
|
+
"src/pages/sign-in/schema.ts",
|
|
174
|
+
"tsconfig.json"
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"nextjs-standalone": {
|
|
179
|
+
"base": [
|
|
180
|
+
".env.example",
|
|
181
|
+
".prettierrc",
|
|
182
|
+
"CLAUDE.md",
|
|
183
|
+
"README.md",
|
|
184
|
+
"app/globals.css",
|
|
185
|
+
"app/page.tsx",
|
|
186
|
+
"eslint.config.js",
|
|
187
|
+
"gitignore",
|
|
188
|
+
"next.config.ts",
|
|
189
|
+
"package.json",
|
|
190
|
+
"postcss.config.mjs",
|
|
191
|
+
"vitest.config.ts",
|
|
192
|
+
"vitest.setup.ts"
|
|
193
|
+
],
|
|
194
|
+
"arches": {
|
|
195
|
+
"flat": [
|
|
196
|
+
"app/api/proxy/[...path]/route.ts",
|
|
197
|
+
"app/globals.css",
|
|
198
|
+
"app/layout.tsx",
|
|
199
|
+
"components/common/.gitkeep",
|
|
200
|
+
"components/common/FallbackBoundary/index.tsx",
|
|
201
|
+
"components/common/PrefetchBoundary/index.tsx",
|
|
202
|
+
"components/layouts/RootLayout.tsx",
|
|
203
|
+
"components/providers/GlobalProvider/index.tsx",
|
|
204
|
+
"components/providers/index.tsx",
|
|
205
|
+
"components/providers/tanstack/QueryClientProvider.tsx",
|
|
206
|
+
"components/providers/tanstack/TanstackDevtoolsProvider.tsx",
|
|
207
|
+
"components/providers/theme/ThemeProvider.tsx",
|
|
208
|
+
"eslint.config.js",
|
|
209
|
+
"lib/api/.gitkeep",
|
|
210
|
+
"lib/api/apiTypes.ts",
|
|
211
|
+
"lib/api/clientFetch.ts",
|
|
212
|
+
"lib/api/error.ts",
|
|
213
|
+
"lib/api/errorMessages.ts",
|
|
214
|
+
"lib/api/http.ts",
|
|
215
|
+
"lib/api/observability.ts",
|
|
216
|
+
"lib/api/queryClient.ts",
|
|
217
|
+
"lib/api/serverFetch.ts",
|
|
218
|
+
"lib/config/.gitkeep",
|
|
219
|
+
"lib/hooks/.gitkeep",
|
|
220
|
+
"lib/hooks/useAppMutation.ts",
|
|
221
|
+
"lib/styles/tokens.css",
|
|
222
|
+
"lib/test/createTestQueryClient.ts",
|
|
223
|
+
"lib/test/index.ts",
|
|
224
|
+
"lib/test/renderWithProviders.tsx",
|
|
225
|
+
"lib/utils/formatDate.ts",
|
|
226
|
+
"lib/utils/formatPrice.ts",
|
|
227
|
+
"lib/utils/getQueryClient.ts",
|
|
228
|
+
"lib/utils/utils.ts",
|
|
229
|
+
"sh-ui.config.json",
|
|
230
|
+
"tsconfig.json"
|
|
231
|
+
],
|
|
232
|
+
"fsd": [
|
|
233
|
+
"app/api/proxy/[...path]/route.ts",
|
|
234
|
+
"app/layout.tsx",
|
|
235
|
+
"sh-ui.config.json",
|
|
236
|
+
"src/app/layouts/RootLayout.tsx",
|
|
237
|
+
"src/app/providers/GlobalProvider/index.tsx",
|
|
238
|
+
"src/app/providers/index.tsx",
|
|
239
|
+
"src/app/providers/tanstack/QueryClientProvider.tsx",
|
|
240
|
+
"src/app/providers/tanstack/TanstackDevtoolsProvider.tsx",
|
|
241
|
+
"src/app/providers/theme/ThemeProvider.tsx",
|
|
242
|
+
"src/entities/.gitkeep",
|
|
243
|
+
"src/features/.gitkeep",
|
|
244
|
+
"src/shared/api/.gitkeep",
|
|
245
|
+
"src/shared/api/apiTypes.ts",
|
|
246
|
+
"src/shared/api/clientFetch.ts",
|
|
247
|
+
"src/shared/api/error.ts",
|
|
248
|
+
"src/shared/api/errorMessages.ts",
|
|
249
|
+
"src/shared/api/http.ts",
|
|
250
|
+
"src/shared/api/observability.ts",
|
|
251
|
+
"src/shared/api/queryClient.ts",
|
|
252
|
+
"src/shared/api/serverFetch.ts",
|
|
253
|
+
"src/shared/config/.gitkeep",
|
|
254
|
+
"src/shared/hooks/.gitkeep",
|
|
255
|
+
"src/shared/hooks/useAppMutation.ts",
|
|
256
|
+
"src/shared/lib/formatDate.ts",
|
|
257
|
+
"src/shared/lib/formatPrice.ts",
|
|
258
|
+
"src/shared/lib/getQueryClient.ts",
|
|
259
|
+
"src/shared/lib/utils.ts",
|
|
260
|
+
"src/shared/model/.gitkeep",
|
|
261
|
+
"src/shared/styles/tokens.css",
|
|
262
|
+
"src/shared/test/createTestQueryClient.ts",
|
|
263
|
+
"src/shared/test/index.ts",
|
|
264
|
+
"src/shared/test/renderWithProviders.tsx",
|
|
265
|
+
"src/shared/ui/.gitkeep",
|
|
266
|
+
"src/shared/ui/FallbackBoundary/index.tsx",
|
|
267
|
+
"src/shared/ui/PrefetchBoundary/index.tsx",
|
|
268
|
+
"src/views/.gitkeep",
|
|
269
|
+
"src/widgets/.gitkeep",
|
|
270
|
+
"tsconfig.json"
|
|
271
|
+
],
|
|
272
|
+
"mes": [
|
|
273
|
+
"app/api/proxy/[...path]/route.ts",
|
|
274
|
+
"app/globals.css",
|
|
275
|
+
"app/layout.tsx",
|
|
276
|
+
"app/sign-in/page.tsx",
|
|
277
|
+
"eslint.config.js",
|
|
278
|
+
"sh-ui.config.json",
|
|
279
|
+
"src/components/common/.gitkeep",
|
|
280
|
+
"src/components/common/FallbackBoundary/index.tsx",
|
|
281
|
+
"src/components/common/PrefetchBoundary/index.tsx",
|
|
282
|
+
"src/components/layouts/RootLayout.tsx",
|
|
283
|
+
"src/components/providers/GlobalProvider/index.tsx",
|
|
284
|
+
"src/components/providers/index.tsx",
|
|
285
|
+
"src/components/providers/tanstack/QueryClientProvider.tsx",
|
|
286
|
+
"src/components/providers/tanstack/TanstackDevtoolsProvider.tsx",
|
|
287
|
+
"src/components/providers/theme/ThemeProvider.tsx",
|
|
288
|
+
"src/hooks/.gitkeep",
|
|
289
|
+
"src/hooks/useAppMutation.ts",
|
|
290
|
+
"src/lib/api/.gitkeep",
|
|
291
|
+
"src/lib/api/apiTypes.ts",
|
|
292
|
+
"src/lib/api/clientFetch.ts",
|
|
293
|
+
"src/lib/api/error.ts",
|
|
294
|
+
"src/lib/api/errorMessages.ts",
|
|
295
|
+
"src/lib/api/http.ts",
|
|
296
|
+
"src/lib/api/observability.ts",
|
|
297
|
+
"src/lib/api/queryClient.ts",
|
|
298
|
+
"src/lib/api/serverFetch.ts",
|
|
299
|
+
"src/lib/config/.gitkeep",
|
|
300
|
+
"src/lib/styles/tokens.css",
|
|
301
|
+
"src/lib/test/createTestQueryClient.ts",
|
|
302
|
+
"src/lib/test/index.ts",
|
|
303
|
+
"src/lib/test/renderWithProviders.tsx",
|
|
304
|
+
"src/lib/utils/formatDate.ts",
|
|
305
|
+
"src/lib/utils/formatPrice.ts",
|
|
306
|
+
"src/lib/utils/getQueryClient.ts",
|
|
307
|
+
"src/lib/utils/utils.ts",
|
|
308
|
+
"src/pages/sign-in/api.ts",
|
|
309
|
+
"src/pages/sign-in/components/.gitkeep",
|
|
310
|
+
"src/pages/sign-in/hooks.ts",
|
|
311
|
+
"src/pages/sign-in/index.tsx",
|
|
312
|
+
"src/pages/sign-in/schema.ts",
|
|
313
|
+
"tsconfig.json"
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
"ui-app-template": {
|
|
318
|
+
"base": [
|
|
319
|
+
"eslint.config.js",
|
|
320
|
+
"package.json",
|
|
321
|
+
"postcss.config.mjs",
|
|
322
|
+
"sh-ui.config.json",
|
|
323
|
+
"src/styles/globals.css",
|
|
324
|
+
"src/styles/tokens.css",
|
|
325
|
+
"tsconfig.json"
|
|
326
|
+
]
|
|
327
|
+
}
|
|
328
|
+
};
|
|
@@ -19,6 +19,11 @@ const OPTIONAL_TOKEN_KEYS = [
|
|
|
19
19
|
'info', 'info-foreground',
|
|
20
20
|
'danger-hover',
|
|
21
21
|
'ring',
|
|
22
|
+
// v0.80.1+ — sidebar cascade 색. 5개 모두 light/dark 양쪽에 정의되어야 emit.
|
|
23
|
+
// sidebar 컴포넌트의 styles.css 가 .sh-ui-sidebar-wrapper 에 var(--background-subtle) 등으로
|
|
24
|
+
// fallback 을 두지만, Tailwind @theme inline 의 --color-sidebar-* 가 :root 에서 해석되도록
|
|
25
|
+
// tokens.css 에도 끌어올린다.
|
|
26
|
+
'sidebar-bg', 'sidebar-fg', 'sidebar-border', 'sidebar-accent', 'sidebar-accent-fg',
|
|
22
27
|
];
|
|
23
28
|
|
|
24
29
|
/**
|
|
@@ -24,6 +24,17 @@ const NEUTRAL_LIGHT = {
|
|
|
24
24
|
'danger-foreground': '#FFFFFF',
|
|
25
25
|
'danger-hover': '#B91C1C',
|
|
26
26
|
'ring': '#A3A3A3',
|
|
27
|
+
'success': '#16A34A',
|
|
28
|
+
'success-foreground': '#FFFFFF',
|
|
29
|
+
'warning': '#D97706',
|
|
30
|
+
'warning-foreground': '#FFFFFF',
|
|
31
|
+
'info': '#2563EB',
|
|
32
|
+
'info-foreground': '#FFFFFF',
|
|
33
|
+
'sidebar-bg': '#FAFAFA',
|
|
34
|
+
'sidebar-fg': '#0A0A0A',
|
|
35
|
+
'sidebar-border': '#E5E5E5',
|
|
36
|
+
'sidebar-accent': '#F5F5F5',
|
|
37
|
+
'sidebar-accent-fg': '#0A0A0A',
|
|
27
38
|
};
|
|
28
39
|
|
|
29
40
|
const NEUTRAL_DARK = {
|
|
@@ -44,6 +55,17 @@ const NEUTRAL_DARK = {
|
|
|
44
55
|
'danger-foreground': '#FFFFFF',
|
|
45
56
|
'danger-hover': '#EF4444',
|
|
46
57
|
'ring': '#737373',
|
|
58
|
+
'success': '#22C55E',
|
|
59
|
+
'success-foreground': '#052E16',
|
|
60
|
+
'warning': '#F59E0B',
|
|
61
|
+
'warning-foreground': '#451A03',
|
|
62
|
+
'info': '#3B82F6',
|
|
63
|
+
'info-foreground': '#172554',
|
|
64
|
+
'sidebar-bg': '#171717',
|
|
65
|
+
'sidebar-fg': '#FAFAFA',
|
|
66
|
+
'sidebar-border': '#262626',
|
|
67
|
+
'sidebar-accent': '#262626',
|
|
68
|
+
'sidebar-accent-fg': '#FAFAFA',
|
|
47
69
|
};
|
|
48
70
|
|
|
49
71
|
export const THEME_PRESETS = {
|
|
@@ -73,6 +95,17 @@ export const THEME_PRESETS = {
|
|
|
73
95
|
'danger-foreground': '#FFFFFF',
|
|
74
96
|
'danger-hover': '#B91C1C',
|
|
75
97
|
'ring': '#94A3B8',
|
|
98
|
+
'success': '#16A34A',
|
|
99
|
+
'success-foreground': '#FFFFFF',
|
|
100
|
+
'warning': '#D97706',
|
|
101
|
+
'warning-foreground': '#FFFFFF',
|
|
102
|
+
'info': '#2563EB',
|
|
103
|
+
'info-foreground': '#FFFFFF',
|
|
104
|
+
'sidebar-bg': '#F8FAFC',
|
|
105
|
+
'sidebar-fg': '#0F172A',
|
|
106
|
+
'sidebar-border': '#E2E8F0',
|
|
107
|
+
'sidebar-accent': '#F1F5F9',
|
|
108
|
+
'sidebar-accent-fg': '#0F172A',
|
|
76
109
|
},
|
|
77
110
|
dark: {
|
|
78
111
|
'background': '#0F172A',
|
|
@@ -92,6 +125,17 @@ export const THEME_PRESETS = {
|
|
|
92
125
|
'danger-foreground': '#450A0A',
|
|
93
126
|
'danger-hover': '#FCA5A5',
|
|
94
127
|
'ring': '#64748B',
|
|
128
|
+
'success': '#22C55E',
|
|
129
|
+
'success-foreground': '#052E16',
|
|
130
|
+
'warning': '#F59E0B',
|
|
131
|
+
'warning-foreground': '#451A03',
|
|
132
|
+
'info': '#60A5FA',
|
|
133
|
+
'info-foreground': '#172554',
|
|
134
|
+
'sidebar-bg': '#1E293B',
|
|
135
|
+
'sidebar-fg': '#F1F5F9',
|
|
136
|
+
'sidebar-border': '#334155',
|
|
137
|
+
'sidebar-accent': '#334155',
|
|
138
|
+
'sidebar-accent-fg': '#F1F5F9',
|
|
95
139
|
},
|
|
96
140
|
radius: 0.375,
|
|
97
141
|
// 정보 밀도 ↑ — 본문 14px 부터, 컨트롤 36px (대시보드/관리자 인상)
|
package/src/mcp.mjs
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
} from "./constants.js";
|
|
50
50
|
import { allPlugins } from "./create/plugins/index.js";
|
|
51
51
|
import { allArchitectures } from "./create/architectures/index.js";
|
|
52
|
+
import { describeTemplate } from "./create/describeTemplate.js";
|
|
52
53
|
import { THEME_PRESET_NAMES } from "./create/theme/presets.js";
|
|
53
54
|
import { decodeTheme } from "./create/theme/decode.js";
|
|
54
55
|
import { encodeTheme } from "./create/theme/encode.js";
|
|
@@ -805,6 +806,51 @@ export async function startMcpServer() {
|
|
|
805
806
|
},
|
|
806
807
|
);
|
|
807
808
|
|
|
809
|
+
// 템플릿 트리 사전 미리보기 — 실제 생성 없이 옵션 조합으로 어떤 파일이 emit 되는지.
|
|
810
|
+
// apps/docs 의 CreateProjectDialog 와 같은 출력 (sh-ui-cli/api 의 describeTemplate).
|
|
811
|
+
server.registerTool(
|
|
812
|
+
"sh_ui_describe_template",
|
|
813
|
+
{
|
|
814
|
+
description:
|
|
815
|
+
"sh-ui create 호출 시 어떤 파일이 생기는지 사전 계산 — 실제 fs 변경 없음. " +
|
|
816
|
+
"사용자가 '미리 보고 싶다' / '어떤 파일 생기는지' / '플러그인 켜면 뭐가 추가돼?' 류 질문을 하면 이 툴 사용 (sh_ui_create_project 의 dry-run 대용). " +
|
|
817
|
+
"베이스 템플릿 + arch 오버레이 + plugin.files + cssFramework 분기 + plugin.transforms (이동/삭제) 까지 정확히 반영. " +
|
|
818
|
+
"반환: { files: 정렬된 전체 경로, groups: 출처별 분류 (base/arch/plugin-*/css/transform) }.",
|
|
819
|
+
inputSchema: {
|
|
820
|
+
platform: z.enum(CREATE_PLATFORMS)
|
|
821
|
+
.describe("타겟 플랫폼"),
|
|
822
|
+
structure: z.enum(CREATE_STRUCTURES).optional()
|
|
823
|
+
.describe("Next.js 구조. platform=next 일 때 의미. 기본 standalone"),
|
|
824
|
+
arch: z.enum(ARCH_NAMES).optional()
|
|
825
|
+
.describe("아키텍처. platform=next 일 때 의미. 기본 fsd"),
|
|
826
|
+
plugins: z.array(z.enum(PLUGIN_NAMES)).optional()
|
|
827
|
+
.describe(`Next.js 플러그인 배열 (${PLUGIN_NAMES.join(', ')}). 미지정 빈 배열`),
|
|
828
|
+
cssFramework: z.enum(CSS_FRAMEWORKS).optional()
|
|
829
|
+
.describe("CSS 프레임워크. 기본 plain. css-modules 면 page.module.css 등 추가"),
|
|
830
|
+
appName: z.string().optional()
|
|
831
|
+
.describe("monorepo 첫 앱 이름. 기본 web"),
|
|
832
|
+
},
|
|
833
|
+
},
|
|
834
|
+
async (input) => {
|
|
835
|
+
try {
|
|
836
|
+
const result = describeTemplate({
|
|
837
|
+
platform: input.platform,
|
|
838
|
+
structure: input.structure,
|
|
839
|
+
arch: input.arch,
|
|
840
|
+
plugins: input.plugins,
|
|
841
|
+
cssFramework: input.cssFramework,
|
|
842
|
+
appName: input.appName,
|
|
843
|
+
});
|
|
844
|
+
return jsonResult(result);
|
|
845
|
+
} catch (e) {
|
|
846
|
+
return {
|
|
847
|
+
isError: true,
|
|
848
|
+
content: [{ type: "text", text: e.message }],
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
},
|
|
852
|
+
);
|
|
853
|
+
|
|
808
854
|
// 변경 내역 조회 — 보너스: 사용자가 "최근 변경 알려줘" 류 요청 시
|
|
809
855
|
server.registerTool(
|
|
810
856
|
"sh_ui_get_changelog",
|
|
File without changes
|
|
@@ -20,6 +20,17 @@
|
|
|
20
20
|
--danger: #DC2626;
|
|
21
21
|
--danger-hover: color-mix(in srgb, var(--danger) 90%, black);
|
|
22
22
|
--danger-foreground: #FFFFFF;
|
|
23
|
+
--success: #16A34A;
|
|
24
|
+
--success-foreground: #FFFFFF;
|
|
25
|
+
--warning: #D97706;
|
|
26
|
+
--warning-foreground: #FFFFFF;
|
|
27
|
+
--info: #2563EB;
|
|
28
|
+
--info-foreground: #FFFFFF;
|
|
29
|
+
--sidebar-bg: #FAFAFA;
|
|
30
|
+
--sidebar-fg: #0A0A0A;
|
|
31
|
+
--sidebar-border: #E5E5E5;
|
|
32
|
+
--sidebar-accent: #F5F5F5;
|
|
33
|
+
--sidebar-accent-fg: #0A0A0A;
|
|
23
34
|
}
|
|
24
35
|
@media (prefers-color-scheme: dark) {
|
|
25
36
|
:root:not(.light):not(.dark) {
|
|
@@ -37,8 +48,19 @@
|
|
|
37
48
|
--primary-foreground: #171717;
|
|
38
49
|
--primary-hover: #E5E5E5;
|
|
39
50
|
--danger: #DC2626;
|
|
40
|
-
|
|
51
|
+
--danger-hover: color-mix(in srgb, var(--danger) 90%, black);
|
|
41
52
|
--danger-foreground: #FFFFFF;
|
|
53
|
+
--success: #22C55E;
|
|
54
|
+
--success-foreground: #052E16;
|
|
55
|
+
--warning: #F59E0B;
|
|
56
|
+
--warning-foreground: #451A03;
|
|
57
|
+
--info: #3B82F6;
|
|
58
|
+
--info-foreground: #172554;
|
|
59
|
+
--sidebar-bg: #171717;
|
|
60
|
+
--sidebar-fg: #FAFAFA;
|
|
61
|
+
--sidebar-border: #262626;
|
|
62
|
+
--sidebar-accent: #262626;
|
|
63
|
+
--sidebar-accent-fg: #FAFAFA;
|
|
42
64
|
}
|
|
43
65
|
}
|
|
44
66
|
.dark {
|
|
@@ -58,6 +80,17 @@
|
|
|
58
80
|
--danger: #DC2626;
|
|
59
81
|
--danger-hover: color-mix(in srgb, var(--danger) 90%, black);
|
|
60
82
|
--danger-foreground: #FFFFFF;
|
|
83
|
+
--success: #22C55E;
|
|
84
|
+
--success-foreground: #052E16;
|
|
85
|
+
--warning: #F59E0B;
|
|
86
|
+
--warning-foreground: #451A03;
|
|
87
|
+
--info: #3B82F6;
|
|
88
|
+
--info-foreground: #172554;
|
|
89
|
+
--sidebar-bg: #171717;
|
|
90
|
+
--sidebar-fg: #FAFAFA;
|
|
91
|
+
--sidebar-border: #262626;
|
|
92
|
+
--sidebar-accent: #262626;
|
|
93
|
+
--sidebar-accent-fg: #FAFAFA;
|
|
61
94
|
}
|
|
62
95
|
/* sh-ui:theme-colors-end */
|
|
63
96
|
|
|
@@ -20,6 +20,17 @@
|
|
|
20
20
|
--danger: #DC2626;
|
|
21
21
|
--danger-hover: color-mix(in srgb, var(--danger) 90%, black);
|
|
22
22
|
--danger-foreground: #FFFFFF;
|
|
23
|
+
--success: #16A34A;
|
|
24
|
+
--success-foreground: #FFFFFF;
|
|
25
|
+
--warning: #D97706;
|
|
26
|
+
--warning-foreground: #FFFFFF;
|
|
27
|
+
--info: #2563EB;
|
|
28
|
+
--info-foreground: #FFFFFF;
|
|
29
|
+
--sidebar-bg: #FAFAFA;
|
|
30
|
+
--sidebar-fg: #0A0A0A;
|
|
31
|
+
--sidebar-border: #E5E5E5;
|
|
32
|
+
--sidebar-accent: #F5F5F5;
|
|
33
|
+
--sidebar-accent-fg: #0A0A0A;
|
|
23
34
|
}
|
|
24
35
|
@media (prefers-color-scheme: dark) {
|
|
25
36
|
:root:not(.light):not(.dark) {
|
|
@@ -37,8 +48,19 @@
|
|
|
37
48
|
--primary-foreground: #171717;
|
|
38
49
|
--primary-hover: #E5E5E5;
|
|
39
50
|
--danger: #DC2626;
|
|
40
|
-
|
|
51
|
+
--danger-hover: color-mix(in srgb, var(--danger) 90%, black);
|
|
41
52
|
--danger-foreground: #FFFFFF;
|
|
53
|
+
--success: #22C55E;
|
|
54
|
+
--success-foreground: #052E16;
|
|
55
|
+
--warning: #F59E0B;
|
|
56
|
+
--warning-foreground: #451A03;
|
|
57
|
+
--info: #3B82F6;
|
|
58
|
+
--info-foreground: #172554;
|
|
59
|
+
--sidebar-bg: #171717;
|
|
60
|
+
--sidebar-fg: #FAFAFA;
|
|
61
|
+
--sidebar-border: #262626;
|
|
62
|
+
--sidebar-accent: #262626;
|
|
63
|
+
--sidebar-accent-fg: #FAFAFA;
|
|
42
64
|
}
|
|
43
65
|
}
|
|
44
66
|
.dark {
|
|
@@ -58,6 +80,17 @@
|
|
|
58
80
|
--danger: #DC2626;
|
|
59
81
|
--danger-hover: color-mix(in srgb, var(--danger) 90%, black);
|
|
60
82
|
--danger-foreground: #FFFFFF;
|
|
83
|
+
--success: #22C55E;
|
|
84
|
+
--success-foreground: #052E16;
|
|
85
|
+
--warning: #F59E0B;
|
|
86
|
+
--warning-foreground: #451A03;
|
|
87
|
+
--info: #3B82F6;
|
|
88
|
+
--info-foreground: #172554;
|
|
89
|
+
--sidebar-bg: #171717;
|
|
90
|
+
--sidebar-fg: #FAFAFA;
|
|
91
|
+
--sidebar-border: #262626;
|
|
92
|
+
--sidebar-accent: #262626;
|
|
93
|
+
--sidebar-accent-fg: #FAFAFA;
|
|
61
94
|
}
|
|
62
95
|
/* sh-ui:theme-colors-end */
|
|
63
96
|
|
|
@@ -20,6 +20,17 @@
|
|
|
20
20
|
--danger: #DC2626;
|
|
21
21
|
--danger-hover: color-mix(in srgb, var(--danger) 90%, black);
|
|
22
22
|
--danger-foreground: #FFFFFF;
|
|
23
|
+
--success: #16A34A;
|
|
24
|
+
--success-foreground: #FFFFFF;
|
|
25
|
+
--warning: #D97706;
|
|
26
|
+
--warning-foreground: #FFFFFF;
|
|
27
|
+
--info: #2563EB;
|
|
28
|
+
--info-foreground: #FFFFFF;
|
|
29
|
+
--sidebar-bg: #FAFAFA;
|
|
30
|
+
--sidebar-fg: #0A0A0A;
|
|
31
|
+
--sidebar-border: #E5E5E5;
|
|
32
|
+
--sidebar-accent: #F5F5F5;
|
|
33
|
+
--sidebar-accent-fg: #0A0A0A;
|
|
23
34
|
}
|
|
24
35
|
@media (prefers-color-scheme: dark) {
|
|
25
36
|
:root:not(.light):not(.dark) {
|
|
@@ -37,8 +48,19 @@
|
|
|
37
48
|
--primary-foreground: #171717;
|
|
38
49
|
--primary-hover: #E5E5E5;
|
|
39
50
|
--danger: #DC2626;
|
|
40
|
-
|
|
51
|
+
--danger-hover: color-mix(in srgb, var(--danger) 90%, black);
|
|
41
52
|
--danger-foreground: #FFFFFF;
|
|
53
|
+
--success: #22C55E;
|
|
54
|
+
--success-foreground: #052E16;
|
|
55
|
+
--warning: #F59E0B;
|
|
56
|
+
--warning-foreground: #451A03;
|
|
57
|
+
--info: #3B82F6;
|
|
58
|
+
--info-foreground: #172554;
|
|
59
|
+
--sidebar-bg: #171717;
|
|
60
|
+
--sidebar-fg: #FAFAFA;
|
|
61
|
+
--sidebar-border: #262626;
|
|
62
|
+
--sidebar-accent: #262626;
|
|
63
|
+
--sidebar-accent-fg: #FAFAFA;
|
|
42
64
|
}
|
|
43
65
|
}
|
|
44
66
|
.dark {
|
|
@@ -58,6 +80,17 @@
|
|
|
58
80
|
--danger: #DC2626;
|
|
59
81
|
--danger-hover: color-mix(in srgb, var(--danger) 90%, black);
|
|
60
82
|
--danger-foreground: #FFFFFF;
|
|
83
|
+
--success: #22C55E;
|
|
84
|
+
--success-foreground: #052E16;
|
|
85
|
+
--warning: #F59E0B;
|
|
86
|
+
--warning-foreground: #451A03;
|
|
87
|
+
--info: #3B82F6;
|
|
88
|
+
--info-foreground: #172554;
|
|
89
|
+
--sidebar-bg: #171717;
|
|
90
|
+
--sidebar-fg: #FAFAFA;
|
|
91
|
+
--sidebar-border: #262626;
|
|
92
|
+
--sidebar-accent: #262626;
|
|
93
|
+
--sidebar-accent-fg: #FAFAFA;
|
|
61
94
|
}
|
|
62
95
|
/* sh-ui:theme-colors-end */
|
|
63
96
|
|