sh-ui-cli 0.66.0 → 0.67.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 +33 -2
- package/data/changelog/versions.json +15 -0
- package/package.json +1 -1
- package/src/create/generator.js +122 -67
- package/src/create/index.mjs +5 -1
- package/src/resolve-context.mjs +56 -0
package/bin/sh-ui.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { init } from "../src/init.mjs";
|
|
|
3
3
|
import { add } from "../src/add.mjs";
|
|
4
4
|
import { list } from "../src/list.mjs";
|
|
5
5
|
import { remove } from "../src/remove.mjs";
|
|
6
|
+
import { findShUiContext } from "../src/resolve-context.mjs";
|
|
6
7
|
|
|
7
8
|
const [, , cmd, ...rest] = process.argv;
|
|
8
9
|
|
|
@@ -29,6 +30,8 @@ const usage = `사용법:
|
|
|
29
30
|
--force (add) 기존 파일을 모두 덮어쓰기 (prompt 없음)
|
|
30
31
|
(remove) 사용자가 수정한 파일도 삭제
|
|
31
32
|
--keep (add) 기존 파일을 모두 유지 (prompt 없음)
|
|
33
|
+
--app <name> (add) monorepo 라우팅 시 대상 ui-{name} 명시
|
|
34
|
+
(apps/<name>/ 안에서 실행하면 자동 추론)
|
|
32
35
|
--all (list) 설치되지 않은 컴포넌트까지 표시
|
|
33
36
|
--dry-run (remove, rename-app) 변경 대상만 출력하고 실행 안 함
|
|
34
37
|
--yes (rename-app) 대화형 확인 생략
|
|
@@ -54,13 +57,41 @@ try {
|
|
|
54
57
|
process.exit(1);
|
|
55
58
|
}
|
|
56
59
|
const onConflict = force ? "overwrite" : keepFlag ? "keep" : "prompt";
|
|
57
|
-
|
|
60
|
+
// --app 은 모노레포 라우팅 시 ui-{app} 명시 (tokens 또는 v0.64.x fallback).
|
|
61
|
+
const appIdx = rest.indexOf("--app");
|
|
62
|
+
const app = appIdx !== -1 ? rest[appIdx + 1] : null;
|
|
63
|
+
const names = rest.filter((a, i) => !a.startsWith("--") && rest[i - 1] !== "--app");
|
|
58
64
|
if (names.length === 0) {
|
|
59
65
|
console.error("에러: 추가할 컴포넌트 이름이 필요합니다.\n");
|
|
60
66
|
console.error(usage);
|
|
61
67
|
process.exit(1);
|
|
62
68
|
}
|
|
63
|
-
|
|
69
|
+
|
|
70
|
+
// v0.67+: cwd 부터 walk-up 으로 sh-ui.config.json (standalone/ui-core/ui-app)
|
|
71
|
+
// 또는 pnpm-workspace.yaml (monorepo 루트) 발견. monorepo 면 자동 라우팅 (tokens →
|
|
72
|
+
// ui-app, 그 외 → ui-core), apps/<name>/ 안에서 실행하면 그 앱이 hintApp.
|
|
73
|
+
const ctx = findShUiContext(process.cwd());
|
|
74
|
+
if (!ctx) {
|
|
75
|
+
console.error(
|
|
76
|
+
"✗ sh-ui.config.json 또는 pnpm-workspace.yaml 을 cwd 부터 부모 트리에서 찾지 못했습니다.\n" +
|
|
77
|
+
" 먼저 `sh-ui init` 또는 `sh-ui create` 로 프로젝트를 초기화하세요.",
|
|
78
|
+
);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
if (ctx.kind === "config") {
|
|
82
|
+
await add({ cwd: ctx.root, names, skipInstall, diffMode, onConflict });
|
|
83
|
+
} else {
|
|
84
|
+
const { addComponent } = await import("../src/create/generator.js");
|
|
85
|
+
await addComponent({
|
|
86
|
+
cwd: ctx.root,
|
|
87
|
+
names,
|
|
88
|
+
app,
|
|
89
|
+
hintApp: ctx.hintApp,
|
|
90
|
+
skipInstall,
|
|
91
|
+
diffMode,
|
|
92
|
+
onConflict,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
64
95
|
break;
|
|
65
96
|
}
|
|
66
97
|
case "list": {
|
|
@@ -2,6 +2,21 @@
|
|
|
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.67.0",
|
|
7
|
+
"date": "2026-05-09",
|
|
8
|
+
"title": "monorepo 어디서든 sh-ui add — walk-up 컨텍스트 라우팅",
|
|
9
|
+
"type": "minor",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**`sh-ui add` 가 monorepo 전 디렉토리에서 동작** — `apps/web/`, monorepo 루트, `packages/ui/ui-core/`, `packages/ui/ui-apps/ui-{name}/` 어디서든. cwd 부터 부모 트리 walk-up 으로 `sh-ui.config.json` (standalone) 또는 `pnpm-workspace.yaml` (monorepo 루트) 자동 발견.",
|
|
12
|
+
"**hintApp 자동 추출** — `apps/web/`에서 `sh-ui add tokens` 실행 시 자동으로 ui-web 으로 라우팅 (별도 `--app` 불필요). `apps/admin/` 에선 ui-admin. monorepo 루트면 prompt (TTY) 또는 모든 앱 (비대화형 + tokens).",
|
|
13
|
+
"**npx subprocess 제거** — orchestrator 가 inner `npx sh-ui add` 대신 `add()` 함수를 in-process 호출. sh-ui-cli 가 모노레포에 local 설치 안 돼도 동작.",
|
|
14
|
+
"**다중 컴포넌트 + tokens 혼합** — `sh-ui add button card tokens` 한 줄로 ui-core 와 ui-app 양쪽에 라우팅 (이름별 그룹화).",
|
|
15
|
+
"**`--app <name>` 플래그 신규 (sh-ui add)** — hintApp 자동 추론을 명시적으로 덮어쓸 때.",
|
|
16
|
+
"**호환** — `sh-ui create add-component <name>` 는 alias 로 유지. v0.65/v0.66 의 ui-core SoT 라우팅 그대로."
|
|
17
|
+
],
|
|
18
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.67.0"
|
|
19
|
+
},
|
|
5
20
|
{
|
|
6
21
|
"version": "0.66.0",
|
|
7
22
|
"date": "2026-05-09",
|
package/package.json
CHANGED
package/src/create/generator.js
CHANGED
|
@@ -423,108 +423,163 @@ export async function addApp(options = {}) {
|
|
|
423
423
|
|
|
424
424
|
// ─── Add component to ui packages ───
|
|
425
425
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
426
|
+
/**
|
|
427
|
+
* 컴포넌트/토큰 추가 오케스트레이터 — monorepo 의 어느 디렉토리에서든 의도대로 라우팅.
|
|
428
|
+
*
|
|
429
|
+
* **v0.67+ 시그니처**: 옵션 객체 받음. `bin/sh-ui.mjs` 가 walk-up 으로 monorepo 루트를 찾아
|
|
430
|
+
* 호출하고, `add()` 를 in-process 로 호출 (npx subprocess 없음 — 외부 sh-ui-cli 설치 의존 X).
|
|
431
|
+
*
|
|
432
|
+
* 라우팅 (monorepo, v0.65+):
|
|
433
|
+
* - `tokens` → `packages/ui/ui-apps/ui-{hintApp 또는 app 또는 prompt 또는 단일}`
|
|
434
|
+
* - 그 외(컴포넌트/훅/lib) → `packages/ui/ui-core` 단일 SoT
|
|
435
|
+
*
|
|
436
|
+
* v0.64.x 호환: ui-core/sh-ui.config.json 부재 시 모든 add 는 ui-apps/ui-{app} 으로 폴백.
|
|
437
|
+
*
|
|
438
|
+
* @param {Object} options
|
|
439
|
+
* @param {string} [options.cwd] monorepo 루트. 기본 process.cwd()
|
|
440
|
+
* @param {string[]} options.names 추가할 이름들 (예: ['button', 'tokens'])
|
|
441
|
+
* @param {string|null} [options.app] 명시적 --app 플래그 (있으면 hintApp 무시)
|
|
442
|
+
* @param {string|null} [options.hintApp] walk-up 결과의 힌트 (apps/web → 'web')
|
|
443
|
+
* @param {boolean} [options.skipInstall]
|
|
444
|
+
* @param {boolean} [options.diffMode]
|
|
445
|
+
* @param {string} [options.onConflict] 'prompt' | 'keep' | 'overwrite'
|
|
446
|
+
*/
|
|
447
|
+
export async function addComponent(options = {}) {
|
|
448
|
+
const { add } = await import('../add.mjs');
|
|
429
449
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
450
|
+
const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
|
|
451
|
+
const names = (options.names ?? []).filter(Boolean);
|
|
452
|
+
const app = options.app ?? null;
|
|
453
|
+
const hintApp = options.hintApp ?? null;
|
|
454
|
+
const skipInstall = options.skipInstall === true;
|
|
455
|
+
const diffMode = options.diffMode === true;
|
|
456
|
+
const onConflict = options.onConflict ?? 'prompt';
|
|
457
|
+
|
|
458
|
+
if (names.length === 0) {
|
|
459
|
+
if (!process.stdin.isTTY) {
|
|
460
|
+
throw new Error('비대화형 환경(TTY 없음)에서는 추가할 이름이 필요합니다.');
|
|
434
461
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
462
|
+
const single = await input({ message: '컴포넌트 이름:' });
|
|
463
|
+
names.push(single);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const isMonorepo = await fs.pathExists(path.join(cwd, 'pnpm-workspace.yaml'));
|
|
467
|
+
if (!isMonorepo) {
|
|
468
|
+
// Standalone: cwd 자체가 sh-ui.config.json 보유 — 그대로 add 호출.
|
|
469
|
+
await add({ cwd, names, skipInstall, diffMode, onConflict });
|
|
438
470
|
return;
|
|
439
471
|
}
|
|
440
472
|
|
|
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
473
|
const uiCoreDir = path.join(cwd, 'packages', 'ui', 'ui-core');
|
|
446
474
|
const uiAppsDir = path.join(cwd, 'packages', 'ui', 'ui-apps');
|
|
447
475
|
const hasUiCore = await fs.pathExists(path.join(uiCoreDir, 'sh-ui.config.json'));
|
|
448
476
|
const hasUiApps = await fs.pathExists(uiAppsDir);
|
|
449
477
|
|
|
450
478
|
if (!hasUiCore && !hasUiApps) {
|
|
451
|
-
|
|
452
|
-
return;
|
|
479
|
+
throw new Error('packages/ui/ui-core 또는 packages/ui/ui-apps/ 디렉토리가 없습니다.');
|
|
453
480
|
}
|
|
454
481
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
482
|
+
// 이름을 라우팅 그룹별로 분리: tokens → ui-app, 그 외 → ui-core.
|
|
483
|
+
const tokenNames = names.filter((n) => n === 'tokens');
|
|
484
|
+
const componentNames = names.filter((n) => n !== 'tokens');
|
|
458
485
|
|
|
459
|
-
|
|
486
|
+
// ─── 비-tokens → ui-core (또는 v0.64.x fallback 시 ui-apps) ───
|
|
487
|
+
if (componentNames.length > 0) {
|
|
488
|
+
if (hasUiCore) {
|
|
489
|
+
if (app) {
|
|
490
|
+
console.log(
|
|
491
|
+
`ℹ️ v0.65+ 에서 컴포넌트는 packages/ui/ui-core 에 단일 emit (--app ${app} 무시).`,
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
console.log(`\n📦 packages/ui/ui-core ← ${componentNames.join(', ')}`);
|
|
495
|
+
await add({ cwd: uiCoreDir, names: componentNames, skipInstall, diffMode, onConflict });
|
|
496
|
+
} else {
|
|
497
|
+
// v0.64.x 호환: ui-core 없으니 모든 ui-app 에 컴포넌트 직접 emit (구 동작).
|
|
498
|
+
const targets = await pickUiAppTargets({ uiAppsDir, app, hintApp, kind: 'component' });
|
|
499
|
+
for (const pkg of targets) {
|
|
500
|
+
const pkgDir = path.join(uiAppsDir, pkg);
|
|
501
|
+
console.log(`\n📦 packages/ui/ui-apps/${pkg} ← ${componentNames.join(', ')}`);
|
|
502
|
+
await add({ cwd: pkgDir, names: componentNames, skipInstall, diffMode, onConflict });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
460
506
|
|
|
461
|
-
// ───
|
|
462
|
-
if (
|
|
463
|
-
if (
|
|
464
|
-
|
|
465
|
-
`ℹ️ v0.65+ 에서 컴포넌트는 packages/ui/ui-core 에 단일 emit 됩니다 (--app ${appName} 무시).`,
|
|
466
|
-
);
|
|
507
|
+
// ─── tokens → ui-app(s) ───
|
|
508
|
+
if (tokenNames.length > 0) {
|
|
509
|
+
if (!hasUiApps) {
|
|
510
|
+
throw new Error('tokens 추가에는 packages/ui/ui-apps/ 가 필요합니다.');
|
|
467
511
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
console.log(
|
|
472
|
-
|
|
473
|
-
console.log(`❌ packages/ui/ui-core 실패: ${error.message}`);
|
|
512
|
+
const targets = await pickUiAppTargets({ uiAppsDir, app, hintApp, kind: 'tokens' });
|
|
513
|
+
for (const pkg of targets) {
|
|
514
|
+
const pkgDir = path.join(uiAppsDir, pkg);
|
|
515
|
+
console.log(`\n📦 packages/ui/ui-apps/${pkg} ← tokens`);
|
|
516
|
+
await add({ cwd: pkgDir, names: tokenNames, skipInstall, diffMode, onConflict });
|
|
474
517
|
}
|
|
475
|
-
return;
|
|
476
518
|
}
|
|
477
519
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
console.log('❌ packages/ui/ui-apps/ 디렉토리가 없습니다.');
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
520
|
+
console.log('\n✅ 추가 완료');
|
|
521
|
+
}
|
|
483
522
|
|
|
523
|
+
/**
|
|
524
|
+
* monorepo 의 ui-app 패키지 후보를 정해 1+ 개 반환.
|
|
525
|
+
*
|
|
526
|
+
* 우선순위:
|
|
527
|
+
* 1. 명시적 `--app` 플래그
|
|
528
|
+
* 2. walk-up 결과의 hintApp (apps/<name>/ 또는 ui-apps/ui-<name>/ 컨텍스트)
|
|
529
|
+
* 3. 단일 ui-app 만 존재 → 자동 선택
|
|
530
|
+
* 4. TTY → 사용자에게 select prompt
|
|
531
|
+
* 5. 비대화형 + 다중 ui-app → tokens 는 'all', 컴포넌트(v0.64.x fallback)는 throw
|
|
532
|
+
*/
|
|
533
|
+
async function pickUiAppTargets({ uiAppsDir, app, hintApp, kind }) {
|
|
484
534
|
const entries = await fs.readdir(uiAppsDir, { withFileTypes: true });
|
|
485
535
|
const uiPackages = entries
|
|
486
536
|
.filter((e) => e.isDirectory() && e.name.startsWith('ui-') && e.name !== 'ui-app-template')
|
|
487
537
|
.map((e) => e.name);
|
|
488
538
|
|
|
489
539
|
if (uiPackages.length === 0) {
|
|
490
|
-
|
|
491
|
-
return;
|
|
540
|
+
throw new Error('packages/ui/ui-apps/ui-* 패키지가 없습니다.');
|
|
492
541
|
}
|
|
493
542
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
const pkgName = `ui-${appName}`;
|
|
543
|
+
if (app) {
|
|
544
|
+
const pkgName = `ui-${app}`;
|
|
497
545
|
if (!uiPackages.includes(pkgName)) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
546
|
+
throw new Error(
|
|
547
|
+
`packages/ui/ui-apps/${pkgName} 이 존재하지 않습니다. 사용 가능: ${uiPackages.join(', ')}`,
|
|
548
|
+
);
|
|
501
549
|
}
|
|
502
|
-
|
|
503
|
-
} else if (uiPackages.length === 1) {
|
|
504
|
-
targets = uiPackages;
|
|
505
|
-
} else {
|
|
506
|
-
const choice = await select({
|
|
507
|
-
message: isTokens ? '어느 ui 패키지에 토큰을 추가할까요?' : '어디에 추가할까요?',
|
|
508
|
-
choices: [
|
|
509
|
-
{ name: '모든 ui 패키지', value: 'all' },
|
|
510
|
-
...uiPackages.map((name) => ({ name: `packages/ui/ui-apps/${name}`, value: name })),
|
|
511
|
-
],
|
|
512
|
-
});
|
|
513
|
-
targets = choice === 'all' ? uiPackages : [choice];
|
|
550
|
+
return [pkgName];
|
|
514
551
|
}
|
|
515
552
|
|
|
516
|
-
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
553
|
+
if (hintApp) {
|
|
554
|
+
const pkgName = `ui-${hintApp}`;
|
|
555
|
+
if (uiPackages.includes(pkgName)) {
|
|
556
|
+
return [pkgName];
|
|
557
|
+
}
|
|
558
|
+
// hintApp 이 가리키는 앱의 ui-패키지가 없으면 일반 흐름으로 떨어짐 (드문 케이스).
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (uiPackages.length === 1) {
|
|
562
|
+
return uiPackages;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (!process.stdin.isTTY) {
|
|
566
|
+
if (kind === 'tokens') {
|
|
567
|
+
// 비대화형 + 다중 ui-app + tokens: 모든 앱에 적용 (가장 안전한 기본값).
|
|
568
|
+
return uiPackages;
|
|
524
569
|
}
|
|
570
|
+
throw new Error(
|
|
571
|
+
`여러 ui-app 후보 (${uiPackages.join(', ')}) — 비대화형 환경에서는 --app 으로 명시하세요.`,
|
|
572
|
+
);
|
|
525
573
|
}
|
|
526
574
|
|
|
527
|
-
|
|
575
|
+
const choice = await select({
|
|
576
|
+
message: kind === 'tokens' ? '어느 ui 패키지에 토큰을 추가할까요?' : '어디에 추가할까요?',
|
|
577
|
+
choices: [
|
|
578
|
+
{ name: '모든 ui 패키지', value: 'all' },
|
|
579
|
+
...uiPackages.map((name) => ({ name: `packages/ui/ui-apps/${name}`, value: name })),
|
|
580
|
+
],
|
|
581
|
+
});
|
|
582
|
+
return choice === 'all' ? uiPackages : [choice];
|
|
528
583
|
}
|
|
529
584
|
|
|
530
585
|
// ─── Generators ───
|
package/src/create/index.mjs
CHANGED
|
@@ -75,7 +75,11 @@ export async function runCreate(rest) {
|
|
|
75
75
|
css: flags.css,
|
|
76
76
|
});
|
|
77
77
|
} else if (command === 'add-component') {
|
|
78
|
-
|
|
78
|
+
// 호환 별칭 — 신규 진입점은 `sh-ui add <name>` (bin/sh-ui.mjs 가 walk-up 으로 라우팅).
|
|
79
|
+
await addComponent({
|
|
80
|
+
names: positional.filter(Boolean),
|
|
81
|
+
app: flags.app ?? null,
|
|
82
|
+
});
|
|
79
83
|
} else {
|
|
80
84
|
await createProject({
|
|
81
85
|
name: positional[0],
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// sh-ui 의 동작 컨텍스트 해석 — `sh-ui add` 등이 cwd 부터 부모 트리를 walk up 해
|
|
2
|
+
// **standalone (sh-ui.config.json 발견)** 또는 **monorepo (pnpm-workspace.yaml 발견)** 컨텍스트 결정.
|
|
3
|
+
//
|
|
4
|
+
// monorepo 에서 사용자가 어디서 명령을 실행했는지 (apps/web, packages/ui/ui-apps/ui-admin 등)
|
|
5
|
+
// 까지 추출해 hintApp 으로 넘김 — `sh-ui add tokens` 가 어느 ui-app 으로 라우팅될지의 기본값.
|
|
6
|
+
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} startDir - 검색 시작점 (보통 process.cwd())
|
|
12
|
+
* @returns {{kind: 'config', root: string} | {kind: 'monorepo', root: string, hintApp: string|null} | null}
|
|
13
|
+
*/
|
|
14
|
+
export function findShUiContext(startDir) {
|
|
15
|
+
let dir = path.resolve(startDir);
|
|
16
|
+
while (true) {
|
|
17
|
+
if (existsSync(path.join(dir, "sh-ui.config.json"))) {
|
|
18
|
+
return { kind: "config", root: dir };
|
|
19
|
+
}
|
|
20
|
+
if (existsSync(path.join(dir, "pnpm-workspace.yaml"))) {
|
|
21
|
+
const hintApp = extractHintApp(dir, startDir);
|
|
22
|
+
return { kind: "monorepo", root: dir, hintApp };
|
|
23
|
+
}
|
|
24
|
+
const parent = path.dirname(dir);
|
|
25
|
+
if (parent === dir) return null;
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* monorepo 루트가 정해진 뒤 startDir 의 상대 경로에서 어느 앱 컨텍스트인지 추출.
|
|
32
|
+
*
|
|
33
|
+
* 매칭 패턴 (모노레포 루트 기준):
|
|
34
|
+
* - `apps/<name>/...` → <name>
|
|
35
|
+
* - `packages/ui/ui-apps/ui-<name>/...` → <name>
|
|
36
|
+
*
|
|
37
|
+
* 그 외(루트 자체, packages/ui/ui-core 등)는 null — 호출자가 prompt 또는 fallback.
|
|
38
|
+
*/
|
|
39
|
+
function extractHintApp(monorepoRoot, startDir) {
|
|
40
|
+
const rel = path.relative(monorepoRoot, path.resolve(startDir));
|
|
41
|
+
if (!rel || rel.startsWith("..")) return null;
|
|
42
|
+
const parts = rel.split(path.sep);
|
|
43
|
+
if (parts[0] === "apps" && parts[1]) {
|
|
44
|
+
return parts[1];
|
|
45
|
+
}
|
|
46
|
+
if (
|
|
47
|
+
parts[0] === "packages" &&
|
|
48
|
+
parts[1] === "ui" &&
|
|
49
|
+
parts[2] === "ui-apps" &&
|
|
50
|
+
parts[3] &&
|
|
51
|
+
parts[3].startsWith("ui-")
|
|
52
|
+
) {
|
|
53
|
+
return parts[3].slice(3);
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|