@t3q-ui/design-system 0.0.0-stage → 1.56.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/CHANGELOG.md +1977 -0
- package/LICENSE.md +88 -0
- package/README.md +464 -2
- package/cli/doctor.mjs +196 -0
- package/cli/lib.mjs +425 -0
- package/cli/setup.mjs +181 -0
- package/cli/t3q-design.mjs +78 -0
- package/dist/chunk-IPF3K4BS.js +4274 -0
- package/dist/chunk-IPF3K4BS.js.map +1 -0
- package/dist/chunk-MHAO45AL.cjs +4427 -0
- package/dist/chunk-MHAO45AL.cjs.map +1 -0
- package/dist/fonts.css +12 -0
- package/dist/index-Ci1Wh5sA.d.cts +253 -0
- package/dist/index-Ci1Wh5sA.d.ts +253 -0
- package/dist/index.cjs +10998 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +332 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +2554 -0
- package/dist/index.d.ts +2554 -0
- package/dist/index.js +10354 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns/index.cjs +885 -0
- package/dist/patterns/index.cjs.map +1 -0
- package/dist/patterns/index.css +265 -0
- package/dist/patterns/index.css.map +1 -0
- package/dist/patterns/index.d.cts +373 -0
- package/dist/patterns/index.d.ts +373 -0
- package/dist/patterns/index.js +873 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/styles.css +2014 -0
- package/package.json +212 -4
- package/skills/tds/SKILL.md +91 -0
- package/skills/tds/examples/dashboard-page.tsx +344 -0
- package/skills/tds/examples/detail-page.tsx +388 -0
- package/skills/tds/examples/form-page.tsx +460 -0
- package/skills/tds/examples/list-page.tsx +275 -0
- package/skills/tds/reference/components.md +1424 -0
- package/skills/tds/rules.md +28 -0
package/cli/setup.mjs
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* t3q-design setup — 소비 레포를 AI 작업용으로 준비한다 (README "빠른 시작" 4단계).
|
|
3
|
+
* 전제: `pnpm add @t3q-ui/design-system` 이 끝나 있다 (이 CLI 가 그 패키지 안에 있으므로).
|
|
4
|
+
* `.npmrc`·MCP 항목은 `registry`·`mcpUrl` 을 줄 때만 쓴다 — 주소를 패키지에 두지 않는다 (사내 절차: docs/INTERNAL_SETUP.md).
|
|
5
|
+
* 모든 쓰기는 "달라졌을 때만" — 두 번 돌리면 아무것도 안 바뀐다.
|
|
6
|
+
*/
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
CODEX_CONFIG,
|
|
11
|
+
CODEX_SKILL_DEST,
|
|
12
|
+
codexMcpBody,
|
|
13
|
+
codexTableOutsideBlock,
|
|
14
|
+
copySkill,
|
|
15
|
+
CSS_IMPORTS,
|
|
16
|
+
ensureImportLine,
|
|
17
|
+
ensureNpmrc,
|
|
18
|
+
findCss,
|
|
19
|
+
insertCssImports,
|
|
20
|
+
managedBlock,
|
|
21
|
+
MCP_SERVER,
|
|
22
|
+
mergeMcpJson,
|
|
23
|
+
npmrcLines,
|
|
24
|
+
ownVersion,
|
|
25
|
+
PKG_NAME,
|
|
26
|
+
readInstalledVersion,
|
|
27
|
+
readText,
|
|
28
|
+
RULES_FILE,
|
|
29
|
+
SKILL_DEST,
|
|
30
|
+
skillStale,
|
|
31
|
+
upsertManagedBlock,
|
|
32
|
+
writeIfChanged,
|
|
33
|
+
} from './lib.mjs'
|
|
34
|
+
|
|
35
|
+
/** @returns {Promise<Array<{ status: 'ok'|'warn'|'fail'|'info', msg: string, fix?: string }>>} */
|
|
36
|
+
export async function setup(cwd, { registry, mcpUrl } = {}) {
|
|
37
|
+
const results = []
|
|
38
|
+
const add = (status, msg, fix) => results.push(fix ? { status, msg, fix } : { status, msg })
|
|
39
|
+
const file = (rel) => path.join(cwd, rel)
|
|
40
|
+
const version = ownVersion()
|
|
41
|
+
|
|
42
|
+
// 0. 설치 확인 (경고만 — 파일은 그래도 써 둔다)
|
|
43
|
+
const installed = readInstalledVersion(cwd)
|
|
44
|
+
if (installed) add('ok', `${PKG_NAME} ${installed}`)
|
|
45
|
+
else add('warn', `${PKG_NAME} 미설치`, `pnpm add ${PKG_NAME}`)
|
|
46
|
+
|
|
47
|
+
// 1. .npmrc — --registry 를 줄 때만
|
|
48
|
+
if (!registry) add('info', '.npmrc 변경 없음 (--registry 를 주면 사내 레지스트리 줄을 기록)')
|
|
49
|
+
else {
|
|
50
|
+
const before = readText(file('.npmrc'))
|
|
51
|
+
const { text, conflict } = ensureNpmrc(before, registry)
|
|
52
|
+
if (conflict)
|
|
53
|
+
add(
|
|
54
|
+
'warn',
|
|
55
|
+
'.npmrc 에 다른 @t3q-ui:registry 가 있어 손대지 않음',
|
|
56
|
+
`아래 2줄로 맞춘다:\n${npmrcLines(registry).join('\n')}`,
|
|
57
|
+
)
|
|
58
|
+
else if (text === before) add('ok', '.npmrc 이미 최신')
|
|
59
|
+
else {
|
|
60
|
+
writeIfChanged(file('.npmrc'), text)
|
|
61
|
+
add('ok', '.npmrc 갱신 (레지스트리 2줄)')
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. CSS — 진입 CSS 가 정확히 1개일 때만 자동 삽입
|
|
66
|
+
{
|
|
67
|
+
const { v4, v3 } = findCss(cwd)
|
|
68
|
+
const paste = `진입 CSS 의 @import 'tailwindcss' 바로 아래에 추가:\n${CSS_IMPORTS.join('\n')}`
|
|
69
|
+
if (v4.length === 1) {
|
|
70
|
+
const css = readText(file(v4[0]))
|
|
71
|
+
const next = insertCssImports(css)
|
|
72
|
+
if (next === css) add('ok', `CSS ${v4[0]} 이미 최신`)
|
|
73
|
+
else {
|
|
74
|
+
writeIfChanged(file(v4[0]), next)
|
|
75
|
+
const added =
|
|
76
|
+
next.includes(CSS_IMPORTS[1]) && !css.includes(CSS_IMPORTS[1])
|
|
77
|
+
? 'styles.css·fonts.css'
|
|
78
|
+
: 'styles.css'
|
|
79
|
+
add('ok', `CSS ${v4[0]} 갱신 (${added} import 추가)`)
|
|
80
|
+
}
|
|
81
|
+
} else if (v4.length === 0 && v3.length > 0) {
|
|
82
|
+
add(
|
|
83
|
+
'fail',
|
|
84
|
+
`Tailwind v3 (@tailwind base: ${v3.join(', ')}) — TDS 는 Tailwind v4 가 필요`,
|
|
85
|
+
'README "Tailwind 설정" 을 따라 v4 로 올린 뒤 다시 setup',
|
|
86
|
+
)
|
|
87
|
+
} else if (v4.length === 0) {
|
|
88
|
+
add('warn', "@import 'tailwindcss' 가 있는 CSS 파일 없음", paste)
|
|
89
|
+
} else {
|
|
90
|
+
add('warn', `진입 CSS 후보가 ${v4.length}개 (${v4.join(', ')}) — 자동 수정 안 함`, paste)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 3. .mcp.json — --mcp-url 을 줄 때만
|
|
95
|
+
if (!mcpUrl) add('info', '.mcp.json 변경 없음 (--mcp-url 을 주면 MCP 항목을 기록) — Skill reference 로 동작')
|
|
96
|
+
else {
|
|
97
|
+
const before = readText(file('.mcp.json'))
|
|
98
|
+
try {
|
|
99
|
+
const next = mergeMcpJson(before, mcpUrl)
|
|
100
|
+
if (next === before) add('ok', `.mcp.json ${MCP_SERVER} 이미 최신`)
|
|
101
|
+
else {
|
|
102
|
+
writeIfChanged(file('.mcp.json'), next)
|
|
103
|
+
add('ok', `.mcp.json ${MCP_SERVER} → ${mcpUrl}`)
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
add(
|
|
107
|
+
'warn',
|
|
108
|
+
'.mcp.json 파싱 실패 — 손대지 않음',
|
|
109
|
+
`mcpServers 에 직접 추가:\n${JSON.stringify({ [MCP_SERVER]: { type: 'http', url: mcpUrl } }, null, 2)}`,
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 4. AGENTS.md 관리 블록
|
|
115
|
+
{
|
|
116
|
+
const rules = readText(RULES_FILE)
|
|
117
|
+
const before = readText(file('AGENTS.md'))
|
|
118
|
+
const had = managedBlock(before)
|
|
119
|
+
const next = upsertManagedBlock(before, rules, version)
|
|
120
|
+
if (next === before) add('ok', 'AGENTS.md TDS 블록 이미 최신')
|
|
121
|
+
else {
|
|
122
|
+
writeIfChanged(file('AGENTS.md'), next)
|
|
123
|
+
add('ok', had ? 'AGENTS.md TDS 블록 갱신' : 'AGENTS.md TDS 블록 추가')
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 5. CLAUDE.md → @AGENTS.md
|
|
128
|
+
{
|
|
129
|
+
const before = readText(file('CLAUDE.md'))
|
|
130
|
+
const next = ensureImportLine(before)
|
|
131
|
+
if (next === before) add('ok', 'CLAUDE.md @AGENTS.md 이미 있음')
|
|
132
|
+
else {
|
|
133
|
+
writeIfChanged(file('CLAUDE.md'), next)
|
|
134
|
+
add('ok', before == null ? 'CLAUDE.md 생성 (@AGENTS.md)' : 'CLAUDE.md 에 @AGENTS.md 추가')
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 6. Skill 복사
|
|
139
|
+
{
|
|
140
|
+
const dest = file(SKILL_DEST)
|
|
141
|
+
const stale = skillStale(dest)
|
|
142
|
+
copySkill(dest)
|
|
143
|
+
add('ok', stale ? `${SKILL_DEST}/ 복사 (/tds)` : `${SKILL_DEST}/ 이미 최신`)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 6b. Codex — 스킬은 .agents/skills/tds, MCP 는 프로젝트 .codex/config.toml 의 관리 블록(# tds:start … # tds:end).
|
|
147
|
+
// Codex 는 .mcp.json·.claude/skills 를 읽지 않는다(2026-10-27 홀드아웃 13: Codex 9 run 중 7 run 이 MCP 대신 d.ts 만 읽음)
|
|
148
|
+
{
|
|
149
|
+
const dest = file(CODEX_SKILL_DEST)
|
|
150
|
+
const stale = skillStale(dest)
|
|
151
|
+
copySkill(dest)
|
|
152
|
+
add('ok', stale ? `${CODEX_SKILL_DEST}/ 복사 (Codex 스킬)` : `${CODEX_SKILL_DEST}/ 이미 최신`)
|
|
153
|
+
// MCP 주소가 없으면 .codex/config.toml 도 건드리지 않는다
|
|
154
|
+
const before = readText(file(CODEX_CONFIG))
|
|
155
|
+
if (!mcpUrl) {
|
|
156
|
+
add('info', `${CODEX_CONFIG} 변경 없음 (--mcp-url 없음)`)
|
|
157
|
+
} else if (codexTableOutsideBlock(before)) {
|
|
158
|
+
add(
|
|
159
|
+
'warn',
|
|
160
|
+
`${CODEX_CONFIG} 에 ${MCP_SERVER} 테이블이 이미 있어 손대지 않음`,
|
|
161
|
+
`그 테이블의 url 이 "${mcpUrl}" 인지 확인`,
|
|
162
|
+
)
|
|
163
|
+
} else {
|
|
164
|
+
const next = upsertManagedBlock(before, codexMcpBody(mcpUrl), version, 'toml')
|
|
165
|
+
if (next === before) add('ok', `${CODEX_CONFIG} ${MCP_SERVER} 이미 최신`)
|
|
166
|
+
else {
|
|
167
|
+
writeIfChanged(file(CODEX_CONFIG), next)
|
|
168
|
+
add('ok', `${CODEX_CONFIG} ${MCP_SERVER} → ${mcpUrl}`)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 7. 안내
|
|
174
|
+
add('info', 'Claude Code 는 이 프로젝트의 .mcp.json 서버를 첫 실행 때 승인하라고 묻는다 → 승인')
|
|
175
|
+
add(
|
|
176
|
+
'info',
|
|
177
|
+
'Codex 는 규칙(AGENTS.md)·.agents/skills/tds·.codex/config.toml 을 읽는다 — Codex 에서 이 프로젝트를 trust 해야 .codex/config.toml 이 적용된다. Cursor·Copilot 은 규칙·Skill(.claude/skills) 을 그대로 읽고 MCP 는 .cursor/mcp.json, .vscode/mcp.json(키 servers) 에 같은 URL 을 등록',
|
|
178
|
+
)
|
|
179
|
+
add('info', '점검: t3q-design doctor')
|
|
180
|
+
return results
|
|
181
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* t3q-design — @t3q-ui/design-system 소비 레포 설치·점검 CLI (로드맵 §7).
|
|
4
|
+
* t3q-design setup [--registry <url>] [--mcp-url <url>] [--cwd <dir>] AI 작업용 파일을 만들거나 갱신한다
|
|
5
|
+
* t3q-design doctor [--cwd <dir>] 상태를 검사하고 조치를 알려 준다
|
|
6
|
+
* 이 파일은 출력과 종료 코드만 맡는다. 동작은 setup.mjs / doctor.mjs, 검사는 cli.test.mjs.
|
|
7
|
+
*/
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
|
|
10
|
+
import { doctor } from './doctor.mjs'
|
|
11
|
+
import { ownVersion, PKG_NAME } from './lib.mjs'
|
|
12
|
+
import { setup } from './setup.mjs'
|
|
13
|
+
|
|
14
|
+
const argv = process.argv.slice(2)
|
|
15
|
+
const arg = (name, def) => {
|
|
16
|
+
const i = argv.indexOf(name)
|
|
17
|
+
return i >= 0 && argv[i + 1] != null ? argv[i + 1] : def
|
|
18
|
+
}
|
|
19
|
+
/** 끝 슬래시를 보장한다 — 레지스트리 URL 은 `${registry}-/package/…` 로 이어 붙인다 */
|
|
20
|
+
const normRegistry = (r) => (r ? r.replace(/\/?$/, '/') : r)
|
|
21
|
+
const flag = (name) => argv.includes(name)
|
|
22
|
+
const cmd = argv.find((a) => !a.startsWith('-'))
|
|
23
|
+
|
|
24
|
+
const HELP = `t3q-design — ${PKG_NAME} 설치·점검
|
|
25
|
+
|
|
26
|
+
사용:
|
|
27
|
+
t3q-design setup [--registry <url>] [--mcp-url <url>] [--cwd <dir>]
|
|
28
|
+
t3q-design doctor [--cwd <dir>]
|
|
29
|
+
|
|
30
|
+
setup CSS import · AGENTS.md 블록 · CLAUDE.md · .claude/skills/tds 를 만들거나 갱신. 플래그를 줄 때만 .npmrc(--registry)·.mcp.json·.codex/config.toml(--mcp-url) 도 기록
|
|
31
|
+
doctor Node · 패키지/peer · CSS · Skill · 규칙을 검사 (실패 시 종료 코드 1). .npmrc 에 @t3q-ui:registry 줄이 있으면 토큰·레지스트리도, .mcp.json 에 항목이 있으면 MCP 도 점검
|
|
32
|
+
|
|
33
|
+
--registry 사내 npm 레지스트리 주소 (사내 설치 전용 — docs/INTERNAL_SETUP.md)
|
|
34
|
+
--mcp-url Storybook MCP 주소 (예: 로컬 Storybook http://localhost:6006/mcp). 없으면 MCP 항목을 쓰지 않는다
|
|
35
|
+
--cwd 대상 레포 (기본: 현재 디렉터리)
|
|
36
|
+
`
|
|
37
|
+
|
|
38
|
+
const ICON = { ok: '✓', warn: '!', fail: '✗', info: '·' }
|
|
39
|
+
|
|
40
|
+
function print(results) {
|
|
41
|
+
for (const r of results) {
|
|
42
|
+
console.log(`${ICON[r.status]} ${r.msg}`)
|
|
43
|
+
if (r.fix) for (const line of r.fix.split('\n')) console.log(` → ${line}`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function main() {
|
|
48
|
+
if (flag('--help') || flag('-h') || !cmd) {
|
|
49
|
+
console.log(HELP)
|
|
50
|
+
return flag('--help') || flag('-h') ? 0 : 1
|
|
51
|
+
}
|
|
52
|
+
const cwd = path.resolve(arg('--cwd', process.cwd()))
|
|
53
|
+
if (cmd === 'setup') {
|
|
54
|
+
console.log(`t3q-design setup (${PKG_NAME} ${ownVersion()}) — ${cwd}\n`)
|
|
55
|
+
const results = await setup(cwd, { registry: normRegistry(arg('--registry')), mcpUrl: arg('--mcp-url') })
|
|
56
|
+
print(results)
|
|
57
|
+
return results.some((r) => r.status === 'fail') ? 1 : 0
|
|
58
|
+
}
|
|
59
|
+
if (cmd === 'doctor') {
|
|
60
|
+
console.log(`t3q-design doctor — ${cwd}\n`)
|
|
61
|
+
const results = await doctor(cwd)
|
|
62
|
+
print(results)
|
|
63
|
+
const fails = results.filter((r) => r.status === 'fail').length
|
|
64
|
+
console.log(fails ? `\n✗ ${fails}개 실패` : '\n✓ 준비 완료')
|
|
65
|
+
return fails ? 1 : 0
|
|
66
|
+
}
|
|
67
|
+
console.error(`알 수 없는 명령: ${cmd}\n`)
|
|
68
|
+
console.log(HELP)
|
|
69
|
+
return 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
main().then(
|
|
73
|
+
(code) => process.exit(code),
|
|
74
|
+
(e) => {
|
|
75
|
+
console.error(`✗ ${e?.stack ?? e}`)
|
|
76
|
+
process.exit(1)
|
|
77
|
+
},
|
|
78
|
+
)
|