sparkdesign 0.4.5 → 0.4.6
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 +74 -22
- package/cli/dist/commands/add.js +22 -12
- package/cli/dist/commands/diff.js +8 -4
- package/cli/dist/commands/init.js +84 -9
- package/cli/dist/commands/list.js +8 -4
- package/cli/dist/index.js +9 -6
- package/cli/dist/utils/config.js +16 -8
- package/cli/dist/utils/package-manager.js +75 -0
- package/cli/dist/utils/registry.js +8 -4
- package/cli/dist/utils/tokens.js +33 -25
- package/cli/dist/utils/transform.js +9 -5
- package/cli/dist/utils/tsconfig.js +182 -0
- package/cli/registry/AGENTS.md +18 -0
- package/cli/registry/__tests__/chat/thinking-indicator.test.tsx +2 -2
- package/cli/registry/chat/chat-input/chat-input-folder-selector.tsx +1 -1
- package/cli/registry/chat/chat-input/folder-permission-dialog.tsx +2 -2
- package/cli/registry/chat/image-generating.tsx +1 -1
- package/cli/registry/chat/response/context.tsx +1 -1
- package/cli/registry/chat/thinking-indicator.tsx +1 -1
- package/cli/registry/tokens/index.css +8 -5
- package/cli/registry/tokens/theme-base.css +235 -0
- package/{dist/tokens/themes/dark-qoder.css → cli/registry/tokens/themes/dark-mint.css} +1 -1
- package/cli/registry/tokens/themes/dark-parchment.css +104 -103
- package/{dist/tokens/themes/light-qoder.css → cli/registry/tokens/themes/light-mint.css} +1 -1
- package/cli/registry/tokens/themes/light-parchment.css +103 -102
- package/dist/registry/basic/alert-dialog.d.ts +7 -5
- package/dist/registry/basic/avatar.d.ts +7 -5
- package/dist/registry/basic/collapse.d.ts +7 -5
- package/dist/registry/basic/collapsible-card.d.ts +7 -6
- package/dist/registry/basic/collapsible.d.ts +7 -5
- package/dist/registry/basic/dropdown-menu.d.ts +7 -5
- package/dist/registry/basic/icons-inline.d.ts +7 -5
- package/dist/registry/basic/kbd.d.ts +7 -5
- package/dist/registry/basic/pagination.d.ts +7 -5
- package/dist/registry/basic/progress.d.ts +7 -5
- package/dist/registry/basic/radio-group.d.ts +7 -5
- package/dist/registry/basic/resizable.d.ts +7 -5
- package/dist/registry/basic/select.d.ts +7 -5
- package/dist/registry/basic/slider.d.ts +7 -5
- package/dist/registry/basic/sonner.d.ts +7 -5
- package/dist/registry/basic/switch.d.ts +7 -5
- package/dist/registry/basic/tabs.d.ts +7 -5
- package/dist/registry/basic/tag.d.ts +7 -5
- package/dist/registry/basic/theme-from-document.d.ts +7 -5
- package/dist/registry/basic/tooltip.d.ts +7 -5
- package/dist/registry/basic/typography.d.ts +7 -5
- package/dist/registry/chat/ask-user-part.d.ts +9 -3
- package/dist/registry/chat/browser-action-part.d.ts +9 -3
- package/dist/registry/chat/chat-input/compound.d.ts +7 -5
- package/dist/registry/chat/chat-input/context.d.ts +7 -5
- package/dist/registry/chat/chat-input/index.d.ts +7 -5
- package/dist/registry/chat/chat-input/types.d.ts +9 -3
- package/dist/registry/chat/chat-input/useAutoResizeTextarea.d.ts +9 -3
- package/dist/registry/chat/code-block-part.d.ts +9 -3
- package/dist/registry/chat/file-attachment.d.ts +7 -5
- package/dist/registry/chat/file-review-part.d.ts +9 -4
- package/dist/registry/chat/generated-images-grid.d.ts +9 -3
- package/dist/registry/chat/generation-status-bar.d.ts +8 -4
- package/dist/registry/chat/hint-banner.d.ts +9 -3
- package/dist/registry/chat/mermaid-part.d.ts +9 -3
- package/dist/registry/chat/plan-part.d.ts +9 -3
- package/dist/registry/chat/reasoning-step/index.d.ts +8 -6
- package/dist/registry/chat/reasoning-step/types.d.ts +9 -3
- package/dist/registry/chat/related-prompts.d.ts +9 -3
- package/dist/registry/chat/response/index.d.ts +8 -6
- package/dist/registry/chat/response/types.d.ts +9 -3
- package/dist/registry/chat/task-part.d.ts +9 -3
- package/dist/registry/chat/terminal-code-block-part.d.ts +9 -3
- package/dist/registry/chat/user-question/UserQuestionCard.d.ts +9 -3
- package/dist/registry/chat/user-question/UserQuestionFooter.d.ts +9 -3
- package/dist/registry/chat/user-question/UserQuestionHeader.d.ts +9 -3
- package/dist/registry/chat/user-question/types.d.ts +8 -5
- package/dist/registry/lib/file-icon-maps.d.ts +7 -7
- package/dist/registry/lib/utils.d.ts +8 -6
- package/dist/spark-design.cjs.js +6 -6
- package/dist/spark-design.es.js +10 -10
- package/dist/sparkdesign.css +2 -0
- package/dist/src/components/basic/AlertDialog/index.d.ts +7 -5
- package/dist/src/components/basic/Avatar/index.d.ts +9 -3
- package/dist/src/components/basic/Button/index.d.ts +9 -3
- package/dist/src/components/basic/Collapse/index.d.ts +7 -4
- package/dist/src/components/basic/Collapsible/index.d.ts +9 -4
- package/dist/src/components/basic/CollapsibleCard/index.d.ts +9 -3
- package/dist/src/components/basic/CollapsibleSection/index.d.ts +7 -7
- package/dist/src/components/basic/DropdownMenu/index.d.ts +7 -5
- package/dist/src/components/basic/EllipsisText/index.d.ts +7 -15
- package/dist/src/components/basic/IconButton/index.d.ts +9 -3
- package/dist/src/components/basic/Kbd/index.d.ts +9 -3
- package/dist/src/components/basic/OptionList/index.d.ts +9 -3
- package/dist/src/components/basic/Pagination/index.d.ts +9 -3
- package/dist/src/components/basic/Progress/index.d.ts +9 -3
- package/dist/src/components/basic/RadioGroup/index.d.ts +9 -3
- package/dist/src/components/basic/Resizable/index.d.ts +9 -3
- package/dist/src/components/basic/Scrollbar/index.d.ts +9 -3
- package/dist/src/components/basic/Select/index.d.ts +7 -4
- package/dist/src/components/basic/ShimmeringText/index.d.ts +9 -3
- package/dist/src/components/basic/Skeleton/index.d.ts +9 -3
- package/dist/src/components/basic/Slider/index.d.ts +9 -3
- package/dist/src/components/basic/Spinner/index.d.ts +9 -3
- package/dist/src/components/basic/Switch/index.d.ts +8 -5
- package/dist/src/components/basic/Table/index.d.ts +9 -3
- package/dist/src/components/basic/Tabs/index.d.ts +9 -3
- package/dist/src/components/basic/Tag/index.d.ts +7 -4
- package/dist/src/components/basic/Toggle/index.d.ts +9 -3
- package/dist/src/components/basic/Tooltip/index.d.ts +7 -4
- package/dist/src/components/basic/Typography/index.d.ts +9 -3
- package/dist/src/components/chat/GeneratedImagesGrid/index.d.ts +9 -3
- package/dist/src/components/chat/GenerationStatusBar/index.d.ts +9 -3
- package/dist/src/components/chat/Markdown/demo-content.d.ts +1 -1
- package/dist/src/components/chat/Markdown/index.d.ts +9 -3
- package/dist/src/components/chat/Response/StreamingMarkdownBlock.d.ts +9 -3
- package/dist/src/components/chat/Response/index.d.ts +8 -6
- package/dist/src/components/chat/UserMessage/index.d.ts +9 -3
- package/dist/src/components/index.d.ts +7 -9
- package/dist/src/icons/context.d.ts +7 -6
- package/dist/src/icons/types.d.ts +7 -5
- package/dist/src/lib/ThemeStyleContext.d.ts +9 -9
- package/dist/src/lib/file-icon.d.ts +7 -6
- package/dist/src/lib/i18n.d.ts +7 -6
- package/dist/src/lib/index.d.ts +9 -3
- package/dist/src/lib/utils.d.ts +7 -5
- package/dist/theme-base.css +7 -8
- package/dist/theme.css +2 -2
- package/dist/themes/{dark-qoder.css → dark-mint.css} +1 -1
- package/dist/themes/{light-qoder.css → light-mint.css} +1 -1
- package/dist/tokens/AGENTS.md +47 -0
- package/dist/tokens/index.css +10 -19
- package/dist/tokens/theme-base.css +7 -8
- package/dist/tokens/theme.css +2 -2
- package/dist/tokens/themes/dark-mint.css +133 -0
- package/dist/tokens/themes/light-mint.css +132 -0
- package/package.json +11 -5
- package/cli/registry/tokens/themes/dark-qoder.css +0 -132
- package/cli/registry/tokens/themes/light-qoder.css +0 -131
- package/dist/qoder-design.css +0 -2
- package/dist/tokens/CLAUDE.md +0 -305
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
现代化 React 设计系统,支持双维度主题(颜色 theme + 布局 scale),5 种内置布局风格,提供 Basic 基础组件与 Chat 对话流组件,可扩展颜色主题。
|
|
4
4
|
|
|
5
|
-
**当前版本**:`0.4.5`([
|
|
5
|
+
**当前版本**:`0.4.5`([CLI 快速上手](docs/getting-started-cli.md) / [NPM 快速上手](docs/getting-started.md))
|
|
6
6
|
|
|
7
|
-
> 🌟 **纯新手?** 查看 [
|
|
7
|
+
> 🌟 **纯新手?** 查看 [CLI 快速上手指南](docs/getting-started-cli.md),一步步带你跑起来!
|
|
8
8
|
|
|
9
9
|
> 以下为**安装与使用说明**;仓库结构、开发脚本与贡献方式见文末 [仓库与开发](#仓库与开发)。
|
|
10
10
|
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
|
|
13
13
|
- **双维度主题**:`data-theme`(颜色)+ `data-style`(布局),独立切换
|
|
14
14
|
- **5 种布局风格**:neutral / compact / soft / sharp / dense
|
|
15
|
-
-
|
|
16
|
-
- **CSS
|
|
15
|
+
- **官方起始 preset**:Mint / Parchment
|
|
16
|
+
- **CSS 变量驱动**:覆盖颜色和 scale token 即可定制
|
|
17
|
+
- **图标系统可替换**:通过 `IconsProvider` 接 Lucide、Remix 或自定义图标库
|
|
17
18
|
- **Tailwind 友好**:类名与设计 token 映射,无硬编码色值
|
|
18
19
|
- **组件分层**:Basic 原子组件 + Chat 对话组件,开箱即用
|
|
19
20
|
|
|
@@ -26,6 +27,36 @@
|
|
|
26
27
|
|
|
27
28
|
**发布模型**:`sparkdesign` 是唯一发布到 npm 的包。CLI 不是第二个独立 npm 包,而是根包中的 `bin` 入口;因此用户侧既可以 `npm install sparkdesign`,也可以直接 `npx sparkdesign@latest add button`。
|
|
28
29
|
|
|
30
|
+
### 风格控制模型
|
|
31
|
+
|
|
32
|
+
不管你用哪种接入方式,真正决定项目风格的都是三层:
|
|
33
|
+
|
|
34
|
+
- **colors**:颜色 token,决定品牌色、背景、边框、语义色
|
|
35
|
+
- **scale**:布局 token,决定间距、圆角、字号和密度
|
|
36
|
+
- **icons**:图标系统,决定视觉语言
|
|
37
|
+
|
|
38
|
+
`mint` / `parchment` 只是官方提供的两套 starter preset,更适合当起点和 showcase 示例,而不是长期绑定的唯一主题心智。
|
|
39
|
+
|
|
40
|
+
### 接入后你实际拿到什么
|
|
41
|
+
|
|
42
|
+
**CLI 模式**
|
|
43
|
+
|
|
44
|
+
- `components.json`:记录默认 `appearance / theme / style`
|
|
45
|
+
- `src/sparkdesign-tokens.css` 或注入后的 `src/index.css`:预设好的颜色和 scale token,可直接改
|
|
46
|
+
- `src/components/ui/...`:复制出来的组件源码,可直接改
|
|
47
|
+
- `src/lib/utils.ts`:组件依赖的工具函数
|
|
48
|
+
|
|
49
|
+
**npm 安装模式**
|
|
50
|
+
|
|
51
|
+
- 预编译样式:`import 'sparkdesign/style'`
|
|
52
|
+
- token 入口:`import 'sparkdesign/theme.css'` / `import 'sparkdesign/scale.css'`
|
|
53
|
+
- 运行时入口:`ThemeStyleProvider`、`IconsProvider`
|
|
54
|
+
|
|
55
|
+
也就是说:
|
|
56
|
+
|
|
57
|
+
- **CLI** 更适合“拿到文件后自己改”
|
|
58
|
+
- **npm** 更适合“先跑起来,再通过 token 和 icon 覆盖”
|
|
59
|
+
|
|
29
60
|
---
|
|
30
61
|
|
|
31
62
|
## 安装(npm 包)
|
|
@@ -42,12 +73,12 @@ npm install sparkdesign
|
|
|
42
73
|
|
|
43
74
|
```bash
|
|
44
75
|
npx sparkdesign init # 初始化 components.json、tokens、utils
|
|
45
|
-
npx sparkdesign add button tooltip # 添加组件到 src/components/
|
|
76
|
+
npx sparkdesign add button tooltip # 添加组件到 src/components/ui/
|
|
46
77
|
npx sparkdesign list # 列出可用组件
|
|
47
78
|
npx sparkdesign diff button # 对比本地与注册表差异
|
|
48
79
|
```
|
|
49
80
|
|
|
50
|
-
详见 [docs/
|
|
81
|
+
详见 [docs/cli/on-demand-import.md](docs/cli/on-demand-import.md)。
|
|
51
82
|
|
|
52
83
|
---
|
|
53
84
|
|
|
@@ -113,8 +144,10 @@ function App() {
|
|
|
113
144
|
|
|
114
145
|
| 主题 | 说明 |
|
|
115
146
|
|--------|----------|
|
|
116
|
-
| `light` |
|
|
117
|
-
| `dark` |
|
|
147
|
+
| `light` | Mint 亮色(默认) |
|
|
148
|
+
| `dark` | Mint 暗色 |
|
|
149
|
+
| `light-parchment` | Parchment 亮色 |
|
|
150
|
+
| `dark-parchment` | Parchment 暗色 |
|
|
118
151
|
|
|
119
152
|
```tsx
|
|
120
153
|
// 在根节点或任意父节点设置
|
|
@@ -128,7 +161,13 @@ function App() {
|
|
|
128
161
|
<body data-theme="dark">
|
|
129
162
|
```
|
|
130
163
|
|
|
131
|
-
|
|
164
|
+
如果你使用 React,`ThemeStyleProvider` 还支持按三维组合主题:
|
|
165
|
+
|
|
166
|
+
- `appearance`: `light | dark`
|
|
167
|
+
- `theme`: `mint | parchment | 自定义对象`
|
|
168
|
+
- `style`: `neutral | compact | soft | sharp | dense`
|
|
169
|
+
|
|
170
|
+
例如 `appearance="dark" theme="parchment" style="soft"` 会得到 `data-theme="dark-parchment"` 与 `data-style="soft"`。主题对应的 CSS 变量定义在 **theme.css**(如 `--color-primary`、`--color-bg-container` 等)。自定义主题见下文「自定义 theme / scale」。
|
|
132
171
|
|
|
133
172
|
### 3. 布局风格(scale)
|
|
134
173
|
|
|
@@ -156,7 +195,15 @@ function App() {
|
|
|
156
195
|
</div>
|
|
157
196
|
```
|
|
158
197
|
|
|
159
|
-
### 4.
|
|
198
|
+
### 4. 真正决定风格的三件事
|
|
199
|
+
|
|
200
|
+
- **颜色变量**:决定品牌色、背景、边框、语义色
|
|
201
|
+
- **scale 变量**:决定间距、圆角、字号和组件密度
|
|
202
|
+
- **图标系统**:决定视觉语言和产品气质
|
|
203
|
+
|
|
204
|
+
官方 `mint` / `parchment` 只是两套 starter preset。无论你用整包还是 CLI,长期稳定的定制入口都应该是 token + icons,而不是依赖 preset 名称本身。
|
|
205
|
+
|
|
206
|
+
### 5. 自定义 theme / scale
|
|
160
207
|
|
|
161
208
|
在**先引入设计系统样式之后**,用你自己的 CSS 覆盖变量即可。
|
|
162
209
|
|
|
@@ -180,9 +227,9 @@ function App() {
|
|
|
180
227
|
}
|
|
181
228
|
```
|
|
182
229
|
|
|
183
|
-
|
|
230
|
+
完整变量说明见 [主题自定义指南](docs/guides/theme-customization.md)(颜色 `--color-*`、间距 `--spacing-*`、圆角 `--radius-*`、字号 `--font-size-*` 等)。
|
|
184
231
|
|
|
185
|
-
###
|
|
232
|
+
### 6. Portal 浮层与主题一致
|
|
186
233
|
|
|
187
234
|
DropdownMenu、Tooltip 等会挂到 `body`,不会继承局部容器的 `data-theme` / `data-style`。两种做法:
|
|
188
235
|
|
|
@@ -193,11 +240,12 @@ DropdownMenu、Tooltip 等会挂到 `body`,不会继承局部容器的 `data-t
|
|
|
193
240
|
import { ThemeStyleProvider, Button, DropdownMenu } from 'sparkdesign'
|
|
194
241
|
|
|
195
242
|
function App() {
|
|
196
|
-
const
|
|
243
|
+
const appearance = 'dark'
|
|
244
|
+
const theme = 'mint'
|
|
197
245
|
const style = 'soft'
|
|
198
246
|
return (
|
|
199
|
-
<ThemeStyleProvider
|
|
200
|
-
<div data-theme=
|
|
247
|
+
<ThemeStyleProvider appearance={appearance} theme={theme} style={style}>
|
|
248
|
+
<div data-theme="dark" data-style={style}>
|
|
201
249
|
<Button>打开</Button>
|
|
202
250
|
<DropdownMenu>...</DropdownMenu>
|
|
203
251
|
</div>
|
|
@@ -231,16 +279,20 @@ function App() {
|
|
|
231
279
|
| **Basic** | 原子级 UI 组件 | Button, IconButton, Tooltip, Select, DropdownMenu, Tabs, Toast, Tag, Progress, Avatar, Table, Slider, Pagination, Collapse, Resizable, Scrollbar, Skeleton, Spinner, Kbd, EllipsisText, Switch, Toggle, ToggleGroup, RadioGroup, AlertDialog, OptionList … |
|
|
232
280
|
| **Chat** | 对话流相关组件 | ChatInput, SendButton, Request, Response, FileCard, FileAttachment, ImageAttachment, FolderButton, ReasoningStep, ToolInvocationCard, PermissionCard, MarkdownBody, GenerationStatusBar, ThinkingIndicator, ImageGenerating, RelatedPrompts, SuggestionPart, SidebarMenu … |
|
|
233
281
|
|
|
234
|
-
完整导出见 [src/components/index.ts](src/components/index.ts)。图标通过 `IconsProvider` / `useIcon` 注入,可替换为 Lucide、Remix 等,见 [图标自定义说明](docs
|
|
282
|
+
完整导出见 [src/components/index.ts](src/components/index.ts)。图标通过 `IconsProvider` / `useIcon` 注入,可替换为 Lucide、Remix 等,见 [图标自定义说明](docs/guides/icons-customization.md)。
|
|
235
283
|
|
|
236
284
|
## 文档(仓库内)
|
|
237
285
|
|
|
238
286
|
| 文档 | 说明 |
|
|
239
287
|
|------|------|
|
|
240
|
-
| [NPM
|
|
241
|
-
| [
|
|
242
|
-
| [
|
|
243
|
-
| [
|
|
288
|
+
| [NPM 快速上手](docs/getting-started.md) | 整包引入、ThemeStyleProvider、Portal 主题继承 |
|
|
289
|
+
| [CLI 快速上手](docs/getting-started-cli.md) | `npx sparkdesign init / add` 入门 |
|
|
290
|
+
| [组件入库文档](docs/guides/component-development.md) | 组件与 Showcase 规范 |
|
|
291
|
+
| [图标自定义说明](docs/guides/icons-customization.md) | IconsProvider / 替换为 Lucide 等 |
|
|
292
|
+
| [CLI 按需引入方案](docs/cli/on-demand-import.md) | CLI 架构与按需引入说明 |
|
|
293
|
+
| [Component Lifecycle SOP](docs/sop/component-lifecycle.md) | 组件从开发到发布的固定流程 |
|
|
294
|
+
| [Theme Lifecycle SOP](docs/sop/theme-lifecycle.md) | preset / token / ThemeStyleProvider 维护流程 |
|
|
295
|
+
| [Release Checklist](docs/sop/release-checklist.md) | 发布前后检查单 |
|
|
244
296
|
|
|
245
297
|
---
|
|
246
298
|
|
|
@@ -262,7 +314,7 @@ cd apps/showcase && npm install && npm run dev
|
|
|
262
314
|
|------|------|
|
|
263
315
|
| `npm run dev` | 设计系统开发服务器 |
|
|
264
316
|
| `npm run build` | 生产构建:Vite 输出 JS → @tailwindcss/cli 预编译 CSS → tsc 类型声明 |
|
|
265
|
-
| `npm run build:css` | 仅重新生成预编译 CSS(`dist/
|
|
317
|
+
| `npm run build:css` | 仅重新生成预编译 CSS(`dist/sparkdesign.css`) |
|
|
266
318
|
| `npm run preview` | 预览构建产物 |
|
|
267
319
|
| `cd apps/showcase && npm run dev` | 组件展示站点 |
|
|
268
320
|
|
|
@@ -280,7 +332,7 @@ src/
|
|
|
280
332
|
### 技术栈与规范
|
|
281
333
|
|
|
282
334
|
- React 19 + TypeScript + Vite 7;Tailwind CSS 4;Radix UI、Framer Motion
|
|
283
|
-
- 项目哲学与 GEB 协议:[
|
|
335
|
+
- 项目哲学与 GEB 协议:[AGENTS.md](https://github.com/nicepkg/spark-design/blob/master/AGENTS.md)
|
|
284
336
|
|
|
285
337
|
---
|
|
286
338
|
|
package/cli/dist/commands/add.js
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [
|
|
3
|
-
* [
|
|
4
|
-
* [
|
|
2
|
+
* [WHO]: Public exports from this file (see implementation below).
|
|
3
|
+
* [FROM]: See the import block immediately after this header.
|
|
4
|
+
* [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
|
|
5
|
+
* [HERE]: cli/src/commands/add.ts — Spark Design source; keep aligned with the main library.
|
|
5
6
|
*
|
|
6
|
-
* [PROTOCOL]:
|
|
7
|
+
* [PROTOCOL]:
|
|
8
|
+
* 1. Keep this P3 header in sync when the public contract changes.
|
|
9
|
+
* 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
|
|
10
|
+
* 3. Follow design tokens and explicit type exports.
|
|
7
11
|
*/
|
|
8
12
|
import path from 'node:path';
|
|
9
13
|
import fs from 'fs-extra';
|
|
10
14
|
import chalk from 'chalk';
|
|
11
|
-
import { execa } from 'execa';
|
|
12
15
|
import { readConfig, writeConfig, getComponentsBaseAlias, resolveTargetDir } from '../utils/config.js';
|
|
13
16
|
import { getRegistryRoot, getComponentMeta, getComponentSource } from '../utils/registry.js';
|
|
14
17
|
import { stripL3Header, transformImports } from '../utils/transform.js';
|
|
15
|
-
import { projectHasTokens,
|
|
18
|
+
import { projectHasTokens, writeSparkTokensFile, getSparkTokensImportPath } from '../utils/tokens.js';
|
|
19
|
+
import { detectPackageManager, getInstallCommand, installDependencies } from '../utils/package-manager.js';
|
|
20
|
+
import { ensureTsAlias } from '../utils/tsconfig.js';
|
|
16
21
|
export async function add(components, options = {}) {
|
|
17
22
|
const cwd = process.cwd();
|
|
18
23
|
const registryRoot = getRegistryRoot();
|
|
@@ -22,23 +27,27 @@ export async function add(components, options = {}) {
|
|
|
22
27
|
await writeConfig(config, cwd);
|
|
23
28
|
console.log(chalk.green('✓'), 'Created components.json (default config)');
|
|
24
29
|
}
|
|
30
|
+
await ensureTsAlias(cwd);
|
|
25
31
|
const hasTokens = await projectHasTokens(cwd);
|
|
26
32
|
if (!hasTokens) {
|
|
27
|
-
const { path: tokensPath, created } = await
|
|
33
|
+
const { path: tokensPath, created } = await writeSparkTokensFile(cwd, registryRoot);
|
|
28
34
|
if (created) {
|
|
29
35
|
console.log(chalk.green('✓'), 'Created', tokensPath);
|
|
30
36
|
}
|
|
31
37
|
console.log(chalk.cyan(' To enable component styles, add to your app entry (e.g. main.tsx or layout.tsx):'));
|
|
32
|
-
console.log(chalk.yellow(' import'), chalk.white(`'${
|
|
38
|
+
console.log(chalk.yellow(' import'), chalk.white(`'${getSparkTokensImportPath()}'`));
|
|
33
39
|
console.log(chalk.gray(' (Paste the line above into your entry file.)'));
|
|
34
40
|
console.log(chalk.gray(' Or run'), chalk.cyan('npx sparkdesign init'), chalk.gray('for full setup with tokens in your CSS entry.'));
|
|
35
41
|
}
|
|
42
|
+
const allDeps = new Set();
|
|
36
43
|
const utilsDest = path.join(cwd, 'src', 'lib', 'utils.ts');
|
|
37
44
|
if (!(await fs.pathExists(utilsDest))) {
|
|
38
45
|
const utilsTemplate = await fs.readFile(path.join(registryRoot, 'lib', 'utils.ts'), 'utf-8');
|
|
39
46
|
await fs.ensureDir(path.dirname(utilsDest));
|
|
40
47
|
await fs.writeFile(utilsDest, utilsTemplate, 'utf-8');
|
|
41
48
|
console.log(chalk.green('✓'), 'Created', utilsDest);
|
|
49
|
+
allDeps.add('clsx');
|
|
50
|
+
allDeps.add('tailwind-merge');
|
|
42
51
|
}
|
|
43
52
|
if (!components || components.length === 0) {
|
|
44
53
|
console.log(chalk.cyan('Usage:'), 'npx sparkdesign add button [tooltip] [dropdown-menu]');
|
|
@@ -52,7 +61,6 @@ export async function add(components, options = {}) {
|
|
|
52
61
|
console.log(chalk.gray(' 目标目录:'), chalk.cyan(path.relative(cwd, targetDir) || targetDir));
|
|
53
62
|
console.log(chalk.gray(' (components.json → aliases.ui)'));
|
|
54
63
|
console.log('');
|
|
55
|
-
const allDeps = new Set();
|
|
56
64
|
for (const name of components) {
|
|
57
65
|
const meta = await getComponentMeta(registryRoot, name);
|
|
58
66
|
if (!meta) {
|
|
@@ -82,12 +90,14 @@ export async function add(components, options = {}) {
|
|
|
82
90
|
console.log(chalk.cyan('Installing dependencies...'));
|
|
83
91
|
const depsArray = Array.from(allDeps);
|
|
84
92
|
try {
|
|
85
|
-
|
|
86
|
-
console.log(chalk.green('✓'),
|
|
93
|
+
const { pm } = await installDependencies(cwd, depsArray);
|
|
94
|
+
console.log(chalk.green('✓'), `Installed ${depsArray.length} dependencies via ${pm}`);
|
|
87
95
|
}
|
|
88
96
|
catch {
|
|
97
|
+
const pm = await detectPackageManager(cwd);
|
|
98
|
+
const install = getInstallCommand(pm, depsArray);
|
|
89
99
|
console.log(chalk.yellow('○'), 'Auto-install failed. Run manually:');
|
|
90
|
-
console.log(
|
|
100
|
+
console.log(` ${install.display}`);
|
|
91
101
|
}
|
|
92
102
|
}
|
|
93
103
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [
|
|
3
|
-
* [
|
|
4
|
-
* [
|
|
2
|
+
* [WHO]: Public exports from this file (see implementation below).
|
|
3
|
+
* [FROM]: See the import block immediately after this header.
|
|
4
|
+
* [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
|
|
5
|
+
* [HERE]: cli/src/commands/diff.ts — Spark Design source; keep aligned with the main library.
|
|
5
6
|
*
|
|
6
|
-
* [PROTOCOL]:
|
|
7
|
+
* [PROTOCOL]:
|
|
8
|
+
* 1. Keep this P3 header in sync when the public contract changes.
|
|
9
|
+
* 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
|
|
10
|
+
* 3. Follow design tokens and explicit type exports.
|
|
7
11
|
*/
|
|
8
12
|
import path from 'node:path';
|
|
9
13
|
import fs from 'fs-extra';
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [
|
|
3
|
-
* [
|
|
4
|
-
* [
|
|
2
|
+
* [WHO]: Public exports from this file (see implementation below).
|
|
3
|
+
* [FROM]: See the import block immediately after this header.
|
|
4
|
+
* [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
|
|
5
|
+
* [HERE]: cli/src/commands/init.ts — Spark Design source; keep aligned with the main library.
|
|
5
6
|
*
|
|
6
|
-
* [PROTOCOL]:
|
|
7
|
+
* [PROTOCOL]:
|
|
8
|
+
* 1. Keep this P3 header in sync when the public contract changes.
|
|
9
|
+
* 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
|
|
10
|
+
* 3. Follow design tokens and explicit type exports.
|
|
7
11
|
*/
|
|
8
12
|
import path from 'node:path';
|
|
9
13
|
import fs from 'fs-extra';
|
|
10
14
|
import prompts from 'prompts';
|
|
11
15
|
import chalk from 'chalk';
|
|
12
|
-
import { execa } from 'execa';
|
|
13
16
|
import { writeConfig } from '../utils/config.js';
|
|
14
17
|
import { getRegistryRoot } from '../utils/registry.js';
|
|
15
18
|
import { ensureDesignTokens, projectHasTokens } from '../utils/tokens.js';
|
|
19
|
+
import { detectPackageManager, getInstallCommand, installDependencies } from '../utils/package-manager.js';
|
|
20
|
+
import { ensureTsAlias } from '../utils/tsconfig.js';
|
|
16
21
|
const STYLES = [
|
|
17
22
|
{ title: 'neutral(平衡标准)', value: 'neutral' },
|
|
18
23
|
{ title: 'compact(紧凑)', value: 'compact' },
|
|
@@ -20,10 +25,54 @@ const STYLES = [
|
|
|
20
25
|
{ title: 'sharp(几何)', value: 'sharp' },
|
|
21
26
|
{ title: 'dense(密集)', value: 'dense' },
|
|
22
27
|
];
|
|
28
|
+
const APPEARANCES = [
|
|
29
|
+
{ title: 'light(亮色)', value: 'light' },
|
|
30
|
+
{ title: 'dark(暗色)', value: 'dark' },
|
|
31
|
+
];
|
|
32
|
+
const THEMES = [
|
|
33
|
+
{ title: 'mint', value: 'mint', description: '官方绿色主题起点' },
|
|
34
|
+
{ title: 'parchment', value: 'parchment', description: '温暖纸感外观' },
|
|
35
|
+
];
|
|
36
|
+
function getDataThemeLabel(appearance, theme) {
|
|
37
|
+
if (theme === 'mint') {
|
|
38
|
+
return appearance;
|
|
39
|
+
}
|
|
40
|
+
return `${appearance}-${theme}`;
|
|
41
|
+
}
|
|
23
42
|
function resolveComponentsDir(aliasesComponents) {
|
|
24
43
|
const trimmed = aliasesComponents.replace(/^@\//, '');
|
|
25
44
|
return trimmed ? path.join('src', trimmed) : 'src/components';
|
|
26
45
|
}
|
|
46
|
+
/** 检查样式链路完整性,缺失时输出可复制的修复命令 */
|
|
47
|
+
async function checkStyleChain(cwd, injectedCss) {
|
|
48
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
49
|
+
if (!(await fs.pathExists(pkgPath)))
|
|
50
|
+
return;
|
|
51
|
+
const pkg = await fs.readJson(pkgPath);
|
|
52
|
+
const allDeps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
|
|
53
|
+
const pm = await detectPackageManager(cwd);
|
|
54
|
+
const isVite = Boolean(allDeps['vite'] || allDeps['@vitejs/plugin-react']);
|
|
55
|
+
if (!allDeps['tailwindcss']) {
|
|
56
|
+
const deps = isVite
|
|
57
|
+
? ['tailwindcss', '@tailwindcss/vite']
|
|
58
|
+
: ['tailwindcss', '@tailwindcss/postcss'];
|
|
59
|
+
console.log(chalk.yellow('⚠'), 'Spark Design 需要 Tailwind CSS 4 驱动样式,当前未检测到:');
|
|
60
|
+
console.log(chalk.cyan(` ${getInstallCommand(pm, deps).display}`));
|
|
61
|
+
if (isVite) {
|
|
62
|
+
console.log(chalk.gray(' 然后在 vite.config 中添加插件:'));
|
|
63
|
+
console.log(chalk.cyan(' import tailwindcss from "@tailwindcss/vite"'));
|
|
64
|
+
console.log(chalk.cyan(' // plugins: [tailwindcss(), ...]'));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (isVite && !allDeps['@tailwindcss/vite'] && !allDeps['@tailwindcss/postcss']) {
|
|
68
|
+
console.log(chalk.yellow('⚠'), 'Tailwind 已安装但缺少 Vite 集成插件:');
|
|
69
|
+
console.log(chalk.cyan(` ${getInstallCommand(pm, ['@tailwindcss/vite']).display}`));
|
|
70
|
+
}
|
|
71
|
+
if (injectedCss) {
|
|
72
|
+
const rel = path.relative(path.join(cwd, 'src'), injectedCss).replace(/\\/g, '/');
|
|
73
|
+
console.log(chalk.gray(' 确保入口文件(如 main.tsx)引入了样式:'), chalk.cyan(`import './${rel}'`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
27
76
|
export async function init() {
|
|
28
77
|
const cwd = process.cwd();
|
|
29
78
|
const registryRoot = getRegistryRoot();
|
|
@@ -36,6 +85,20 @@ export async function init() {
|
|
|
36
85
|
choices: STYLES,
|
|
37
86
|
initial: 0,
|
|
38
87
|
},
|
|
88
|
+
{
|
|
89
|
+
type: 'select',
|
|
90
|
+
name: 'appearance',
|
|
91
|
+
message: '选择亮暗外观 (appearance)',
|
|
92
|
+
choices: APPEARANCES,
|
|
93
|
+
initial: 0,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
type: 'select',
|
|
97
|
+
name: 'theme',
|
|
98
|
+
message: '选择颜色主题 (theme family)',
|
|
99
|
+
choices: THEMES,
|
|
100
|
+
initial: 0,
|
|
101
|
+
},
|
|
39
102
|
{
|
|
40
103
|
type: 'text',
|
|
41
104
|
name: 'componentsDir',
|
|
@@ -53,10 +116,14 @@ export async function init() {
|
|
|
53
116
|
process.exit(0);
|
|
54
117
|
}
|
|
55
118
|
const style = answers.style;
|
|
119
|
+
const appearance = answers.appearance;
|
|
120
|
+
const theme = answers.theme;
|
|
56
121
|
const rawDir = (answers.componentsDir ?? 'components').toString().trim().replace(/^src\/?/, '');
|
|
57
122
|
const componentsPath = rawDir || 'components';
|
|
58
123
|
const config = {
|
|
59
124
|
style,
|
|
125
|
+
appearance,
|
|
126
|
+
theme,
|
|
60
127
|
aliases: {
|
|
61
128
|
components: `@/${componentsPath}`,
|
|
62
129
|
utils: '@/lib/utils',
|
|
@@ -65,6 +132,7 @@ export async function init() {
|
|
|
65
132
|
};
|
|
66
133
|
await writeConfig(config, cwd);
|
|
67
134
|
console.log(chalk.green('✓'), 'Created components.json');
|
|
135
|
+
await ensureTsAlias(cwd);
|
|
68
136
|
const componentsDir = resolveComponentsDir(config.aliases.components);
|
|
69
137
|
const libDir = path.join(cwd, 'src', 'lib');
|
|
70
138
|
const utilsDest = path.join(libDir, 'utils.ts');
|
|
@@ -80,17 +148,24 @@ export async function init() {
|
|
|
80
148
|
console.log(chalk.yellow('○'), 'Design tokens already present in project');
|
|
81
149
|
}
|
|
82
150
|
else {
|
|
83
|
-
console.log(chalk.yellow('
|
|
151
|
+
console.log(chalk.yellow('⚠'), 'Token 注入失败。请手动创建 CSS 入口后重试:');
|
|
152
|
+
console.log(chalk.cyan(' mkdir -p src && touch src/index.css'));
|
|
153
|
+
console.log(chalk.gray(' 然后重新运行'), chalk.cyan('npx sparkdesign init'));
|
|
84
154
|
}
|
|
155
|
+
await checkStyleChain(cwd, injected);
|
|
85
156
|
if (answers.installDeps) {
|
|
157
|
+
const deps = ['class-variance-authority', 'clsx', 'tailwind-merge'];
|
|
86
158
|
try {
|
|
87
|
-
|
|
88
|
-
console.log(chalk.green('✓'),
|
|
159
|
+
const { pm } = await installDependencies(cwd, deps);
|
|
160
|
+
console.log(chalk.green('✓'), `Installed dependencies via ${pm}`);
|
|
89
161
|
}
|
|
90
162
|
catch {
|
|
91
|
-
|
|
163
|
+
const pm = await detectPackageManager(cwd);
|
|
164
|
+
const install = getInstallCommand(pm, deps);
|
|
165
|
+
console.log(chalk.yellow('○'), `Run manually: ${install.display}`);
|
|
92
166
|
}
|
|
93
167
|
}
|
|
94
168
|
await fs.ensureDir(path.join(cwd, componentsDir));
|
|
169
|
+
console.log(chalk.green('✓'), 'Default theme preset:', chalk.cyan(`appearance=${appearance}`), chalk.cyan(`theme=${theme}`), chalk.gray(`(data-theme="${getDataThemeLabel(appearance, theme)}", data-style="${style}")`));
|
|
95
170
|
console.log(chalk.green('✓'), 'Spark Design initialized. Run'), chalk.cyan('npx sparkdesign add button'), chalk.green('to add components.');
|
|
96
171
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [
|
|
3
|
-
* [
|
|
4
|
-
* [
|
|
2
|
+
* [WHO]: Public exports from this file (see implementation below).
|
|
3
|
+
* [FROM]: See the import block immediately after this header.
|
|
4
|
+
* [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
|
|
5
|
+
* [HERE]: cli/src/commands/list.ts — Spark Design source; keep aligned with the main library.
|
|
5
6
|
*
|
|
6
|
-
* [PROTOCOL]:
|
|
7
|
+
* [PROTOCOL]:
|
|
8
|
+
* 1. Keep this P3 header in sync when the public contract changes.
|
|
9
|
+
* 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
|
|
10
|
+
* 3. Follow design tokens and explicit type exports.
|
|
7
11
|
*/
|
|
8
12
|
import chalk from 'chalk';
|
|
9
13
|
import { getRegistryRoot, listComponents } from '../utils/registry.js';
|
package/cli/dist/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* [
|
|
4
|
-
* [
|
|
5
|
-
* [
|
|
3
|
+
* [WHO]: Public exports from this file (see implementation below).
|
|
4
|
+
* [FROM]: See the import block immediately after this header.
|
|
5
|
+
* [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
|
|
6
|
+
* [HERE]: cli/src/index.ts — Spark Design source; keep aligned with the main library.
|
|
6
7
|
*
|
|
7
8
|
* [PROTOCOL]:
|
|
8
|
-
* 1.
|
|
9
|
-
* 2.
|
|
9
|
+
* 1. Keep this P3 header in sync when the public contract changes.
|
|
10
|
+
* 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
|
|
11
|
+
* 3. Follow design tokens and explicit type exports.
|
|
10
12
|
*/
|
|
11
13
|
import { program } from 'commander';
|
|
14
|
+
import rootPackageJson from '../../package.json' with { type: 'json' };
|
|
12
15
|
import { init } from './commands/init.js';
|
|
13
16
|
import { add } from './commands/add.js';
|
|
14
17
|
import { list } from './commands/list.js';
|
|
@@ -16,7 +19,7 @@ import { diffCmd } from './commands/diff.js';
|
|
|
16
19
|
program
|
|
17
20
|
.name('sparkdesign')
|
|
18
21
|
.description('Spark Design CLI - 按需添加组件(源码复制到当前项目)')
|
|
19
|
-
.version(
|
|
22
|
+
.version(rootPackageJson.version);
|
|
20
23
|
program
|
|
21
24
|
.command('init')
|
|
22
25
|
.description('初始化项目:写入 components.json、tokens、utils')
|
package/cli/dist/utils/config.js
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [
|
|
3
|
-
* [
|
|
4
|
-
* [
|
|
2
|
+
* [WHO]: Public exports from this file (see implementation below).
|
|
3
|
+
* [FROM]: See the import block immediately after this header.
|
|
4
|
+
* [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
|
|
5
|
+
* [HERE]: cli/src/utils/config.ts — Spark Design source; keep aligned with the main library.
|
|
5
6
|
*
|
|
6
|
-
* [PROTOCOL]:
|
|
7
|
+
* [PROTOCOL]:
|
|
8
|
+
* 1. Keep this P3 header in sync when the public contract changes.
|
|
9
|
+
* 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
|
|
10
|
+
* 3. Follow design tokens and explicit type exports.
|
|
7
11
|
*/
|
|
8
12
|
import path from 'node:path';
|
|
9
13
|
import fs from 'fs-extra';
|
|
10
14
|
const defaultConfig = {
|
|
11
15
|
style: 'neutral',
|
|
16
|
+
appearance: 'light',
|
|
17
|
+
theme: 'mint',
|
|
12
18
|
aliases: {
|
|
13
19
|
components: '@/components',
|
|
14
20
|
utils: '@/lib/utils',
|
|
15
21
|
ui: '@/components/ui',
|
|
16
22
|
},
|
|
17
23
|
};
|
|
18
|
-
/** 优先用 aliases.ui
|
|
24
|
+
/** 优先用 aliases.ui 作为组件安装目录,未设时回退到 components + '/ui' */
|
|
19
25
|
export function getComponentsBaseAlias(config) {
|
|
20
|
-
return config.aliases.ui ?? config.aliases.components + '/
|
|
26
|
+
return config.aliases.ui ?? config.aliases.components + '/ui';
|
|
21
27
|
}
|
|
22
|
-
/** 将 alias 解析为绝对目录(如 @/components/
|
|
28
|
+
/** 将 alias 解析为绝对目录(如 @/components/ui → cwd/src/components/ui) */
|
|
23
29
|
export function resolveTargetDir(aliasesBase, cwd) {
|
|
24
30
|
const withoutAlias = aliasesBase.replace(/^@\//, '').trim();
|
|
25
31
|
let base = withoutAlias || 'components';
|
|
@@ -33,6 +39,8 @@ export async function readConfig(cwd = process.cwd()) {
|
|
|
33
39
|
const parsed = JSON.parse(content);
|
|
34
40
|
return {
|
|
35
41
|
style: parsed.style ?? defaultConfig.style,
|
|
42
|
+
appearance: parsed.appearance ?? defaultConfig.appearance,
|
|
43
|
+
theme: parsed.theme ?? defaultConfig.theme,
|
|
36
44
|
aliases: {
|
|
37
45
|
components: parsed.aliases?.components ?? defaultConfig.aliases.components,
|
|
38
46
|
utils: parsed.aliases?.utils ?? defaultConfig.aliases.utils,
|
|
@@ -47,7 +55,7 @@ export async function readConfig(cwd = process.cwd()) {
|
|
|
47
55
|
export async function writeConfig(config, cwd = process.cwd()) {
|
|
48
56
|
const configPath = path.join(cwd, 'components.json');
|
|
49
57
|
await fs.writeJson(configPath, {
|
|
50
|
-
$schema: 'https://
|
|
58
|
+
$schema: 'https://spark.design/schema.json',
|
|
51
59
|
...config,
|
|
52
60
|
}, { spaces: 2 });
|
|
53
61
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [WHO]: Public exports from this file (see implementation below).
|
|
3
|
+
* [FROM]: See the import block immediately after this header.
|
|
4
|
+
* [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
|
|
5
|
+
* [HERE]: cli/src/utils/package-manager.ts — Spark Design source; keep aligned with the main library.
|
|
6
|
+
*
|
|
7
|
+
* [PROTOCOL]:
|
|
8
|
+
* 1. Keep this P3 header in sync when the public contract changes.
|
|
9
|
+
* 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
|
|
10
|
+
* 3. Follow design tokens and explicit type exports.
|
|
11
|
+
*/
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import fs from 'fs-extra';
|
|
14
|
+
import { execa } from 'execa';
|
|
15
|
+
const LOCKFILE_TO_PM = [
|
|
16
|
+
{ file: 'pnpm-lock.yaml', pm: 'pnpm' },
|
|
17
|
+
{ file: 'yarn.lock', pm: 'yarn' },
|
|
18
|
+
{ file: 'bun.lockb', pm: 'bun' },
|
|
19
|
+
{ file: 'bun.lock', pm: 'bun' },
|
|
20
|
+
{ file: 'package-lock.json', pm: 'npm' },
|
|
21
|
+
];
|
|
22
|
+
function parsePackageManagerField(value) {
|
|
23
|
+
if (typeof value !== 'string') {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (value.startsWith('pnpm@'))
|
|
27
|
+
return 'pnpm';
|
|
28
|
+
if (value.startsWith('yarn@'))
|
|
29
|
+
return 'yarn';
|
|
30
|
+
if (value.startsWith('bun@'))
|
|
31
|
+
return 'bun';
|
|
32
|
+
if (value.startsWith('npm@'))
|
|
33
|
+
return 'npm';
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
export async function detectPackageManager(cwd) {
|
|
37
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
38
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
39
|
+
try {
|
|
40
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
41
|
+
const fromField = parsePackageManagerField(packageJson.packageManager);
|
|
42
|
+
if (fromField) {
|
|
43
|
+
return fromField;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Ignore malformed package.json and continue to lockfile detection.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const candidate of LOCKFILE_TO_PM) {
|
|
51
|
+
if (await fs.pathExists(path.join(cwd, candidate.file))) {
|
|
52
|
+
return candidate.pm;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return 'npm';
|
|
56
|
+
}
|
|
57
|
+
export function getInstallCommand(pm, dependencies) {
|
|
58
|
+
switch (pm) {
|
|
59
|
+
case 'pnpm':
|
|
60
|
+
return { command: 'pnpm', args: ['add', ...dependencies], display: `pnpm add ${dependencies.join(' ')}` };
|
|
61
|
+
case 'yarn':
|
|
62
|
+
return { command: 'yarn', args: ['add', ...dependencies], display: `yarn add ${dependencies.join(' ')}` };
|
|
63
|
+
case 'bun':
|
|
64
|
+
return { command: 'bun', args: ['add', ...dependencies], display: `bun add ${dependencies.join(' ')}` };
|
|
65
|
+
case 'npm':
|
|
66
|
+
default:
|
|
67
|
+
return { command: 'npm', args: ['install', ...dependencies], display: `npm install ${dependencies.join(' ')}` };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export async function installDependencies(cwd, dependencies) {
|
|
71
|
+
const pm = await detectPackageManager(cwd);
|
|
72
|
+
const install = getInstallCommand(pm, dependencies);
|
|
73
|
+
await execa(install.command, install.args, { cwd, stdio: 'inherit' });
|
|
74
|
+
return { pm, display: install.display };
|
|
75
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [
|
|
3
|
-
* [
|
|
4
|
-
* [
|
|
2
|
+
* [WHO]: Public exports from this file (see implementation below).
|
|
3
|
+
* [FROM]: See the import block immediately after this header.
|
|
4
|
+
* [TO]: sparkdesign package consumers; output of CLI `add` when applicable.
|
|
5
|
+
* [HERE]: cli/src/utils/registry.ts — Spark Design source; keep aligned with the main library.
|
|
5
6
|
*
|
|
6
|
-
* [PROTOCOL]:
|
|
7
|
+
* [PROTOCOL]:
|
|
8
|
+
* 1. Keep this P3 header in sync when the public contract changes.
|
|
9
|
+
* 2. Update module AGENTS.md (P2) and root AGENTS.md (P1) when boundaries change.
|
|
10
|
+
* 3. Follow design tokens and explicit type exports.
|
|
7
11
|
*/
|
|
8
12
|
import path from 'node:path';
|
|
9
13
|
import fs from 'fs-extra';
|