sh-ui-cli 0.64.7 → 0.66.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/bin/sh-ui.mjs CHANGED
@@ -18,6 +18,8 @@ const usage = `사용법:
18
18
  sh-ui rename-app <old> <new> monorepo 의 앱 이름 일괄 변경
19
19
  (apps/<old>/, packages/ui/ui-apps/ui-<old>/
20
20
  디렉토리 + 모든 import/path 패턴)
21
+ sh-ui migrate-v065 v0.64.x → v0.65 자동 마이그레이션
22
+ (--dry-run 기본, --apply 로 실제 적용)
21
23
  sh-ui mcp MCP 서버(stdio) 시작 — IDE-내 AI용
22
24
  sh-ui mcp init --client <name> IDE MCP 설정 파일에 sh-ui 엔트리 자동 추가
23
25
  (claude-code | cursor | claude-desktop)
@@ -93,6 +95,18 @@ try {
93
95
  await renameApp({ cwd: process.cwd(), oldName, newName, yes, dryRun, skipInstall });
94
96
  break;
95
97
  }
98
+ case "migrate-v065": {
99
+ const apply = rest.includes("--apply");
100
+ const skipImportRewrite = rest.includes("--skip-import-rewrite");
101
+ const { migrateToV065 } = await import("../src/migrate-v065.mjs");
102
+ const { summary } = await migrateToV065({
103
+ cwd: process.cwd(),
104
+ dryRun: !apply,
105
+ skipImportRewrite,
106
+ });
107
+ console.log(summary);
108
+ break;
109
+ }
96
110
  case "remove":
97
111
  case "rm": {
98
112
  const force = rest.includes("--force");
@@ -2,6 +2,32 @@
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.66.0",
7
+ "date": "2026-05-09",
8
+ "title": "monorepo: sh_ui_add_app MCP 툴 + sh_ui_migrate_to_v065 자동 마이그레이션",
9
+ "type": "minor",
10
+ "highlights": [
11
+ "**`sh_ui_add_app` MCP 툴 신규** — 기존 모노레포에 새 Next.js 앱 추가 (`apps/{name}/` + `packages/ui/ui-apps/ui-{name}/` 동시 생성). v0.65 ui-core/ui-app 분리 레이아웃 자동 준수, theme/css 인자로 새 앱 별 톤/프레임워크 지정 가능. CLI 진입점 `sh-ui create add-app [name] [--port/--plugins/--theme/--css]` 도 동시 확장.",
12
+ "**`sh_ui_migrate_to_v065` 자동 마이그레이션** — v0.64.x → v0.65 monorepo 컴포넌트 통합 도구. ui-{app}/src/{components,hooks,lib}/ 의 파일을 ui-core 단일 SoT 로 이동, ui-app 에 `role: \"tokens-only\"` 마커 + paths/aliases/exports 정리, `apps/*` 의 `@workspace/ui-{app}/(components|hooks|lib)/` 임포트를 `@workspace/ui-core/...` 로 일괄 재작성. dryRun 기본, 컨텐츠 충돌 시 abort (자동 병합 안 함).",
13
+ "**`apps/docs/migrations/v0.65.md`** — 자동 도구 사용법 + 충돌 해결 가이드 + 수동 마이그레이션 6 단계 + 호환성 노트.",
14
+ "**addApp 옵션 객체 시그니처** — `addApp({ name, port, plugins, theme, css, cwd })`. 비대화형 (MCP daemon, CI) 지원 + 새 ui-app 의 tokens.css 에 theme 주입까지 createProject 흐름과 통일."
15
+ ],
16
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.66.0"
17
+ },
18
+ {
19
+ "version": "0.65.0",
20
+ "date": "2026-05-09",
21
+ "title": "monorepo 컴포넌트 단일 SoT — ui-core 도입, ui-app 은 tokens-only",
22
+ "type": "minor",
23
+ "highlights": [
24
+ "**컴포넌트 중복 emit 제거** — monorepo 에서 `sh-ui add <component>` 가 `packages/ui/ui-core` 한 곳에만 컴포넌트/훅을 떨어뜨림. 이전엔 ui-{app} 마다 같은 파일이 복제돼 sync 비용이 컸다.",
25
+ "**역할 분리** — `ui-core` 가 컴포넌트/훅/lib SoT, `ui-{app}` 은 토큰/스타일 전용 (`role: \"tokens-only\"` 마커). `sh-ui add tokens` 만 ui-app 으로 라우팅, 그 외는 모두 ui-core.",
26
+ "**친절한 거부 메시지** — 사용자가 ui-app 에서 직접 `sh-ui add button` 을 실행하면 \"ui-core 에 추가하세요\" 안내 후 종료. 잘못된 위치에 컴포넌트 누적 차단.",
27
+ "**v0.64.x 호환 fallback** — 기존 monorepo 레이아웃(ui-core 부재)에서는 자동으로 ui-apps 직접 라우팅으로 폴백. 마이그레이션 자동 도구는 후속 v0.66.0 (PR-D)."
28
+ ],
29
+ "url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.65.0"
30
+ },
5
31
  {
6
32
  "version": "0.64.7",
7
33
  "date": "2026-05-08",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh-ui-cli",
3
- "version": "0.64.7",
3
+ "version": "0.66.0",
4
4
  "description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/add.mjs CHANGED
@@ -363,6 +363,19 @@ export async function add({
363
363
  );
364
364
  }
365
365
 
366
+ // role: "tokens-only" 패키지 (v0.65+ ui-app) 는 tokens 만 허용.
367
+ // 컴포넌트는 sibling ui-core 패키지로 라우팅되도록 친절한 에러로 안내.
368
+ if (config.role === "tokens-only") {
369
+ const offending = names.filter((n) => n !== "tokens");
370
+ if (offending.length > 0) {
371
+ throw new Error(
372
+ `이 패키지는 'tokens-only' role 입니다 — ${offending.map((n) => `'${n}'`).join(', ')} 컴포넌트를 추가할 수 없습니다.\n` +
373
+ `컴포넌트는 sibling ui-core 패키지에 추가하세요 (예: cd ../ui-core && sh-ui add ${offending[0]}).\n` +
374
+ `또는 monorepo 루트에서 \`sh-ui add ${offending[0]}\` 실행 시 자동으로 ui-core 로 라우팅됩니다.`,
375
+ );
376
+ }
377
+ }
378
+
366
379
  // 비대화형(non-TTY)이면 prompt 를 못 띄우니 안전하게 keep 으로 강등.
367
380
  const effectiveStrategy =
368
381
  onConflict === "prompt" && !process.stdin.isTTY ? "keep" : onConflict;
@@ -12,7 +12,7 @@ const VALID_STRUCTURES = CREATE_STRUCTURES;
12
12
  const VALID_PLUGINS = allPlugins.map((p) => p.name);
13
13
  const VALID_ARCHES = allArchitectures.map((a) => a.name);
14
14
 
15
- const VALUE_FLAGS = ['platform', 'structure', 'plugins', 'theme', 'app', 'css', 'arch'];
15
+ const VALUE_FLAGS = ['platform', 'structure', 'plugins', 'theme', 'app', 'css', 'arch', 'port'];
16
16
  const BOOL_FLAGS = ['yes', 'help', 'dry-run'];
17
17
 
18
18
  const SUBCOMMANDS = ['add-app', 'add-component'];
@@ -337,47 +337,84 @@ async function listAllFiles(targetDir) {
337
337
 
338
338
  // ─── Add app to existing monorepo ───
339
339
 
340
- export async function addApp() {
341
- const isMonorepo = await fs.pathExists(
342
- path.resolve(process.cwd(), 'pnpm-workspace.yaml'),
343
- );
340
+ /**
341
+ * 기존 monorepo 에 새 Next.js 앱 추가 — apps/{name}/ + packages/ui/ui-apps/ui-{name}/ 동시 생성.
342
+ *
343
+ * 옵션 객체 시그니처 (v0.66+) — MCP `sh_ui_add_app` 와 CLI `sh-ui add-app` 모두 같은 진입점.
344
+ * 미지정 옵션은 TTY 면 prompt, 비대화형(MCP/CI)이면 기본값 또는 에러.
345
+ *
346
+ * 산출물: apps/{name}/ (Next.js + arch overlay) + packages/ui/ui-apps/ui-{name}/ (tokens-only,
347
+ * v0.65 layout) — theme 인자 주면 ui-app 의 tokens.css 에 주입, css 인자는 컴포넌트 변종 결정.
348
+ */
349
+ export async function addApp(options = {}) {
350
+ const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
351
+ const isMonorepo = await fs.pathExists(path.resolve(cwd, 'pnpm-workspace.yaml'));
344
352
  if (!isMonorepo) {
345
- console.log('현재 디렉토리가 모노레포가 아닙니다. pnpm-workspace.yaml이 없습니다.');
353
+ const msg = '현재 디렉토리가 모노레포가 아닙니다. pnpm-workspace.yaml 이 없습니다.';
354
+ if (!process.stdin.isTTY) throw new Error(msg);
355
+ console.log(`❌ ${msg}`);
346
356
  return;
347
357
  }
348
358
 
349
- const appName = await input({
359
+ // 비대화형 (MCP daemon, CI) 가드 — name 만 필수. port/plugins 는 기본값.
360
+ if (!process.stdin.isTTY && !options.name) {
361
+ throw new Error('비대화형 환경(TTY 없음)에서는 name 이 필요합니다.');
362
+ }
363
+
364
+ const appName = options.name ?? await input({
350
365
  message: '앱 이름:',
351
366
  default: 'web',
352
367
  });
353
368
 
354
- const port = await input({
355
- message: '포트 번호:',
356
- default: '3000',
357
- });
358
-
359
- const selectedPlugins = await checkbox({
360
- message: '추가 기능 선택 (Space로 선택):',
361
- choices: getPluginChoices(),
362
- });
363
-
364
- const plugins = getPluginsByNames(selectedPlugins);
369
+ const port = options.port ?? (process.stdin.isTTY
370
+ ? await input({ message: '포트 번호:', default: '3000' })
371
+ : '3000');
372
+
373
+ let plugins;
374
+ if (options.plugins) {
375
+ plugins = getPluginsByNames(options.plugins);
376
+ } else if (process.stdin.isTTY) {
377
+ const selected = await checkbox({
378
+ message: '추가 기능 선택 (Space로 선택):',
379
+ choices: getPluginChoices(),
380
+ });
381
+ plugins = getPluginsByNames(selected);
382
+ } else {
383
+ plugins = [];
384
+ }
365
385
  plugins.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
366
386
 
367
- const appsDir = path.resolve(process.cwd(), 'apps', appName);
387
+ // theme/css resolve createProject 와 같은 패턴.
388
+ let theme = null;
389
+ let themeBase = null;
390
+ if (options.theme) {
391
+ theme = resolveTheme(options.theme);
392
+ themeBase = THEME_PRESET_NAMES.includes(options.theme) ? options.theme : 'custom';
393
+ }
394
+ const css = options.css ?? CSS_FRAMEWORK_DEFAULT;
368
395
 
396
+ const appsDir = path.resolve(cwd, 'apps', appName);
369
397
  if (await fs.pathExists(appsDir)) {
370
- console.log(`❌ apps/${appName} 디렉토리가 이미 존재합니다.`);
398
+ const msg = `apps/${appName} 디렉토리가 이미 존재합니다.`;
399
+ if (!process.stdin.isTTY) throw new Error(msg);
400
+ console.log(`❌ ${msg}`);
371
401
  return;
372
402
  }
373
403
 
374
- // addApp 기존 monorepo 추가 arch 일관성을 위해 모노레포가 처음
375
- // 만들어질 정한 값과 같아야 한다. 현재는 root sh-ui.config.json 등에 별도 저장
376
- // 두므로 일단 DEFAULT_ARCH (fsd) 로 fallback. 향후 root config 에 arch 박아두고
377
- // 여기서 읽어오는 흐름으로 개선 가능.
404
+ // arch 모노레포가 처음 만들어질 정한 값과 같아야 한다. root config 별도
405
+ // 저장 두므로 일단 DEFAULT_ARCH (fsd) fallback. 향후 root config arch
406
+ // 박아두고 여기서 읽어오는 흐름으로 개선 가능.
378
407
  const arch = assertArchPlatformCompat(DEFAULT_ARCH, 'next');
379
408
 
380
- await generateApp(appsDir, appName, port, plugins, arch);
409
+ await generateApp(appsDir, appName, port, plugins, arch, css);
410
+
411
+ // monorepo 의 새 ui-app 패키지 — tokens-only role + theme 주입 + cssFramework 패치.
412
+ // generateMonorepo 의 흐름과 정확히 동일.
413
+ const uiAppDir = path.resolve(cwd, 'packages', 'ui', 'ui-apps', `ui-${appName}`);
414
+ if (theme) {
415
+ await injectCssTheme(uiAppDir, theme);
416
+ }
417
+ await patchShUiConfig(path.join(uiAppDir, 'sh-ui.config.json'), css, themeBase);
381
418
 
382
419
  console.log(`\n✅ apps/${appName} 이 추가되었습니다!`);
383
420
  console.log('\n pnpm install');
@@ -401,9 +438,45 @@ export async function addComponent(componentName, appName) {
401
438
  return;
402
439
  }
403
440
 
404
- // Monorepo: packages/ui/ui-apps/* 에서 실행
441
+ // Monorepo (v0.65+): tokens 는 packages/ui/ui-apps/ui-{app} 으로, 그 외 컴포넌트/훅은
442
+ // packages/ui/ui-core 단일 SoT 로 라우팅. 컴포넌트 중복 emit 제거가 v0.65 의 핵심.
443
+ // v0.64.x 호환: ui-core/sh-ui.config.json 미존재 시 (구 모노레포 레이아웃) 기존 ui-apps
444
+ // 직접 라우팅 분기로 내려감.
445
+ const uiCoreDir = path.join(cwd, 'packages', 'ui', 'ui-core');
405
446
  const uiAppsDir = path.join(cwd, 'packages', 'ui', 'ui-apps');
406
- if (!(await fs.pathExists(uiAppsDir))) {
447
+ const hasUiCore = await fs.pathExists(path.join(uiCoreDir, 'sh-ui.config.json'));
448
+ const hasUiApps = await fs.pathExists(uiAppsDir);
449
+
450
+ if (!hasUiCore && !hasUiApps) {
451
+ console.log('❌ packages/ui/ui-core 또는 packages/ui/ui-apps/ 디렉토리가 없습니다.');
452
+ return;
453
+ }
454
+
455
+ if (!componentName) {
456
+ componentName = await input({ message: '컴포넌트 이름:' });
457
+ }
458
+
459
+ const isTokens = componentName === 'tokens';
460
+
461
+ // ─── 비-tokens (컴포넌트/훅/lib) → ui-core 단일 라우팅 ───
462
+ if (!isTokens && hasUiCore) {
463
+ if (appName) {
464
+ console.log(
465
+ `ℹ️ v0.65+ 에서 컴포넌트는 packages/ui/ui-core 에 단일 emit 됩니다 (--app ${appName} 무시).`,
466
+ );
467
+ }
468
+ console.log(`\n📦 packages/ui/ui-core 에 ${componentName} 추가 중...`);
469
+ try {
470
+ execSync(`npx sh-ui add ${componentName}`, { cwd: uiCoreDir, stdio: 'inherit' });
471
+ console.log(`✅ packages/ui/ui-core 완료`);
472
+ } catch (error) {
473
+ console.log(`❌ packages/ui/ui-core 실패: ${error.message}`);
474
+ }
475
+ return;
476
+ }
477
+
478
+ // ─── tokens → ui-apps/ui-{app}(s), 또는 v0.64.x 호환 fallback (ui-core 없음) ───
479
+ if (!hasUiApps) {
407
480
  console.log('❌ packages/ui/ui-apps/ 디렉토리가 없습니다.');
408
481
  return;
409
482
  }
@@ -418,10 +491,6 @@ export async function addComponent(componentName, appName) {
418
491
  return;
419
492
  }
420
493
 
421
- if (!componentName) {
422
- componentName = await input({ message: '컴포넌트 이름:' });
423
- }
424
-
425
494
  let targets;
426
495
  if (appName) {
427
496
  const pkgName = `ui-${appName}`;
@@ -431,9 +500,11 @@ export async function addComponent(componentName, appName) {
431
500
  return;
432
501
  }
433
502
  targets = [pkgName];
503
+ } else if (uiPackages.length === 1) {
504
+ targets = uiPackages;
434
505
  } else {
435
506
  const choice = await select({
436
- message: '어디에 추가할까요?',
507
+ message: isTokens ? '어느 ui 패키지에 토큰을 추가할까요?' : '어디에 추가할까요?',
437
508
  choices: [
438
509
  { name: '모든 ui 패키지', value: 'all' },
439
510
  ...uiPackages.map((name) => ({ name: `packages/ui/ui-apps/${name}`, value: name })),
@@ -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
21
+ sh-ui create add-app [name] [--port <n>] [--plugins ..] [--theme ..] [--css ..]
22
22
  sh-ui create add-component <name> [--app <name>]
23
23
 
24
24
  옵션:
@@ -67,7 +67,13 @@ export async function runCreate(rest) {
67
67
  }
68
68
 
69
69
  if (command === 'add-app') {
70
- await addApp();
70
+ await addApp({
71
+ name: positional[0],
72
+ port: flags.port,
73
+ plugins: flags.plugins,
74
+ theme: flags.theme,
75
+ css: flags.css,
76
+ });
71
77
  } else if (command === 'add-component') {
72
78
  await addComponent(positional[0], flags.app);
73
79
  } else {
package/src/mcp.mjs CHANGED
@@ -7,6 +7,7 @@
7
7
  // 노출 툴:
8
8
  // sh_ui_describe_init - init 4개 축(platform/base/radius/mode) enum + 한글 설명
9
9
  // sh_ui_create_project - 빈 폴더에 Next.js/Flutter 프로젝트 스캐폴드
10
+ // sh_ui_add_app - 기존 모노레포에 새 Next.js 앱 추가
10
11
  // sh_ui_init - sh-ui.config.json 생성 (비대화형)
11
12
  // sh_ui_list_components - 플랫폼 전체 컴포넌트 + 요약
12
13
  // sh_ui_get_component - 단일 컴포넌트의 메타·소스·deps
@@ -16,6 +17,7 @@
16
17
  // sh_ui_encode_theme - 토큰 객체 → base64 (사용자가 손본 톤을 영구 보관)
17
18
  // sh_ui_decode_theme - base64 → 토큰 객체 (기존 테마 일부만 수정 후 재인코딩)
18
19
  // sh_ui_rename_app - monorepo 의 앱 이름 일괄 변경 (디렉토리 + import/path)
20
+ // sh_ui_migrate_to_v065 - v0.64.x → v0.65 자동 마이그레이션 (컴포넌트 ui-core 단일화)
19
21
 
20
22
  import { readFile } from "node:fs/promises";
21
23
  import { existsSync } from "node:fs";
@@ -29,7 +31,8 @@ import { add } from "./add.mjs";
29
31
  import { list } from "./list.mjs";
30
32
  import { remove } from "./remove.mjs";
31
33
  import { renameApp } from "./rename-app.mjs";
32
- import { createProject } from "./create/generator.js";
34
+ import { migrateToV065 } from "./migrate-v065.mjs";
35
+ import { createProject, addApp } from "./create/generator.js";
33
36
  import {
34
37
  getRegistryRoot,
35
38
  getSummariesPath,
@@ -325,6 +328,44 @@ export async function startMcpServer() {
325
328
  },
326
329
  );
327
330
 
331
+ server.registerTool(
332
+ "sh_ui_add_app",
333
+ {
334
+ description:
335
+ "기존 모노레포에 새 Next.js 앱 추가 — apps/{name}/ + packages/ui/ui-apps/ui-{name}/ 동시 생성. " +
336
+ "사용자가 '앱 추가' / 'monorepo 에 새 앱' / 'add admin app' 류 요청을 하면 이 툴 사용 (Bash 로 npx " + cliName + " add-app 직접 호출보다 우선). " +
337
+ "v0.65+ 레이아웃 준수 — ui-{name} 은 tokens-only role, 컴포넌트는 sibling ui-core 가 SoT. " +
338
+ "theme/css 는 새 ui-app 에만 적용 (다른 앱 영향 없음). monorepo 가 아니면 (pnpm-workspace.yaml 없음) 에러.",
339
+ inputSchema: {
340
+ name: z.string().min(1)
341
+ .describe("앱 이름 — apps/{name}/ + packages/ui/ui-apps/ui-{name}/ 디렉토리명. 영숫자 + 하이픈."),
342
+ port: z.string().optional()
343
+ .describe("개발 서버 포트. 기본 3000. 다른 앱과 겹치면 사용자가 직접 다르게 지정."),
344
+ plugins: z.array(z.enum(PLUGIN_NAMES)).optional()
345
+ .describe(`Next.js 플러그인 (${PLUGIN_NAMES.join(', ')}). 미지정시 빈 배열`),
346
+ theme: z.string().optional()
347
+ .describe(`프리셋 이름 (${THEME_PRESETS_LIST}) 또는 base64 테마 코드. 새 ui-app 의 tokens.css 에만 주입.`),
348
+ cssFramework: z.enum(CSS_FRAMEWORKS).optional()
349
+ .describe("CSS 프레임워크. 기본 plain. 새 앱의 컴포넌트 변종 결정 — 같은 모노레포 내 다른 앱과 다른 값 가능."),
350
+ cwd: z.string().optional()
351
+ .describe("모노레포 루트 (pnpm-workspace.yaml 있는 곳). 기본 process.cwd()"),
352
+ },
353
+ },
354
+ async (input) => {
355
+ const text = await captureConsole(() =>
356
+ addApp({
357
+ name: input.name,
358
+ port: input.port,
359
+ plugins: input.plugins,
360
+ theme: input.theme,
361
+ css: input.cssFramework,
362
+ cwd: resolveCwd(input),
363
+ }),
364
+ );
365
+ return textResult(text || "✓ 앱 추가 완료");
366
+ },
367
+ );
368
+
328
369
  server.registerTool(
329
370
  "sh_ui_init",
330
371
  {
@@ -607,6 +648,37 @@ export async function startMcpServer() {
607
648
  },
608
649
  );
609
650
 
651
+ // v0.64.x → v0.65 monorepo 자동 마이그레이션.
652
+ server.registerTool(
653
+ "sh_ui_migrate_to_v065",
654
+ {
655
+ description:
656
+ "v0.64.x → v0.65 monorepo 자동 마이그레이션 — 컴포넌트/훅/lib 를 ui-{app} 들에서 ui-core 단일 SoT 로 통합 + ui-app 을 tokens-only role 로 정리 + apps/* 의 import (`@workspace/ui-{app}/components/...` → `@workspace/ui-core/...`) 재작성. " +
657
+ "**dryRun 기본 — 안전 우선**. 컨텐츠 충돌(같은 logical path 의 파일이 ui-app 마다 다른 내용) 검출 시 abort + 충돌 목록 반환 (자동 병합 안 함, 사용자가 손본 컴포넌트 보호). " +
658
+ "monorepo 전용 (pnpm-workspace.yaml + packages/ui/ui-apps 필요). 적용 후 사용자에게 `pnpm install` 안내 필수.",
659
+ inputSchema: {
660
+ cwd: z.string().optional().describe("monorepo 루트. 기본 process.cwd()"),
661
+ dryRun: z.boolean().optional().describe("plan 만 반환, 실제 변경 X. 기본 true (안전)"),
662
+ skipImportRewrite: z.boolean().optional().describe("apps/* import 재작성 생략. 기본 false"),
663
+ },
664
+ },
665
+ async (input) => {
666
+ try {
667
+ const { summary } = await migrateToV065({
668
+ cwd: resolveCwd(input),
669
+ dryRun: input.dryRun !== false,
670
+ skipImportRewrite: input.skipImportRewrite === true,
671
+ });
672
+ return textResult(summary);
673
+ } catch (e) {
674
+ return {
675
+ isError: true,
676
+ content: [{ type: "text", text: e.message }],
677
+ };
678
+ }
679
+ },
680
+ );
681
+
610
682
  // 변경 내역 조회 — 보너스: 사용자가 "최근 변경 알려줘" 류 요청 시
611
683
  server.registerTool(
612
684
  "sh_ui_get_changelog",
@@ -0,0 +1,431 @@
1
+ // v0.64.x → v0.65 monorepo 마이그레이션 도구.
2
+ //
3
+ // v0.64.x 레이아웃: 컴포넌트가 packages/ui/ui-apps/ui-{app}/src/components/ 에 emit.
4
+ // 같은 모노레포 내 N 개 앱이면 같은 컴포넌트가 N 번 복제됨.
5
+ // v0.65 레이아웃: 컴포넌트는 packages/ui/ui-core/src/components/ 단일 SoT.
6
+ // ui-{app} 은 tokens-only role (토큰/스타일만).
7
+ //
8
+ // 안전 원칙:
9
+ // - dryRun 기본 — 실제 파일 변경은 명시적 dryRun=false 시에만.
10
+ // - 컨텐츠 충돌 시 즉시 abort — 자동 병합 시도하지 않음 (사용자가 손본 컴포넌트 보호).
11
+ // - import 재작성은 정확한 패턴 (`@workspace/ui-{app}/{kind}/`) 만 — false-positive 차단.
12
+
13
+ import * as fsp from "node:fs/promises";
14
+ import path from "node:path";
15
+ import { createHash } from "node:crypto";
16
+
17
+ const COMPONENT_KINDS = ["components", "hooks", "lib"];
18
+
19
+ async function fileExists(p) {
20
+ try {
21
+ await fsp.access(p);
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ async function listFilesRecursive(dir) {
29
+ const out = [];
30
+ async function walk(d) {
31
+ const entries = await fsp.readdir(d, { withFileTypes: true });
32
+ for (const e of entries) {
33
+ const full = path.join(d, e.name);
34
+ if (e.isDirectory()) {
35
+ if (e.name === "node_modules" || e.name === ".next" || e.name === "dist") continue;
36
+ await walk(full);
37
+ } else if (e.isFile()) {
38
+ out.push(full);
39
+ }
40
+ }
41
+ }
42
+ await walk(dir);
43
+ return out;
44
+ }
45
+
46
+ function hashContent(s) {
47
+ return createHash("sha256").update(s).digest("hex").slice(0, 12);
48
+ }
49
+
50
+ /**
51
+ * 마이그레이션 plan 빌드 + (옵션) 적용.
52
+ *
53
+ * @param {Object} options
54
+ * @param {string} [options.cwd] — monorepo 루트. 기본 process.cwd().
55
+ * @param {boolean} [options.dryRun=true] — true 면 plan 텍스트 반환, false 면 실제 적용.
56
+ * @param {boolean} [options.skipImportRewrite] — apps/* 의 import 재작성 생략. 기본 false.
57
+ * @returns {Promise<{plan: object, summary: string}>}
58
+ */
59
+ export async function migrateToV065(options = {}) {
60
+ const cwd = path.resolve(options.cwd ?? process.cwd());
61
+ const dryRun = options.dryRun !== false; // 기본 true (안전)
62
+ const skipImportRewrite = options.skipImportRewrite === true;
63
+
64
+ // ─── 1. 사전 검사 ───
65
+ if (!(await fileExists(path.join(cwd, "pnpm-workspace.yaml")))) {
66
+ throw new Error(
67
+ "pnpm-workspace.yaml 없음 — 모노레포가 아닙니다. v0.65 마이그레이션은 monorepo 변종에만 적용됩니다.",
68
+ );
69
+ }
70
+
71
+ const uiAppsDir = path.join(cwd, "packages", "ui", "ui-apps");
72
+ if (!(await fileExists(uiAppsDir))) {
73
+ throw new Error(
74
+ "packages/ui/ui-apps/ 없음 — v0.64.x monorepo 레이아웃이 아니거나 이미 마이그레이션됨.",
75
+ );
76
+ }
77
+
78
+ const uiPackages = (await fsp.readdir(uiAppsDir, { withFileTypes: true }))
79
+ .filter((e) => e.isDirectory() && e.name.startsWith("ui-") && e.name !== "ui-app-template")
80
+ .map((e) => e.name);
81
+
82
+ if (uiPackages.length === 0) {
83
+ throw new Error("packages/ui/ui-apps/ui-* 패키지 없음.");
84
+ }
85
+
86
+ const uiCoreDir = path.join(cwd, "packages", "ui", "ui-core");
87
+ const uiCoreCfgPath = path.join(uiCoreDir, "sh-ui.config.json");
88
+ const uiCoreAlreadyV065 = await fileExists(uiCoreCfgPath);
89
+
90
+ // ─── 2. 파일 수집 + 충돌 검출 ───
91
+ // logicalPath ('src/components/button/index.tsx') → [{app, srcPath, hash, content}]
92
+ const fileMap = new Map();
93
+ for (const pkg of uiPackages) {
94
+ const pkgDir = path.join(uiAppsDir, pkg);
95
+ for (const kind of COMPONENT_KINDS) {
96
+ const subDir = path.join(pkgDir, "src", kind);
97
+ if (!(await fileExists(subDir))) continue;
98
+ const files = await listFilesRecursive(subDir);
99
+ for (const file of files) {
100
+ const rel = path.relative(subDir, file);
101
+ if (rel === ".gitkeep" || rel.startsWith(".gitkeep")) continue;
102
+ const content = await fsp.readFile(file, "utf-8");
103
+ const hash = hashContent(content);
104
+ const logicalPath = path.posix.join("src", kind, ...rel.split(path.sep));
105
+ const list = fileMap.get(logicalPath) ?? [];
106
+ list.push({ app: pkg, srcPath: file, hash, content });
107
+ fileMap.set(logicalPath, list);
108
+ }
109
+ }
110
+ }
111
+
112
+ // ui-core 에 같은 logical path 의 기존 파일이 있으면 충돌 검출 대상에 포함.
113
+ for (const [logicalPath, entries] of fileMap) {
114
+ const uiCorePath = path.join(uiCoreDir, logicalPath);
115
+ if (await fileExists(uiCorePath)) {
116
+ const existing = await fsp.readFile(uiCorePath, "utf-8");
117
+ const hash = hashContent(existing);
118
+ entries.push({ app: "ui-core (existing)", srcPath: uiCorePath, hash, content: existing });
119
+ }
120
+ }
121
+
122
+ const moves = [];
123
+ const conflicts = [];
124
+ for (const [logicalPath, entries] of fileMap) {
125
+ const uniqueHashes = [...new Set(entries.map((e) => e.hash))];
126
+ if (uniqueHashes.length > 1) {
127
+ conflicts.push({
128
+ logicalPath,
129
+ variants: entries.map((e) => ({ app: e.app, hash: e.hash })),
130
+ });
131
+ } else {
132
+ moves.push({
133
+ logicalPath,
134
+ sourceApps: entries
135
+ .filter((e) => e.app !== "ui-core (existing)")
136
+ .map((e) => e.app),
137
+ canonicalSrc: entries[0].srcPath,
138
+ dest: path.join(uiCoreDir, logicalPath),
139
+ alreadyInUiCore: entries.some((e) => e.app === "ui-core (existing)"),
140
+ });
141
+ }
142
+ }
143
+
144
+ // ─── 3. ui-app config / package.json 패치 plan ───
145
+ const appConfigPatches = [];
146
+ const appPackageJsonPatches = [];
147
+ for (const pkg of uiPackages) {
148
+ const cfgPath = path.join(uiAppsDir, pkg, "sh-ui.config.json");
149
+ if (await fileExists(cfgPath)) {
150
+ const cfg = JSON.parse(await fsp.readFile(cfgPath, "utf-8"));
151
+ if (cfg.role !== "tokens-only") {
152
+ appConfigPatches.push({ pkg, cfgPath });
153
+ }
154
+ }
155
+ const pkgJsonPath = path.join(uiAppsDir, pkg, "package.json");
156
+ if (await fileExists(pkgJsonPath)) {
157
+ const pkgJson = JSON.parse(await fsp.readFile(pkgJsonPath, "utf-8"));
158
+ const removeExports = ["./components/*", "./hooks/*", "./lib/*"]
159
+ .filter((k) => pkgJson.exports?.[k]);
160
+ if (removeExports.length > 0) {
161
+ appPackageJsonPatches.push({ pkg, pkgJsonPath, removeExports });
162
+ }
163
+ }
164
+ }
165
+
166
+ // ─── 4. apps/* import 재작성 plan ───
167
+ const importRewrites = [];
168
+ if (!skipImportRewrite) {
169
+ const appsDir = path.join(cwd, "apps");
170
+ if (await fileExists(appsDir)) {
171
+ const apps = (await fsp.readdir(appsDir, { withFileTypes: true }))
172
+ .filter((e) => e.isDirectory())
173
+ .map((e) => e.name);
174
+ const tsExt = /\.(ts|tsx|mjs|js|jsx)$/;
175
+ const re = /@workspace\/ui-([a-zA-Z0-9-]+)\/(components|hooks|lib)\//g;
176
+ for (const app of apps) {
177
+ const appDir = path.join(appsDir, app);
178
+ const files = await listFilesRecursive(appDir);
179
+ for (const file of files) {
180
+ if (!tsExt.test(file)) continue;
181
+ const content = await fsp.readFile(file, "utf-8");
182
+ if (!content.includes("@workspace/ui-")) continue;
183
+ let count = 0;
184
+ const rewritten = content.replace(re, (match, appName) => {
185
+ if (appName === "core") return match; // already core
186
+ count++;
187
+ return match.replace(`@workspace/ui-${appName}/`, "@workspace/ui-core/");
188
+ });
189
+ if (count > 0) {
190
+ importRewrites.push({ file, count, rewritten });
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ const plan = {
198
+ uiCoreAlreadyV065,
199
+ uiPackages,
200
+ moves,
201
+ conflicts,
202
+ appConfigPatches,
203
+ appPackageJsonPatches,
204
+ importRewrites,
205
+ };
206
+
207
+ // ─── 5. 충돌 발생 시 abort ───
208
+ if (conflicts.length > 0) {
209
+ const summary = renderConflicts(conflicts);
210
+ const err = new Error(
211
+ `자동 마이그레이션 abort — ${conflicts.length} 개 파일에 컨텐츠 충돌.\n` +
212
+ `같은 logical path 의 파일이 ui-app 마다 다른 내용을 가지고 있습니다 (사용자가 손본 결과 가능성).\n` +
213
+ `수동으로 해결한 뒤 재실행하거나, 충돌 파일은 수동 이전.\n\n${summary}`,
214
+ );
215
+ err.conflicts = conflicts;
216
+ throw err;
217
+ }
218
+
219
+ // ─── 6. dryRun: plan 텍스트 반환 ───
220
+ if (dryRun) {
221
+ const summary = renderPlan(plan, cwd);
222
+ return { plan, summary };
223
+ }
224
+
225
+ // ─── 7. apply ───
226
+ await applyPlan(plan, { cwd, uiAppsDir, uiCoreDir });
227
+ return {
228
+ plan,
229
+ summary:
230
+ `✓ v0.65 마이그레이션 완료\n` +
231
+ ` ${moves.length} 파일 ui-core 로 이동\n` +
232
+ ` ${appConfigPatches.length} ui-app sh-ui.config.json 패치\n` +
233
+ ` ${appPackageJsonPatches.length} ui-app package.json 패치\n` +
234
+ ` ${importRewrites.length} apps/* 파일 import 재작성`,
235
+ };
236
+ }
237
+
238
+ function renderPlan(plan, cwd) {
239
+ const lines = [];
240
+ lines.push("[DRY RUN] v0.65 마이그레이션 plan — 실제 적용은 dryRun: false");
241
+ lines.push("");
242
+
243
+ if (!plan.uiCoreAlreadyV065) {
244
+ lines.push("◇ ui-core sh-ui.config.json 신규 생성 + package.json exports 추가");
245
+ } else {
246
+ lines.push("◇ ui-core 는 이미 v0.65 레이아웃 — 컴포넌트만 이동");
247
+ }
248
+ lines.push("");
249
+
250
+ if (plan.moves.length > 0) {
251
+ lines.push(`◇ 파일 이동 (${plan.moves.length}개) — N 개 ui-app 의 동일 컨텐츠 → ui-core 단일 SoT:`);
252
+ for (const m of plan.moves.slice(0, 30)) {
253
+ const sources = m.sourceApps.length > 0 ? m.sourceApps.join(", ") : "(이미 ui-core)";
254
+ lines.push(` ${m.logicalPath} ← ${sources}${m.alreadyInUiCore ? " (ui-core 동일)" : ""}`);
255
+ }
256
+ if (plan.moves.length > 30) lines.push(` ... +${plan.moves.length - 30} more`);
257
+ lines.push("");
258
+ }
259
+
260
+ if (plan.appConfigPatches.length > 0) {
261
+ lines.push(`◇ ui-app sh-ui.config.json 패치 (${plan.appConfigPatches.length}개):`);
262
+ for (const p of plan.appConfigPatches) {
263
+ lines.push(` ${p.pkg}: role: "tokens-only" 추가, components/utils paths/aliases 제거`);
264
+ }
265
+ lines.push("");
266
+ }
267
+
268
+ if (plan.appPackageJsonPatches.length > 0) {
269
+ lines.push(`◇ ui-app package.json exports 정리 (${plan.appPackageJsonPatches.length}개):`);
270
+ for (const p of plan.appPackageJsonPatches) {
271
+ lines.push(` ${p.pkg}: ${p.removeExports.join(", ")} 제거`);
272
+ }
273
+ lines.push("");
274
+ }
275
+
276
+ if (plan.importRewrites.length > 0) {
277
+ lines.push(`◇ apps/* import 재작성 (${plan.importRewrites.length}개 파일):`);
278
+ for (const r of plan.importRewrites.slice(0, 20)) {
279
+ lines.push(` ${path.relative(cwd, r.file)} (${r.count} 곳)`);
280
+ }
281
+ if (plan.importRewrites.length > 20) {
282
+ lines.push(` ... +${plan.importRewrites.length - 20} more files`);
283
+ }
284
+ lines.push("");
285
+ }
286
+
287
+ if (
288
+ plan.moves.length === 0 &&
289
+ plan.appConfigPatches.length === 0 &&
290
+ plan.appPackageJsonPatches.length === 0 &&
291
+ plan.importRewrites.length === 0
292
+ ) {
293
+ lines.push("✓ 변경 사항 없음 — 이미 v0.65 레이아웃.");
294
+ } else {
295
+ lines.push("실제 적용: dryRun: false 로 재호출.");
296
+ }
297
+ return lines.join("\n");
298
+ }
299
+
300
+ function renderConflicts(conflicts) {
301
+ const lines = [];
302
+ for (const c of conflicts.slice(0, 15)) {
303
+ lines.push(` ${c.logicalPath}`);
304
+ for (const v of c.variants) {
305
+ lines.push(` ${v.app}: ${v.hash}`);
306
+ }
307
+ }
308
+ if (conflicts.length > 15) {
309
+ lines.push(` ... +${conflicts.length - 15} more`);
310
+ }
311
+ return lines.join("\n");
312
+ }
313
+
314
+ async function applyPlan(plan, ctx) {
315
+ const { uiAppsDir, uiCoreDir } = ctx;
316
+
317
+ // 1. ui-core 디렉토리/configs 보장
318
+ await fsp.mkdir(uiCoreDir, { recursive: true });
319
+ await fsp.mkdir(path.join(uiCoreDir, "src", "components"), { recursive: true });
320
+ await fsp.mkdir(path.join(uiCoreDir, "src", "hooks"), { recursive: true });
321
+ await fsp.mkdir(path.join(uiCoreDir, "src", "lib"), { recursive: true });
322
+
323
+ if (!plan.uiCoreAlreadyV065) {
324
+ await ensureUiCoreConfig(uiCoreDir);
325
+ await ensureUiCorePackageJsonExports(uiCoreDir);
326
+ }
327
+
328
+ // 2. 파일 이동 — copy 후 source 삭제
329
+ for (const m of plan.moves) {
330
+ if (!m.alreadyInUiCore) {
331
+ await fsp.mkdir(path.dirname(m.dest), { recursive: true });
332
+ await fsp.copyFile(m.canonicalSrc, m.dest);
333
+ }
334
+ for (const app of m.sourceApps) {
335
+ const appSrc = path.join(uiAppsDir, app, m.logicalPath);
336
+ if (await fileExists(appSrc)) {
337
+ await fsp.rm(appSrc, { force: true });
338
+ }
339
+ }
340
+ }
341
+
342
+ // 3. ui-app sh-ui.config.json 패치 — role 추가, components/utils paths/aliases 제거
343
+ for (const p of plan.appConfigPatches) {
344
+ const cfg = JSON.parse(await fsp.readFile(p.cfgPath, "utf-8"));
345
+ cfg.role = "tokens-only";
346
+ if (cfg.paths) {
347
+ delete cfg.paths.components;
348
+ delete cfg.paths.hooks;
349
+ delete cfg.paths.utils;
350
+ }
351
+ if (cfg.aliases) {
352
+ delete cfg.aliases.components;
353
+ delete cfg.aliases.hooks;
354
+ delete cfg.aliases.utils;
355
+ delete cfg.aliases.ui;
356
+ if (Object.keys(cfg.aliases).length === 0) delete cfg.aliases;
357
+ }
358
+ // role 을 platform/cssFramework 직후로 이동시키기 위해 재구성.
359
+ const ordered = {};
360
+ if (cfg.platform != null) ordered.platform = cfg.platform;
361
+ if (cfg.cssFramework != null) ordered.cssFramework = cfg.cssFramework;
362
+ ordered.role = "tokens-only";
363
+ for (const [k, v] of Object.entries(cfg)) {
364
+ if (k === "platform" || k === "cssFramework" || k === "role") continue;
365
+ ordered[k] = v;
366
+ }
367
+ await fsp.writeFile(p.cfgPath, JSON.stringify(ordered, null, 2) + "\n");
368
+ }
369
+
370
+ // 4. ui-app package.json exports 정리
371
+ for (const p of plan.appPackageJsonPatches) {
372
+ const pkgJson = JSON.parse(await fsp.readFile(p.pkgJsonPath, "utf-8"));
373
+ if (pkgJson.exports) {
374
+ for (const k of p.removeExports) delete pkgJson.exports[k];
375
+ }
376
+ await fsp.writeFile(p.pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n");
377
+ }
378
+
379
+ // 5. apps/* import 재작성
380
+ for (const r of plan.importRewrites) {
381
+ await fsp.writeFile(r.file, r.rewritten);
382
+ }
383
+
384
+ // 6. 비어진 ui-app 의 src/components/, src/hooks/, src/lib/ 디렉토리 정리.
385
+ // .gitkeep 남기지 않고 디렉토리 자체 제거 (v0.65 ui-app 은 styles 만).
386
+ for (const pkg of plan.uiPackages) {
387
+ for (const kind of COMPONENT_KINDS) {
388
+ const subDir = path.join(uiAppsDir, pkg, "src", kind);
389
+ if (!(await fileExists(subDir))) continue;
390
+ const remaining = await listFilesRecursive(subDir);
391
+ const allGitkeep = remaining.every((f) => path.basename(f) === ".gitkeep");
392
+ if (remaining.length === 0 || allGitkeep) {
393
+ await fsp.rm(subDir, { recursive: true, force: true });
394
+ }
395
+ }
396
+ }
397
+ }
398
+
399
+ async function ensureUiCoreConfig(uiCoreDir) {
400
+ const cfgPath = path.join(uiCoreDir, "sh-ui.config.json");
401
+ if (await fileExists(cfgPath)) return;
402
+ const cfg = {
403
+ platform: "react",
404
+ cssFramework: "plain",
405
+ paths: {
406
+ components: "src/components",
407
+ hooks: "src/hooks",
408
+ utils: "src/lib/utils.ts",
409
+ },
410
+ aliases: {
411
+ components: "@workspace/ui-core/components",
412
+ hooks: "@workspace/ui-core/hooks",
413
+ utils: "@workspace/ui-core/lib/utils",
414
+ ui: "@workspace/ui-core/components",
415
+ },
416
+ };
417
+ await fsp.writeFile(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
418
+ }
419
+
420
+ async function ensureUiCorePackageJsonExports(uiCoreDir) {
421
+ const pkgJsonPath = path.join(uiCoreDir, "package.json");
422
+ if (!(await fileExists(pkgJsonPath))) return;
423
+ const pkgJson = JSON.parse(await fsp.readFile(pkgJsonPath, "utf-8"));
424
+ pkgJson.exports = {
425
+ "./components/*": "./src/components/*.tsx",
426
+ "./hooks/*": "./src/hooks/*.ts",
427
+ "./lib/*": "./src/lib/*.ts",
428
+ ...(pkgJson.exports ?? {}),
429
+ };
430
+ await fsp.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n");
431
+ }
@@ -7,17 +7,29 @@
7
7
  "lint": "eslint . --max-warnings 0"
8
8
  },
9
9
  "dependencies": {
10
+ "@base-ui/react": "^1.4.1",
10
11
  "class-variance-authority": "^0.7.1",
11
12
  "clsx": "^2.1.1",
12
- "tailwind-merge": "^3.5.0"
13
+ "lucide-react": "^0.563.0",
14
+ "next-themes": "^0.4.6",
15
+ "react": "^19.2.4",
16
+ "react-dom": "^19.2.4",
17
+ "react-hook-form": "^7.56.4",
18
+ "sonner": "^2.0.7",
19
+ "tailwind-merge": "^3.5.0",
20
+ "zod": "^4.3.6"
13
21
  },
14
22
  "devDependencies": {
23
+ "@types/react": "^19.2.10",
24
+ "@types/react-dom": "^19.2.3",
15
25
  "@workspace/eslint-config": "workspace:*",
16
26
  "@workspace/typescript-config": "workspace:*",
17
27
  "eslint": "^9.39.2",
18
28
  "typescript": "^5.9.3"
19
29
  },
20
30
  "exports": {
31
+ "./components/*": "./src/components/*.tsx",
32
+ "./hooks/*": "./src/hooks/*.ts",
21
33
  "./lib/*": "./src/lib/*.ts"
22
34
  }
23
35
  }
@@ -0,0 +1,15 @@
1
+ {
2
+ "platform": "react",
3
+ "cssFramework": "plain",
4
+ "paths": {
5
+ "components": "src/components",
6
+ "hooks": "src/hooks",
7
+ "utils": "src/lib/utils.ts"
8
+ },
9
+ "aliases": {
10
+ "components": "@workspace/ui-core/components",
11
+ "hooks": "@workspace/ui-core/hooks",
12
+ "utils": "@workspace/ui-core/lib/utils",
13
+ "ui": "@workspace/ui-core/components"
14
+ }
15
+ }
@@ -7,15 +7,9 @@
7
7
  "lint": "eslint . --max-warnings 0"
8
8
  },
9
9
  "dependencies": {
10
- "@base-ui/react": "^1.4.1",
11
10
  "@workspace/ui-core": "workspace:*",
12
- "lucide-react": "^0.563.0",
13
- "next-themes": "^0.4.6",
14
11
  "react": "^19.2.4",
15
- "react-dom": "^19.2.4",
16
- "react-hook-form": "^7.56.4",
17
- "sonner": "^2.0.7",
18
- "zod": "^4.3.6"
12
+ "react-dom": "^19.2.4"
19
13
  },
20
14
  "devDependencies": {
21
15
  "@tailwindcss/postcss": "^4.1.18",
@@ -30,9 +24,6 @@
30
24
  },
31
25
  "exports": {
32
26
  "./globals.css": "./src/styles/globals.css",
33
- "./postcss.config": "./postcss.config.mjs",
34
- "./lib/*": "./src/lib/*.ts",
35
- "./components/*": "./src/components/*.tsx",
36
- "./hooks/*": "./src/hooks/*.ts"
27
+ "./postcss.config": "./postcss.config.mjs"
37
28
  }
38
29
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "platform": "react",
3
3
  "cssFramework": "plain",
4
+ "role": "tokens-only",
4
5
  "theme": {
5
6
  "base": "neutral",
6
7
  "radius": "md",
@@ -8,13 +9,6 @@
8
9
  },
9
10
  "paths": {
10
11
  "tokens": "src/styles/tokens.css",
11
- "styles": "src/styles",
12
- "components": "src/components",
13
- "utils": "src/lib/utils.ts"
14
- },
15
- "aliases": {
16
- "components": "@workspace/ui-app-name/components",
17
- "utils": "@workspace/ui-app-name/lib/utils",
18
- "ui": "@workspace/ui-app-name/components"
12
+ "styles": "src/styles"
19
13
  }
20
14
  }
File without changes