sh-ui-cli 0.97.0 → 0.98.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/README.md +1 -1
- package/data/changelog/versions.json +25 -0
- package/package.json +1 -1
- package/src/add.mjs +39 -14
- package/src/api.d.ts +0 -2
- package/src/constants.js +0 -5
- package/src/create/cli-args.js +1 -5
- package/src/create/describeTemplate.js +2 -31
- package/src/create/generator.js +9 -665
- package/src/create/index.mjs +2 -5
- package/src/create/plugins/index.js +1 -3
- package/src/create/plugins/nextIntl.js +0 -4
- package/src/create/plugins/pluginSchema.js +1 -1
- package/src/css-bundle.mjs +60 -0
- package/src/mcp.mjs +0 -34
- package/src/remove.mjs +10 -1
- package/src/create/plugins/authJwt.js +0 -420
- package/src/create/plugins/sentry.js +0 -467
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ npx sh-ui-cli create
|
|
|
22
22
|
|
|
23
23
|
# 비대화형 (에이전트 / CI)
|
|
24
24
|
npx sh-ui-cli create my-app --platform next --structure standalone --yes
|
|
25
|
-
npx sh-ui-cli create my-app --platform next --structure monorepo --plugins
|
|
25
|
+
npx sh-ui-cli create my-app --platform next --structure monorepo --plugins next-intl --yes
|
|
26
26
|
npx sh-ui-cli create my-app --platform flutter --yes
|
|
27
27
|
```
|
|
28
28
|
|
|
@@ -2,6 +2,31 @@
|
|
|
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.98.1",
|
|
7
|
+
"date": "2026-05-19",
|
|
8
|
+
"title": "크로스컴포넌트 상대 import 를 aliases.components 로 재작성 — NodeNext 소비자 호환",
|
|
9
|
+
"type": "patch",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**크로스컴포넌트 import 가 이제 `aliases.components` 경유로 emit** — `sidebar`(→popover), `calendar`(→select), `date-picker`(→calendar), `markdown-editor`(→code-editor), `code-tabs`(→tabs/code-panel), `form-rhf`/`form-tanstack`/`form-yup`(→form) 등 형제 컴포넌트를 import 하던 모든 컴포넌트가, registry 의 `import … from \"../popover\"` 상대경로 대신 사용자 `sh-ui.config.json` 의 `aliases.components`(예: `@workspace/ui-core/components/popover`)로 재작성된다. `@SH_UI_UTILS@` → `aliases.utils` 치환과 동일한 add-time 변환.",
|
|
12
|
+
"**`moduleResolution: \"NodeNext\"`/`\"Node16\"` 소비자 깨짐 수정** — 상대경로/디렉터리 import 는 NodeNext 가 거부하고, 과거 일부 버전이 emit 한 `\"../popover/index.tsx\"` 의 명시적 `.tsx` 확장자는 TS5097 을 유발해 소비자가 `allowImportingTsExtensions`+`noEmit`(패키지 전체 declaration emit 비활성화) 같은 침습적 tsconfig 변경을 강요당했다. 이제 `\"Bundler\"`·`\"NodeNext\"` 양쪽에서 소비자 tsconfig 변경 없이 resolve 된다.",
|
|
13
|
+
"**add/remove 대칭 + registry 불변** — `remove` 의 unmodified 판정도 같은 재작성을 replay 해 형제-import 컴포넌트 삭제 시 '사용자 수정됨' false positive 가 나지 않는다. registry 소스·docs 듀얼카피·generated 산출물은 변경 없음(CLI 변환만). `aliases.components` 미설정 시 `@SH_UI_UTILS@` 와 동일한 톤의 친절 에러로 사전 안내. 회귀 단위 테스트 추가(전 스위트 407 통과, dual-copy drift 47 components 0)."
|
|
14
|
+
],
|
|
15
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.98.1"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"version": "0.98.0",
|
|
19
|
+
"date": "2026-05-16",
|
|
20
|
+
"title": "auth-jwt · sentry 플러그인 제거 — 스캐폴더에서 두 통합 완전 삭제 (breaking)",
|
|
21
|
+
"type": "minor",
|
|
22
|
+
"highlights": [
|
|
23
|
+
"**`sentry` 플러그인 + `--observability` 옵션 제거 (breaking)** — Next 의 `--plugins sentry` 와 vite 전용 `--observability sentry` 를 둘 다 삭제. `sh_ui_create_project` · `sh_ui_add_app` · `sh_ui_describe_template` 의 `observability` 인자, `DescribeTemplateOptions.observability` 타입, `emitSentry()` 도 함께 제거. 이제 `--observability` 는 '알 수 없는 플래그', `--plugins sentry` 는 '알 수 없는 플러그인' 으로 거부된다.",
|
|
24
|
+
"**`auth-jwt` 플러그인 제거 (breaking)** — `--plugins auth-jwt` 및 auth-jwt+next-intl proxy.ts 병합 로직 삭제. `next-intl` 단독 scaffold 는 자체 proxy.ts 를 그대로 emit (회귀 가드 smoke 유지).",
|
|
25
|
+
"**유지: i18n / next-intl** — `--plugins next-intl` 과 vite `--i18n react-i18next` / `--locales` 는 그대로. 스캐폴더 플러그인은 이제 `next-intl` 단일. Sentry/JWT 는 프로젝트마다 설정이 갈려 baked-in scaffold 가치가 낮고, 특히 Sentry 의 Next 통합은 버전 churn 유지보수 부채라 의도적으로 축소.",
|
|
26
|
+
"**마이그레이션** — Sentry 가 필요하면 생성 후 공식 `@sentry/nextjs` 또는 `@sentry/react` 를 직접 추가. 인증은 프로젝트 백엔드 명세에 맞춰 직접 구성. docs `/plugins/sentry`·`/plugins/auth-jwt` 페이지 및 `/create` UI 토글 제거, smoke 시나리오 정리."
|
|
27
|
+
],
|
|
28
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.98.0"
|
|
29
|
+
},
|
|
5
30
|
{
|
|
6
31
|
"version": "0.97.0",
|
|
7
32
|
"date": "2026-05-16",
|
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/api.d.ts
CHANGED
|
@@ -143,8 +143,6 @@ export interface DescribeTemplateOptions {
|
|
|
143
143
|
i18n?: 'none' | 'react-i18next';
|
|
144
144
|
/** i18n 활성화 시 생성할 locale 코드 (comma-separated). 기본 'ko,en' */
|
|
145
145
|
locales?: string;
|
|
146
|
-
/** vite 전용 — Sentry observability opt-in. v0.93.0+ */
|
|
147
|
-
observability?: 'none' | 'sentry';
|
|
148
146
|
}
|
|
149
147
|
|
|
150
148
|
export interface DescribeTemplateGroup {
|
package/src/constants.js
CHANGED
|
@@ -60,8 +60,3 @@ export const INIT_DEFAULTS = {
|
|
|
60
60
|
export const I18N_LIBRARIES = ['none', 'react-i18next'];
|
|
61
61
|
export const I18N_DEFAULT = 'none';
|
|
62
62
|
export const I18N_DEFAULT_LOCALES = 'ko,en';
|
|
63
|
-
|
|
64
|
-
// ─── observability (vite preset 전용 — v0.93.0+) ───
|
|
65
|
-
// 'none' 디폴트 — opt-in 으로 @sentry/react + @sentry/vite-plugin 설치 + 셋업.
|
|
66
|
-
// GlitchTip self-hosted 도 같은 SDK — DSN 만 변경. 향후 bugsnag 등 추가 시 enum 확장.
|
|
67
|
-
export const OBSERVABILITY_PROVIDERS = ['none', 'sentry'];
|
package/src/create/cli-args.js
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
CSS_FRAMEWORKS_SUPPORTED,
|
|
5
5
|
CSS_FRAMEWORKS_PLANNED,
|
|
6
6
|
I18N_LIBRARIES,
|
|
7
|
-
OBSERVABILITY_PROVIDERS,
|
|
8
7
|
} from '../constants.js';
|
|
9
8
|
import { allPlugins } from './plugins/index.js';
|
|
10
9
|
import { allArchitectures } from './architectures/index.js';
|
|
@@ -14,7 +13,7 @@ const VALID_STRUCTURES = CREATE_STRUCTURES;
|
|
|
14
13
|
const VALID_PLUGINS = allPlugins.map((p) => p.name);
|
|
15
14
|
const VALID_ARCHES = allArchitectures.map((a) => a.name);
|
|
16
15
|
|
|
17
|
-
const VALUE_FLAGS = ['platform', 'structure', 'plugins', 'theme', 'app', 'css', 'arch', 'port', 'i18n', 'locales'
|
|
16
|
+
const VALUE_FLAGS = ['platform', 'structure', 'plugins', 'theme', 'app', 'css', 'arch', 'port', 'i18n', 'locales'];
|
|
18
17
|
const BOOL_FLAGS = ['yes', 'help', 'dry-run'];
|
|
19
18
|
|
|
20
19
|
const SUBCOMMANDS = ['add-app', 'add-component'];
|
|
@@ -80,9 +79,6 @@ export const parseArgs = (argv) => {
|
|
|
80
79
|
if (name === 'i18n' && !I18N_LIBRARIES.includes(value)) {
|
|
81
80
|
throw new Error(`--i18n 은 ${I18N_LIBRARIES.join('/')} 중 하나여야 함 (받은 값: ${value})`);
|
|
82
81
|
}
|
|
83
|
-
if (name === 'observability' && !OBSERVABILITY_PROVIDERS.includes(value)) {
|
|
84
|
-
throw new Error(`--observability 는 ${OBSERVABILITY_PROVIDERS.join('/')} 중 하나여야 함 (받은 값: ${value})`);
|
|
85
|
-
}
|
|
86
82
|
if (name === 'css' && !CSS_FRAMEWORKS_SUPPORTED.includes(value)) {
|
|
87
83
|
// planned 값은 '곧 옵니다' 신호로 분기 — 사용자 의도가 더 명확히 전달.
|
|
88
84
|
if (CSS_FRAMEWORKS_PLANNED.includes(value)) {
|
|
@@ -31,13 +31,12 @@ import { CSS_FRAMEWORK_DEFAULT } from '../constants.js';
|
|
|
31
31
|
* @property {'next'|'flutter'|'vite'} [platform]
|
|
32
32
|
* @property {'standalone'|'monorepo'} [structure] next/vite 일 때만 의미
|
|
33
33
|
* @property {string} [arch] next 일 때 'fsd'|'flat'|'mes'
|
|
34
|
-
* @property {string[]} [plugins] ['
|
|
34
|
+
* @property {string[]} [plugins] ['next-intl']
|
|
35
35
|
* @property {'tailwind'|'plain'|'css-modules'} [cssFramework]
|
|
36
36
|
* @property {string} [projectName]
|
|
37
37
|
* @property {string} [appName] monorepo 첫 앱 이름. 기본 'web'
|
|
38
38
|
* @property {'none'|'react-i18next'} [i18n] vite 전용 — react-i18next 셋업
|
|
39
39
|
* @property {string} [locales] i18n 활성화 시 생성할 locale 코드 (comma-separated, default 'ko,en')
|
|
40
|
-
* @property {'none'|'sentry'} [observability] vite 전용 — Sentry 셋업 (v0.93.0+)
|
|
41
40
|
*/
|
|
42
41
|
|
|
43
42
|
/**
|
|
@@ -67,7 +66,6 @@ export function describeTemplate(opts = {}) {
|
|
|
67
66
|
appName: rawAppName = 'web',
|
|
68
67
|
i18n = 'none',
|
|
69
68
|
locales = 'ko,en',
|
|
70
|
-
observability = 'none',
|
|
71
69
|
} = opts;
|
|
72
70
|
|
|
73
71
|
if (platform === 'flutter') {
|
|
@@ -108,17 +106,6 @@ export function describeTemplate(opts = {}) {
|
|
|
108
106
|
];
|
|
109
107
|
groups.push(makeGroup('i18n', `i18n (${i18n})`, i18nFiles));
|
|
110
108
|
}
|
|
111
|
-
if (observability === 'sentry') {
|
|
112
|
-
const isFsd = safeArchName === 'fsd';
|
|
113
|
-
const obsBase = isFsd ? 'src/shared/observability' : 'src/lib/observability';
|
|
114
|
-
const providersBase = isFsd ? 'src/app/providers' : 'src/components/providers';
|
|
115
|
-
groups.push(makeGroup('sentry', `Sentry observability`, [
|
|
116
|
-
`${obsBase}/sentry.ts`,
|
|
117
|
-
`${obsBase}/index.ts`,
|
|
118
|
-
`${providersBase}/SentryProvider.tsx`,
|
|
119
|
-
'.env.example',
|
|
120
|
-
]));
|
|
121
|
-
}
|
|
122
109
|
return finalize(groups);
|
|
123
110
|
}
|
|
124
111
|
|
|
@@ -174,18 +161,6 @@ export function describeTemplate(opts = {}) {
|
|
|
174
161
|
groups.push(makeGroup('i18n', `i18n (${i18n}, apps/${appName}/)`, i18nFiles));
|
|
175
162
|
}
|
|
176
163
|
|
|
177
|
-
if (observability === 'sentry') {
|
|
178
|
-
const isFsd = safeArchName === 'fsd';
|
|
179
|
-
const obsBase = isFsd ? 'src/shared/observability' : 'src/lib/observability';
|
|
180
|
-
const providersBase = isFsd ? 'src/app/providers' : 'src/components/providers';
|
|
181
|
-
groups.push(makeGroup('sentry', `Sentry observability (apps/${appName}/)`, [
|
|
182
|
-
`apps/${appName}/${obsBase}/sentry.ts`,
|
|
183
|
-
`apps/${appName}/${obsBase}/index.ts`,
|
|
184
|
-
`apps/${appName}/${providersBase}/SentryProvider.tsx`,
|
|
185
|
-
`apps/${appName}/.env.example`,
|
|
186
|
-
]));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
164
|
return finalize(groups);
|
|
190
165
|
}
|
|
191
166
|
|
|
@@ -280,13 +255,9 @@ function buildNextGroups({ prefix, templateKey, arch, plugins, cssFramework }) {
|
|
|
280
255
|
// generator.js applyCssFrameworkVariant 와 동일한 분기 — intl 활성 시 [locale]/, 아니면 app/.
|
|
281
256
|
if (cssFramework === 'css-modules') {
|
|
282
257
|
const intlActive = plugins.some((p) => p.name === 'next-intl');
|
|
283
|
-
const sentryActive = plugins.some((p) => p.name === 'sentry');
|
|
284
258
|
const cssPaths = [];
|
|
285
259
|
const pageDir = intlActive ? 'app/[locale]' : 'app';
|
|
286
260
|
cssPaths.push(`${prefix}${pageDir}/page.module.css`);
|
|
287
|
-
if (sentryActive) {
|
|
288
|
-
cssPaths.push(`${prefix}${pageDir}/error.module.css`);
|
|
289
|
-
}
|
|
290
261
|
groups.push(makeGroup(
|
|
291
262
|
'css',
|
|
292
263
|
'CSS: css-modules 변종',
|
|
@@ -337,7 +308,7 @@ function applyMovesAndDeletes(groups, moves, deletes) {
|
|
|
337
308
|
const added = [];
|
|
338
309
|
for (const { from, to } of moves) {
|
|
339
310
|
const removed = removeFromAllGroups(groups, from);
|
|
340
|
-
// 원본이 어디에도 없었다면 (
|
|
311
|
+
// 원본이 어디에도 없었다면 (move 소스가 현재 file-plan 에 없음) — 새로
|
|
341
312
|
// 추가하지 않는다. generator.js 의 `if (await fs.pathExists(fromPath))` 시맨틱.
|
|
342
313
|
if (removed) added.push(to);
|
|
343
314
|
}
|