sh-ui-cli 0.90.0 → 0.91.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.
@@ -2,6 +2,19 @@
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.91.0",
7
+ "date": "2026-05-14",
8
+ "title": "add-app 가 platform (next/vite) + tauri 인식 — vite monorepo 에 새 앱 정상 추가",
9
+ "type": "minor",
10
+ "highlights": [
11
+ "**`sh-ui-cli add-app` + MCP `sh_ui_add_app` 가 `platform: vite | next` 인식** — 이전엔 Next.js 하드코딩이라 vite monorepo (v0.87.0+) 에 `add-app` 하면 잘못된 Next.js 앱이 들어가던 빈틈 해소. `--platform` 미지정 시 기존 `apps/*` 의 `package.json` deps 스캔해서 platform 추론 (모든 앱이 같은 플랫폼이면 그 값으로). 혼재 or 빈 apps/ 면 TTY 면 prompt, 비대화형이면 'next' fallback.",
12
+ "**`--tauri` 옵션이 add-app 에도 동작** — `sh-ui-cli add-app admin --tauri` 또는 MCP `tauri: true` 로 `apps/{name}/src-tauri/` 까지 한 번에 emit. v0.90.0 의 monorepo+tauri 와 동일하게 dev port 가 `tauri.conf.json` 의 `devUrl` 과 `vite.config.ts` 의 `server.port` 양쪽에 자동 박힘. tauri + platform=next 조합은 CLI + MCP 양쪽에서 명시적 Korean 에러.",
13
+ "**Smoke V13 + V14 회귀 가드** — V13 은 vite monorepo create 후 `add-app admin --port 3001 --tauri` (platform 미지정) 가 추론으로 vite 선택 + apps/admin/src-tauri/ + devUrl=3001 + crate name 까지 단언. V14 는 next monorepo 에 `add-app --platform next --tauri` 가 에러 throw 확인. 기존 scenarios 4 + 4b 도 새 platform 추론 분기에 맞춰 prompt 모의 갱신.",
14
+ "**Backward-compat 확인** — Next.js 모노레포에서 기존 `add-app` 호출 흐름은 platform 추론 결과 'next' 가 나와 동일 동작. 깨지는 호출 없음."
15
+ ],
16
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.91.0"
17
+ },
5
18
  {
6
19
  "version": "0.90.0",
7
20
  "date": "2026-05-14",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.90.0",
3
+ "version": "0.91.0",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -477,6 +477,37 @@ export async function addApp(options = {}) {
477
477
  throw new Error('비대화형 환경(TTY 없음)에서는 name 이 필요합니다.');
478
478
  }
479
479
 
480
+ // platform 결정 — 명시 시 그대로, 미지정 시 기존 apps/* 의 package.json 스캔으로 추론.
481
+ let platform = options.platform;
482
+ if (!platform) {
483
+ platform = await inferMonorepoPlatform(cwd);
484
+ if (!platform) {
485
+ // 모노레포에 아직 앱이 없거나 혼재 — 안전한 디폴트 (next 가 기존 동작).
486
+ // 첫 앱은 user 가 명시하는 게 권장 (interactive prompt).
487
+ if (process.stdin.isTTY) {
488
+ platform = await select({
489
+ message: '플랫폼:',
490
+ choices: [
491
+ { name: 'Next.js', value: 'next' },
492
+ { name: 'Vite (SPA)', value: 'vite' },
493
+ ],
494
+ });
495
+ } else {
496
+ platform = 'next';
497
+ }
498
+ }
499
+ }
500
+ if (platform !== 'next' && platform !== 'vite') {
501
+ throw new Error(
502
+ `add-app 는 platform=next 또는 vite 만 지원 (받은 값: ${platform}). flutter 는 standalone 만 지원.`,
503
+ );
504
+ }
505
+ if (options.tauri && platform !== 'vite') {
506
+ throw new Error(
507
+ `tauri 는 platform=vite 일 때만 지원합니다 (현재 platform=${platform}). --platform vite 사용 또는 tauri 옵션 제거.`,
508
+ );
509
+ }
510
+
480
511
  const appName = validateProjectName(
481
512
  options.name ?? await input({
482
513
  message: '앱 이름:',
@@ -523,9 +554,13 @@ export async function addApp(options = {}) {
523
554
  // arch 는 모노레포가 처음 만들어질 때 정한 값과 같아야 한다. root config 에 별도
524
555
  // 저장 안 해 두므로 일단 DEFAULT_ARCH (fsd) fallback. 향후 root config 에 arch
525
556
  // 박아두고 여기서 읽어오는 흐름으로 개선 가능.
526
- const arch = assertArchPlatformCompat(DEFAULT_ARCH, 'next');
557
+ const arch = assertArchPlatformCompat(DEFAULT_ARCH, platform);
527
558
 
528
- await generateApp(appsDir, appName, port, plugins, arch, css);
559
+ if (platform === 'vite') {
560
+ await generateViteApp(appsDir, appName, port, arch, css, { tauri: !!options.tauri });
561
+ } else {
562
+ await generateApp(appsDir, appName, port, plugins, arch, css);
563
+ }
529
564
 
530
565
  // monorepo 의 새 ui-app 패키지 — tokens-only role + theme 주입 + cssFramework 패치.
531
566
  // generateMonorepo 의 흐름과 정확히 동일.
@@ -538,6 +573,41 @@ export async function addApp(options = {}) {
538
573
  console.log(`\n✅ apps/${appName} 이 추가되었습니다!`);
539
574
  console.log('\n pnpm install');
540
575
  console.log(` pnpm --filter ${appName} dev\n`);
576
+
577
+ if (platform === 'vite' && options.tauri) {
578
+ console.log('Tauri 데스크탑 셸:');
579
+ console.log(` cd apps/${appName}`);
580
+ console.log(' pnpm tauri dev # Rust 처음 빌드는 5~10분 — 캐시 후 5~10초');
581
+ console.log(' (Rust 미설치 시 https://rustup.rs/ 참고)\n');
582
+ }
583
+ }
584
+
585
+ /**
586
+ * 모노레포의 기존 apps/* 들 package.json 을 스캔해 platform 추론.
587
+ * - 모든 앱이 vite — 'vite'
588
+ * - 모든 앱이 next — 'next'
589
+ * - 혼재 또는 다른 조합 — null (user 가 명시해야 함)
590
+ * - apps/ 가 없거나 비었으면 null
591
+ */
592
+ async function inferMonorepoPlatform(cwd) {
593
+ const appsDir = path.resolve(cwd, 'apps');
594
+ if (!(await fs.pathExists(appsDir))) return null;
595
+ const entries = await fsp.readdir(appsDir, { withFileTypes: true });
596
+ const apps = entries.filter((e) => e.isDirectory());
597
+ if (apps.length === 0) return null;
598
+
599
+ const platforms = new Set();
600
+ for (const app of apps) {
601
+ const pkgPath = path.join(appsDir, app.name, 'package.json');
602
+ if (!(await fs.pathExists(pkgPath))) continue;
603
+ const pkg = await fs.readJson(pkgPath);
604
+ const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
605
+ if (deps.next) platforms.add('next');
606
+ else if (deps.vite) platforms.add('vite');
607
+ }
608
+
609
+ if (platforms.size === 1) return [...platforms][0];
610
+ return null;
541
611
  }
542
612
 
543
613
  // ─── Add component to ui packages ───
@@ -18,7 +18,7 @@ export const HELP_TEXT = `sh-ui create — sh-ui 프로젝트 스캐폴드 (Next
18
18
 
19
19
  사용법:
20
20
  sh-ui create [name] [options]
21
- sh-ui create add-app [name] [--port <n>] [--plugins ..] [--theme ..] [--css ..]
21
+ sh-ui create add-app [name] [--port <n>] [--platform <next|vite>] [--plugins ..] [--theme ..] [--css ..] [--tauri]
22
22
  sh-ui create add-component <name> [--app <name>]
23
23
 
24
24
  옵션:
@@ -73,6 +73,8 @@ export async function runCreate(rest) {
73
73
  plugins: flags.plugins,
74
74
  theme: flags.theme,
75
75
  css: flags.css,
76
+ platform: flags.platform,
77
+ tauri: flags.tauri,
76
78
  });
77
79
  } else if (command === 'add-component') {
78
80
  // 호환 별칭 — 신규 진입점은 `sh-ui add <name>` (bin/sh-ui.mjs 가 walk-up 으로 라우팅).
package/src/mcp.mjs CHANGED
@@ -472,10 +472,11 @@ export async function startMcpServer() {
472
472
  "sh_ui_add_app",
473
473
  {
474
474
  description:
475
- "기존 모노레포에 새 Next.js 추가 — apps/{name}/ + packages/ui/ui-apps/ui-{name}/ 동시 생성. " +
475
+ "기존 모노레포에 새 앱 (Next.js 또는 Vite) 추가 — apps/{name}/ + packages/ui/ui-apps/ui-{name}/ 동시 생성. " +
476
476
  "사용자가 '앱 추가' / 'monorepo 에 새 앱' / 'add admin app' 류 요청을 하면 이 툴 사용 (Bash 로 npx " + cliName + " add-app 직접 호출보다 우선). " +
477
477
  "v0.65+ 레이아웃 준수 — ui-{name} 은 tokens-only role, 컴포넌트는 sibling ui-core 가 SoT. " +
478
- "theme/css 는 새 ui-app 에만 적용 (다른 앱 영향 없음). monorepo 가 아니면 (pnpm-workspace.yaml 없음) 에러.",
478
+ "theme/css 는 새 ui-app 에만 적용 (다른 앱 영향 없음). monorepo 가 아니면 (pnpm-workspace.yaml 없음) 에러. " +
479
+ "platform 미지정 시 기존 apps/* 스캔해 추론 (모든 앱이 같은 플랫폼이면 그것으로). vite + tauri:true 면 apps/{name}/src-tauri/ 도 함께 emit.",
479
480
  inputSchema: {
480
481
  name: z.string().min(1)
481
482
  .describe("앱 이름 — apps/{name}/ + packages/ui/ui-apps/ui-{name}/ 디렉토리명. 영숫자 + 하이픈."),
@@ -487,6 +488,10 @@ export async function startMcpServer() {
487
488
  .describe(`프리셋 이름 (${THEME_PRESETS_LIST}) 또는 base64 테마 코드. 새 ui-app 의 tokens.css 에만 주입.`),
488
489
  cssFramework: z.enum(CSS_FRAMEWORKS).optional()
489
490
  .describe("CSS 프레임워크. 기본 plain. 새 앱의 컴포넌트 변종 결정 — 같은 모노레포 내 다른 앱과 다른 값 가능."),
491
+ platform: z.enum(["next", "vite"]).optional()
492
+ .describe("플랫폼 — next | vite. 미지정 시 기존 apps/* 의 deps 로 추론 (모든 앱이 같은 플랫폼이면 그것으로, 혼재면 명시 필요)."),
493
+ tauri: z.boolean().optional()
494
+ .describe("Tauri 2.x 데스크탑 셸 — platform=vite 일 때만 의미. apps/{name}/src-tauri/ 에 emit. 기본 false."),
490
495
  cwd: z.string().optional()
491
496
  .describe("모노레포 루트 (pnpm-workspace.yaml 있는 곳). 기본 process.cwd()"),
492
497
  },
@@ -497,6 +502,15 @@ export async function startMcpServer() {
497
502
  } catch (e) {
498
503
  return { isError: true, content: [{ type: "text", text: e.message }] };
499
504
  }
505
+ if (input.tauri && input.platform === "next") {
506
+ return {
507
+ isError: true,
508
+ content: [{
509
+ type: "text",
510
+ text: "tauri 는 platform=vite 일 때만 지원합니다. --platform vite 사용 또는 tauri 옵션 제거.",
511
+ }],
512
+ };
513
+ }
500
514
  const text = await captureConsole(() =>
501
515
  addApp({
502
516
  name: input.name,
@@ -504,6 +518,8 @@ export async function startMcpServer() {
504
518
  plugins: input.plugins,
505
519
  theme: input.theme,
506
520
  css: input.cssFramework,
521
+ platform: input.platform,
522
+ tauri: input.tauri,
507
523
  cwd: resolveCwd(input),
508
524
  }),
509
525
  );