denji 0.2.0 → 0.3.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/README.md +107 -21
- package/configuration_schema.json +179 -104
- package/dist/index.mjs +1 -6
- package/dist/strategy-C4DjhM4A.mjs +13 -0
- package/dist/strategy-HhTswc-Y.mjs +17 -0
- package/dist/strategy-bLzeYhwz.mjs +15 -0
- package/dist/svg-Dnt5ibPB.mjs +1 -0
- package/package.json +10 -7
package/README.md
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
# Denji
|
|
2
2
|
|
|
3
|
-
CLI tool for managing SVG icons in
|
|
3
|
+
CLI tool for managing SVG icons in frontend projects. Fetches icons from Iconify, converts to optimized components, and maintains a centralized icons file.
|
|
4
|
+
|
|
5
|
+
Supports **React**, **Preact**, and **Solid**.
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
Visit https://denji-docs.vercel.app/docs to view the full documentation.
|
|
4
10
|
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
8
|
-
|
|
14
|
+
npm add -D denji
|
|
9
15
|
```
|
|
10
16
|
|
|
11
17
|
## Quick Start
|
|
@@ -39,7 +45,7 @@ Create `denji.json`:
|
|
|
39
45
|
"typescript": true,
|
|
40
46
|
"a11y": "hidden",
|
|
41
47
|
"hooks": {
|
|
42
|
-
"postAdd": ["
|
|
48
|
+
"postAdd": ["npx biome check --write ./src/icons.tsx"]
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
```
|
|
@@ -49,20 +55,110 @@ Create `denji.json`:
|
|
|
49
55
|
| Option | Type | Default | Description |
|
|
50
56
|
|--------|------|---------|-------------|
|
|
51
57
|
| `output` | `string` | - | Output file path (e.g., `./src/icons.tsx`) |
|
|
52
|
-
| `framework` | `"react"` | - | Target framework |
|
|
58
|
+
| `framework` | `"react"` \| `"preact"` \| `"solid"` | - | Target framework |
|
|
53
59
|
| `typescript` | `boolean` | `true` | Generate TypeScript |
|
|
54
60
|
| `a11y` | `"hidden"` \| `"img"` \| `"title"` \| `"presentation"` \| `false` | - | SVG accessibility strategy |
|
|
55
61
|
| `hooks` | `object` | - | Lifecycle hooks |
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
## Frameworks
|
|
64
|
+
|
|
65
|
+
### React
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
denji init --framework react
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"framework": "react",
|
|
74
|
+
"react": {
|
|
75
|
+
"forwardRef": true
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Usage:**
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { Icons } from "./icons";
|
|
84
|
+
|
|
85
|
+
function App() {
|
|
86
|
+
return <Icons.Check className="size-4 text-green-500" />;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Options:**
|
|
91
|
+
|
|
92
|
+
| Option | Type | Default | Description |
|
|
93
|
+
|--------|------|---------|-------------|
|
|
94
|
+
| `react.forwardRef` | `boolean` | `false` | Wrap icons with `forwardRef` |
|
|
95
|
+
|
|
96
|
+
### Preact
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
denji init --framework preact
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"framework": "preact",
|
|
105
|
+
"preact": {
|
|
106
|
+
"forwardRef": true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Usage:**
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { Icons } from "./icons";
|
|
115
|
+
|
|
116
|
+
function App() {
|
|
117
|
+
return <Icons.Check className="size-4 text-green-500" />;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Options:**
|
|
122
|
+
|
|
123
|
+
| Option | Type | Default | Description |
|
|
124
|
+
|--------|------|---------|-------------|
|
|
125
|
+
| `preact.forwardRef` | `boolean` | `false` | Wrap icons with `forwardRef` (uses `preact/compat`) |
|
|
126
|
+
|
|
127
|
+
### Solid
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
denji init --framework solid
|
|
131
|
+
```
|
|
58
132
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"framework": "solid"
|
|
136
|
+
}
|
|
137
|
+
```
|
|
64
138
|
|
|
65
|
-
|
|
139
|
+
**Usage:**
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import { Icons } from "./icons";
|
|
143
|
+
|
|
144
|
+
function App() {
|
|
145
|
+
return <Icons.Check class="size-4 text-green-500" />;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> Note: Solid uses `class` instead of `className`. Refs are passed as regular props - no `forwardRef` needed.
|
|
150
|
+
|
|
151
|
+
## Accessibility
|
|
152
|
+
|
|
153
|
+
| Strategy | Description |
|
|
154
|
+
|----------|-------------|
|
|
155
|
+
| `hidden` | Adds `aria-hidden="true"` (decorative icons) |
|
|
156
|
+
| `img` | Adds `role="img"` with `aria-label` |
|
|
157
|
+
| `title` | Adds `<title>` element inside SVG |
|
|
158
|
+
| `presentation` | Adds `role="presentation"` |
|
|
159
|
+
| `false` | No accessibility attributes |
|
|
160
|
+
|
|
161
|
+
## Hooks
|
|
66
162
|
|
|
67
163
|
Available hooks: `preAdd`, `postAdd`, `preRemove`, `postRemove`, `preClear`, `postClear`, `preList`, `postList`
|
|
68
164
|
|
|
@@ -112,16 +208,6 @@ denji clear
|
|
|
112
208
|
denji clear --yes # Skip confirmation
|
|
113
209
|
```
|
|
114
210
|
|
|
115
|
-
## Usage
|
|
116
|
-
|
|
117
|
-
```tsx
|
|
118
|
-
import { Icons } from "./icons";
|
|
119
|
-
|
|
120
|
-
function App() {
|
|
121
|
-
return <Icons.Check className="size-4 text-green-500" />;
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
211
|
## License
|
|
126
212
|
|
|
127
213
|
MIT
|
|
@@ -1,123 +1,198 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
"$schema": {
|
|
6
|
-
"default": "https://denji-docs.vercel.app/configuration_schema.json",
|
|
7
|
-
"description": "The URL of the JSON Schema for this configuration file (e.g., './node_modules/denji/configuration_schema.json')",
|
|
8
|
-
"type": "string"
|
|
9
|
-
},
|
|
10
|
-
"output": {
|
|
11
|
-
"type": "string",
|
|
12
|
-
"description": "The output file path for generated icon components (e.g., './src/icons.tsx')"
|
|
13
|
-
},
|
|
14
|
-
"framework": {
|
|
15
|
-
"type": "string",
|
|
16
|
-
"enum": [
|
|
17
|
-
"react"
|
|
18
|
-
],
|
|
19
|
-
"description": "The framework to generate icon components for"
|
|
20
|
-
},
|
|
21
|
-
"typescript": {
|
|
22
|
-
"default": true,
|
|
23
|
-
"description": "Whether to generate TypeScript code",
|
|
24
|
-
"type": "boolean"
|
|
25
|
-
},
|
|
26
|
-
"a11y": {
|
|
27
|
-
"description": "Accessibility strategy for SVG icons (hidden: aria-hidden, img: role=img with aria-label, title: <title> element, presentation: role=presentation, false: no a11y attrs)",
|
|
28
|
-
"anyOf": [
|
|
29
|
-
{
|
|
30
|
-
"type": "string",
|
|
31
|
-
"enum": [
|
|
32
|
-
"hidden",
|
|
33
|
-
"img",
|
|
34
|
-
"title",
|
|
35
|
-
"presentation"
|
|
36
|
-
]
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"type": "boolean",
|
|
40
|
-
"const": false
|
|
41
|
-
}
|
|
42
|
-
]
|
|
43
|
-
},
|
|
44
|
-
"trackSource": {
|
|
45
|
-
"default": true,
|
|
46
|
-
"description": "Add data-icon attribute with Iconify source name (enables update command, debugging, and identifying icon collections)",
|
|
47
|
-
"type": "boolean"
|
|
48
|
-
},
|
|
49
|
-
"hooks": {
|
|
50
|
-
"description": "Hooks to run at various stages",
|
|
3
|
+
"allOf": [
|
|
4
|
+
{
|
|
51
5
|
"type": "object",
|
|
52
6
|
"properties": {
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
},
|
|
58
|
-
"description": "Scripts to run before adding icons"
|
|
7
|
+
"$schema": {
|
|
8
|
+
"default": "https://denji-docs.vercel.app/configuration_schema.json",
|
|
9
|
+
"description": "The URL of the JSON Schema for this configuration file (e.g., './node_modules/denji/configuration_schema.json')",
|
|
10
|
+
"type": "string"
|
|
59
11
|
},
|
|
60
|
-
"
|
|
61
|
-
"type": "
|
|
62
|
-
"
|
|
63
|
-
"type": "string"
|
|
64
|
-
},
|
|
65
|
-
"description": "Scripts to run after adding icons"
|
|
12
|
+
"output": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "The output file path for generated icon components (e.g., './src/icons.tsx')"
|
|
66
15
|
},
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
},
|
|
72
|
-
"description": "Scripts to run before removing icons"
|
|
16
|
+
"typescript": {
|
|
17
|
+
"default": true,
|
|
18
|
+
"description": "Whether to generate TypeScript code",
|
|
19
|
+
"type": "boolean"
|
|
73
20
|
},
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
21
|
+
"a11y": {
|
|
22
|
+
"anyOf": [
|
|
23
|
+
{
|
|
24
|
+
"type": "string",
|
|
25
|
+
"enum": [
|
|
26
|
+
"hidden",
|
|
27
|
+
"img",
|
|
28
|
+
"title",
|
|
29
|
+
"presentation"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "boolean",
|
|
34
|
+
"const": false
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"description": "Accessibility strategy for SVG icons (hidden: aria-hidden, img: role=img with aria-label, title: <title> element, presentation: role=presentation, false: no a11y attrs)"
|
|
80
38
|
},
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
|
|
85
|
-
},
|
|
86
|
-
"description": "Scripts to run before clearing all icons"
|
|
39
|
+
"trackSource": {
|
|
40
|
+
"default": true,
|
|
41
|
+
"description": "Add data-icon attribute with Iconify source name (enables update command, debugging, and identifying icon collections)",
|
|
42
|
+
"type": "boolean"
|
|
87
43
|
},
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
|
|
44
|
+
"hooks": {
|
|
45
|
+
"description": "Hooks to run at various stages",
|
|
46
|
+
"type": "object",
|
|
47
|
+
"properties": {
|
|
48
|
+
"preAdd": {
|
|
49
|
+
"type": "array",
|
|
50
|
+
"items": {
|
|
51
|
+
"type": "string"
|
|
52
|
+
},
|
|
53
|
+
"description": "Scripts to run before adding icons"
|
|
54
|
+
},
|
|
55
|
+
"postAdd": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": {
|
|
58
|
+
"type": "string"
|
|
59
|
+
},
|
|
60
|
+
"description": "Scripts to run after adding icons"
|
|
61
|
+
},
|
|
62
|
+
"preRemove": {
|
|
63
|
+
"type": "array",
|
|
64
|
+
"items": {
|
|
65
|
+
"type": "string"
|
|
66
|
+
},
|
|
67
|
+
"description": "Scripts to run before removing icons"
|
|
68
|
+
},
|
|
69
|
+
"postRemove": {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"items": {
|
|
72
|
+
"type": "string"
|
|
73
|
+
},
|
|
74
|
+
"description": "Scripts to run after removing icons"
|
|
75
|
+
},
|
|
76
|
+
"preClear": {
|
|
77
|
+
"type": "array",
|
|
78
|
+
"items": {
|
|
79
|
+
"type": "string"
|
|
80
|
+
},
|
|
81
|
+
"description": "Scripts to run before clearing all icons"
|
|
82
|
+
},
|
|
83
|
+
"postClear": {
|
|
84
|
+
"type": "array",
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "string"
|
|
87
|
+
},
|
|
88
|
+
"description": "Scripts to run after clearing all icons"
|
|
89
|
+
},
|
|
90
|
+
"preList": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"items": {
|
|
93
|
+
"type": "string"
|
|
94
|
+
},
|
|
95
|
+
"description": "Scripts to run before listing icons"
|
|
96
|
+
},
|
|
97
|
+
"postList": {
|
|
98
|
+
"type": "array",
|
|
99
|
+
"items": {
|
|
100
|
+
"type": "string"
|
|
101
|
+
},
|
|
102
|
+
"description": "Scripts to run after listing icons"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"additionalProperties": false
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"required": [
|
|
109
|
+
"$schema",
|
|
110
|
+
"output",
|
|
111
|
+
"typescript",
|
|
112
|
+
"trackSource"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"oneOf": [
|
|
117
|
+
{
|
|
118
|
+
"type": "object",
|
|
119
|
+
"properties": {
|
|
120
|
+
"framework": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"const": "react",
|
|
123
|
+
"description": "React framework"
|
|
124
|
+
},
|
|
125
|
+
"react": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"properties": {
|
|
128
|
+
"forwardRef": {
|
|
129
|
+
"default": false,
|
|
130
|
+
"description": "Wrap icon components with forwardRef",
|
|
131
|
+
"type": "boolean"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"required": [
|
|
135
|
+
"forwardRef"
|
|
136
|
+
],
|
|
137
|
+
"additionalProperties": false,
|
|
138
|
+
"description": "React-specific configuration options"
|
|
139
|
+
}
|
|
92
140
|
},
|
|
93
|
-
"
|
|
141
|
+
"required": [
|
|
142
|
+
"framework"
|
|
143
|
+
]
|
|
94
144
|
},
|
|
95
|
-
|
|
96
|
-
"type": "
|
|
97
|
-
"
|
|
98
|
-
"
|
|
145
|
+
{
|
|
146
|
+
"type": "object",
|
|
147
|
+
"properties": {
|
|
148
|
+
"framework": {
|
|
149
|
+
"type": "string",
|
|
150
|
+
"const": "preact",
|
|
151
|
+
"description": "Preact framework"
|
|
152
|
+
},
|
|
153
|
+
"preact": {
|
|
154
|
+
"type": "object",
|
|
155
|
+
"properties": {
|
|
156
|
+
"forwardRef": {
|
|
157
|
+
"default": false,
|
|
158
|
+
"description": "Wrap icon components with forwardRef",
|
|
159
|
+
"type": "boolean"
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"required": [
|
|
163
|
+
"forwardRef"
|
|
164
|
+
],
|
|
165
|
+
"additionalProperties": false,
|
|
166
|
+
"description": "Preact-specific configuration options"
|
|
167
|
+
}
|
|
99
168
|
},
|
|
100
|
-
"
|
|
169
|
+
"required": [
|
|
170
|
+
"framework"
|
|
171
|
+
]
|
|
101
172
|
},
|
|
102
|
-
|
|
103
|
-
"type": "
|
|
104
|
-
"
|
|
105
|
-
"
|
|
173
|
+
{
|
|
174
|
+
"type": "object",
|
|
175
|
+
"properties": {
|
|
176
|
+
"framework": {
|
|
177
|
+
"type": "string",
|
|
178
|
+
"const": "solid",
|
|
179
|
+
"description": "Solid framework"
|
|
180
|
+
},
|
|
181
|
+
"solid": {
|
|
182
|
+
"type": "object",
|
|
183
|
+
"properties": {},
|
|
184
|
+
"additionalProperties": false,
|
|
185
|
+
"description": "Solid-specific configuration options"
|
|
186
|
+
}
|
|
106
187
|
},
|
|
107
|
-
"
|
|
188
|
+
"required": [
|
|
189
|
+
"framework"
|
|
190
|
+
]
|
|
108
191
|
}
|
|
109
|
-
|
|
110
|
-
"additionalProperties": false
|
|
192
|
+
]
|
|
111
193
|
}
|
|
112
|
-
},
|
|
113
|
-
"required": [
|
|
114
|
-
"$schema",
|
|
115
|
-
"output",
|
|
116
|
-
"framework",
|
|
117
|
-
"typescript",
|
|
118
|
-
"trackSource"
|
|
119
194
|
],
|
|
120
|
-
"additionalProperties": false,
|
|
121
195
|
"title": "Denji Configuration Schema",
|
|
122
|
-
"description": "Schema for Denji configuration file"
|
|
196
|
+
"description": "Schema for Denji configuration file",
|
|
197
|
+
"unevaluatedProperties": false
|
|
123
198
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as e}from"commander";import t from"node:path";import{cancel as n,confirm as r,intro as i,isCancel as a,outro as o,select as s,text as c}from"@clack/prompts";import{z as l}from"zod";import u from"node:fs/promises";import d from"picocolors";import{spawn as f}from"node:child_process";import{transform as p}from"@svgr/core";import{parseSync as m}from"oxc-parser";var h=`denji`,g=`A CLI tool to manage your SVG icons`,_=`0.2.0`;const v=l.enum([`react`]),y=l.enum([`hidden`,`img`,`title`,`presentation`]).or(l.literal(!1)),b=l.object({$schema:l.string().optional().default(`https://denji-docs.vercel.app/configuration_schema.json`).describe(`The URL of the JSON Schema for this configuration file (e.g., './node_modules/denji/configuration_schema.json')`),output:l.string().describe(`The output file path for generated icon components (e.g., './src/icons.tsx')`),framework:v.describe(`The framework to generate icon components for`),typescript:l.boolean().default(!0).describe(`Whether to generate TypeScript code`),a11y:y.optional().describe(`Accessibility strategy for SVG icons (hidden: aria-hidden, img: role=img with aria-label, title: <title> element, presentation: role=presentation, false: no a11y attrs)`),trackSource:l.boolean().default(!0).describe(`Add data-icon attribute with Iconify source name (enables update command, debugging, and identifying icon collections)`),hooks:l.object({preAdd:l.array(l.string()).describe(`Scripts to run before adding icons`),postAdd:l.array(l.string()).describe(`Scripts to run after adding icons`),preRemove:l.array(l.string()).describe(`Scripts to run before removing icons`),postRemove:l.array(l.string()).describe(`Scripts to run after removing icons`),preClear:l.array(l.string()).describe(`Scripts to run before clearing all icons`),postClear:l.array(l.string()).describe(`Scripts to run after clearing all icons`),preList:l.array(l.string()).describe(`Scripts to run before listing icons`),postList:l.array(l.string()).describe(`Scripts to run after listing icons`)}).partial().optional().describe(`Hooks to run at various stages`)}).meta({title:`Denji Configuration Schema`,description:`Schema for Denji configuration file`}),x=`denji.json`;var S=class{constructor(e){this.value=e}isOk(){return!0}isErr(){return!1}},C=class{constructor(e){this.error=e}isOk(){return!1}isErr(){return!0}};async function w(e,t,n){try{return await u.writeFile(e,t,n),new S(null)}catch{return new C(`Failed to write file.`)}}async function T(e,t){try{return new S(await u.readFile(e,t))}catch{return new C(`Failed to read file.`)}}async function ee(e,t){try{return await u.mkdir(e,t),new S(null)}catch{return new C(`Failed to create directory.`)}}async function E(e,t){try{return await u.access(e,t),!0}catch{return!1}}async function D(e){let n=t.join(e,x);if(!await E(n))return new C(`${x} not found. Run "denji init" first.`);let r=await T(n,`utf-8`);if(r.isErr())return new C(`Failed to read ${x}`);try{let e=b.safeParse(JSON.parse(r.value));return e.success?new S(e.data):new C(`Invalid ${x}: ${e.error.message}`)}catch{return new C(`Invalid JSON in ${x}`)}}const O=console,k={error(e){O.error(d.red(e))},warn(e){O.warn(d.yellow(e))},info(e){O.info(d.cyan(e))},success(e){O.info(d.green(e))},break(){O.info(``)}};function A(e){k.error(`Something went wrong. Please check the error below for more details.`),k.error(`If the problem persists, please open an issue on GitHub.`),k.break(),k.error(e),k.break(),process.exit(1)}async function j(e,t){if(!e||e.length===0)return new S(null);for(let n of e){k.info(`Running: ${n}`);let e=await te(n,t);if(e.isErr())return e}return new S(null)}function te(e,t){return new Promise(n=>{let r=f(e,{cwd:t,shell:!0,stdio:`inherit`});r.on(`error`,e=>{n(new C(`Hook failed: ${e.message}`))}),r.on(`close`,t=>{n(t===0?new S(null):new C(`Hook "${e}" exited with code ${t}`))})})}const M=/^[a-z0-9]+(-[a-z0-9]+)*$/;function ne(e){let t=e.split(`:`);if(t.length!==2)return new C(`Invalid icon format "${e}". Expected "prefix:name" (e.g., mdi:home)`);let[n,r]=t;return M.test(n)?M.test(r)?new S({prefix:n,name:r}):new C(`Invalid name "${r}". Must match: lowercase letters, numbers, hyphens`):new C(`Invalid prefix "${n}". Must match: lowercase letters, numbers, hyphens`)}const re=/[-_]/,N=/^\d/;function P(e){let t=e.split(`:`)[1];if(!t)throw Error(`Invalid icon format: ${e}`);let n=t.split(re).filter(Boolean).map(e=>e.at(0)?.toUpperCase()+e.slice(1).toLowerCase()),r=n[0];for(;r&&N.test(r);)n.push(n.shift()??``),r=n[0];return n.join(``)}async function F(e){try{let{loadIcon:t,buildIcon:n}=await import(`iconify-icon`),r=await t(e);if(!r)return new C(`Icon "${e}" not found`);let i=n(r,{height:`1em`});return i?new S(`<svg xmlns="http://www.w3.org/2000/svg" ${Object.entries(i.attributes).map(([e,t])=>`${e}="${t}"`).join(` `)}>${i.body}</svg>`):new C(`Failed to build icon "${e}"`)}catch{return new C(`Icon "${e}" not found`)}}const I=/<svg[\s\S]*<\/svg>/,L=/<svg([^>]*)>/;function R(e){return e.replace(/([a-z])([A-Z])/g,`$1 $2`)}function z(e,t){switch(e){case`hidden`:return{"aria-hidden":`true`};case`img`:return{role:`img`,"aria-label":R(t)};case`presentation`:return{role:`presentation`};default:return{}}}async function B(e,t,n={}){let{a11y:r,trackSource:i,iconName:a}=n,o=z(r,t);i&&a&&(o[`data-icon`]=a);let s=(await p(e,{plugins:[`@svgr/plugin-svgo`,`@svgr/plugin-jsx`],svgoConfig:{plugins:[`preset-default`,`convertStyleToAttrs`,`sortAttrs`,`mergePaths`]},jsxRuntime:`automatic`,typescript:!1,expandProps:`end`,svgProps:o,titleProp:r===`title`},{componentName:`Icon`})).match(I);if(!s)throw Error(`Failed to extract SVG from SVGR output`);let c=s[0];if(r===`title`){let e=R(t);c=c.replace(L,`<svg$1><title>${e}</title>`)}return`${t}: (props) => (${c})`}function V(e){let t=m(`icons.tsx`,e).program,n=[],r=0,i=0;for(let e of t.body)if(e.type===`ExportNamedDeclaration`&&e.declaration?.type===`VariableDeclaration`){for(let t of e.declaration.declarations)if(t.id?.type===`Identifier`&&t.id.name===`Icons`){let e=t.init;if(e?.type===`TSSatisfiesExpression`&&(e=e.expression),e?.type===`TSAsExpression`&&(e=e.expression),e?.type===`ObjectExpression`){r=e.start+1,i=e.end-1;for(let t of e.properties)t.type!==`SpreadElement`&&t.key?.type===`Identifier`&&n.push({name:t.key.name,start:t.start,end:t.end})}}}return{icons:n,objectStart:r,objectEnd:i}}function H(e){let{icons:t}=V(e);return t.map(e=>e.name)}function U(e,t,n){let{icons:r,objectStart:i,objectEnd:a}=V(e),o=r.findIndex(e=>e.name.localeCompare(t)>0);if(r.length===0)return`${e.slice(0,i)}\n ${n},\n${e.slice(a)}`;if(o===-1){let t=r.at(-1);if(!t)throw Error(`Failed to find last icon`);return`${e.slice(0,t.end)},\n ${n}${e.slice(t.end)}`}if(o===0){let t=r[0];if(!t)throw Error(`Failed to find first icon`);return`${e.slice(0,t.start)}${n},\n ${e.slice(t.start)}`}let s=r[o];if(!s)throw Error(`Failed to find target icon`);let c=s.start;return`${e.slice(0,c)}${n},\n ${e.slice(c)}`}function W(e,t,n){let{icons:r}=V(e),i=r.find(e=>e.name===t);return i?`${e.slice(0,i.start)}${n}${e.slice(i.end)}`:e}function G(e,t){let{icons:n}=V(e),r=n.findIndex(e=>e.name===t);if(r===-1)return e;let i=n[r];if(!i)return e;let a=r===n.length-1;if(n.length===1)return`${e.slice(0,i.start).trimEnd()}${e.slice(i.end).trimStart()}`;if(a){let t=n[r-1];return t?e.slice(t.end,i.start).indexOf(`,`)===-1?`${e.slice(0,t.end)}\n${e.slice(i.end).trimStart()}`:`${e.slice(0,t.end)}${e.slice(i.end)}`:e}let o=n[r+1];return o?`${e.slice(0,i.start)}${e.slice(o.start)}`:e}const K=`Operation cancelled.`;async function q(e,t=K){let i=await r(e);return a(i)&&(n(t),process.exit(0)),i}async function J(e,t=K){let r=await s(e);return a(r)&&(n(t),process.exit(0)),r}async function Y(e,t=K){let r=await c({...e,placeholder:e.placeholder??e.defaultValue});return a(r)&&(n(t),process.exit(0)),r}const ie=new e().name(`add`).description(`Add icons to your project`).argument(`<icons...>`,`Icon names (e.g., mdi:home lucide:check)`).option(`--name <name>`,`Custom component name (single icon only)`).option(`--a11y <strategy>`,`Accessibility strategy (overrides config)`).option(`-c, --cwd <cwd>`,`The working directory. Defaults to the current directory.`,process.cwd()).action(async(e,t)=>{i(`denji add`);let n=await ae(e,t);n.isErr()&&A(n.error),o(`Added ${e.length} icon(s)`)});async function ae(e,n){if(!await E(n.cwd))return new C(`Directory does not exist: ${n.cwd}`);if(n.name&&e.length>1)return new C(`--name can only be used with a single icon`);for(let t of e){let e=ne(t);if(e.isErr())return e}let r;if(n.a11y!==void 0){let e=n.a11y===`false`?!1:n.a11y,t=y.safeParse(e);if(!t.success)return new C(`Invalid a11y strategy: ${n.a11y}. Use: hidden, img, title, presentation, false`);r=t.data}let i=await D(n.cwd);if(i.isErr())return i;let a=i.value,o=await j(a.hooks?.preAdd,n.cwd);if(o.isErr())return o;let s=t.resolve(n.cwd,a.output);if(!await E(s))return new C(`Icons file not found: ${a.output}. Run "denji init" first.`);let c=await T(s,`utf-8`);if(c.isErr())return new C(`Failed to read icons file: ${a.output}`);let l=c.value,u=H(l),d=0;for(let t of e){let e=n.name??P(t);if(u.includes(e)&&!await q({message:`Icon "${e}" already exists. Overwrite?`,initialValue:!1})){k.info(`Skipped ${e}`);continue}let i=await F(t);if(i.isErr()){k.error(`Failed to fetch ${t}: ${i.error}`);continue}let o=await B(i.value,e,{a11y:r??a.a11y,trackSource:a.trackSource??!0,iconName:t});u.includes(e)?(l=W(l,e,o),k.success(`Replaced ${e}`)):(l=U(l,e,o),u.push(e),k.success(`Added ${e}`)),d++}if(d>0){if((await w(s,l)).isErr())return new C(`Failed to write icons file: ${a.output}`);let e=await j(a.hooks?.postAdd,n.cwd);if(e.isErr())return e}return new S(null)}function X(e){return e.framework===`react`?e.typescript?`export type IconProps = React.ComponentProps<"svg">;
|
|
3
|
-
export type Icon = (props: IconProps) => React.JSX.Element;
|
|
4
|
-
|
|
5
|
-
export const Icons = {} as const satisfies Record<string, Icon>;
|
|
6
|
-
`:`export const Icons = {};
|
|
7
|
-
`:``}const oe=new e().name(`clear`).description(`Remove all icons from your project`).aliases([`clr`,`reset`]).option(`-c, --cwd <cwd>`,`The working directory. Defaults to the current directory.`,process.cwd()).option(`-y, --yes`,`Skip confirmation prompt`,!1).action(async e=>{i(`denji clear`);let t=await se(e);t.isErr()&&A(t.error),o(`All icons removed`)});async function se(e){if(!await E(e.cwd))return new C(`Directory does not exist: ${e.cwd}`);let r=await D(e.cwd);if(r.isErr())return r;let i=r.value,a=t.resolve(e.cwd,i.output);if(!await E(a))return new C(`Icons file not found: ${i.output}. Run "denji init" first.`);let o=await T(a,`utf-8`);if(o.isErr())return new C(`Failed to read icons file: ${i.output}`);let s=o.value,c=H(s);if(c.length===0)return k.info(`No icons to remove`),new S(null);e.yes||await q({message:`Remove all ${c.length} icon(s)?`,initialValue:!1})||n(K);let l=await j(i.hooks?.preClear,e.cwd);if(l.isErr())return l;if((await w(a,X(i))).isErr())return new C(`Failed to write icons file: ${i.output}`);let u=await j(i.hooks?.postClear,e.cwd);return u.isErr()?u:new S(null)}const ce=new e().name(`init`).description(`Initialize a new denji project`).option(`-c, --cwd <cwd>`,`The working directory. Defaults to the current directory.`,process.cwd()).option(`--output <file>`,`Output file path for icons`).option(`--framework <framework>`,`Framework to use`).option(`--typescript`,`Use TypeScript`,!0).option(`--no-typescript`,`Use JavaScript`).option(`--a11y <strategy>`,`Accessibility strategy for SVG icons`).option(`--track-source`,`Track Iconify source names`,!0).option(`--no-track-source`,`Don't track Iconify source names`).action(async e=>{i(`denji init`);let t=await le(e);t.isErr()&&A(t.error),o(`Project initialized successfully!`)});async function le(e){if(!await E(e.cwd))return new C(`Directory does not exist: ${e.cwd}`);let n=t.join(e.cwd,x);if(await E(n))return new C(`${x} already exists in ${e.cwd}`);let r=await ue(e);if(r.isErr())return r;let i=r.value,a=de(i);if(a.isErr())return a;let o=t.resolve(e.cwd,i.output);if(await E(o))return new C(`Output file already exists: ${o}`);let s=t.dirname(o);return!await E(s)&&(await ee(s,{recursive:!0})).isErr()?new C(`Failed to create directory: ${s}`):(await w(n,JSON.stringify(i,null,2))).isErr()?new C(`Failed to write ${x}`):(k.success(`Created ${x}`),(await w(o,X(i))).isErr()?new C(`Failed to write ${i.output}`):(k.success(`Created ${i.output}`),new S(null)))}async function ue(e){let t=e.output??await Y({message:`Where should icons be created?`,defaultValue:`./src/icons.tsx`}),n=e.framework??await J({message:`Which framework are you using?`,options:[{value:`react`,label:`React`}],initialValue:`react`}),r=v.safeParse(n);if(!r.success)return new C(`Invalid framework: ${n}. Use: react`);let i=r.data,a=e.typescript??await q({message:`Use TypeScript?`,initialValue:!0}),o=e.a11y??await J({message:`Which accessibility strategy should be used?`,options:[{value:`hidden`,label:`aria-hidden`,hint:`Hide from screen readers (decorative icons)`},{value:`img`,label:`role="img"`,hint:`Meaningful icon with aria-label`},{value:`title`,label:`title`,hint:`Add <title> element inside SVG`},{value:`presentation`,label:`presentation`,hint:`role=presentation (older pattern)`},{value:!1,label:`false`,hint:`No accessibility attributes`}],initialValue:`hidden`}),s=y.safeParse(o);if(!s.success)return new C(`Invalid a11y strategy: ${o}. Use: hidden, img, title, presentation, false`);let c=s.data,l=e.trackSource??await q({message:`Track Iconify source names? (for update command, debugging, identifying collections)`,initialValue:!0});return new S(b.parse({output:t,framework:i,typescript:a,a11y:c,trackSource:l}))}function de(e){let n=t.extname(e.output);if(e.framework===`react`){if(e.typescript&&n!==`.tsx`)return new C(`Invalid extension "${n}" for React + TypeScript. Use ".tsx"`);if(!e.typescript&&n!==`.jsx`)return new C(`Invalid extension "${n}" for React + JavaScript. Use ".jsx"`)}return new S(null)}const Z=new e().name(`list`).description(`List all icons in your project`).option(`-c, --cwd <cwd>`,`The working directory. Defaults to the current directory.`,process.cwd()).option(`--json`,`Output icons as JSON`).action(async e=>{e.json||i(`denji list`);let t=await fe(e);t.isErr()&&A(t.error),e.json||o(`Done`)});async function fe(e){if(!await E(e.cwd))return new C(`Directory does not exist: ${e.cwd}`);let n=await D(e.cwd);if(n.isErr())return n;let r=n.value,i=t.resolve(e.cwd,r.output);if(!await E(i))return new C(`Icons file not found: ${r.output}. Run "denji init" first.`);let a=await T(i,`utf-8`);if(a.isErr())return new C(`Failed to read icons file: ${r.output}`);let o=await j(r.hooks?.preList,e.cwd);if(o.isErr())return o;let s=a.value,{icons:c}=V(s);if(e.json){let t={count:c.length,output:r.output,icons:c.map(e=>e.name)};console.info(JSON.stringify(t,null,2));let n=await j(r.hooks?.postList,e.cwd);return n.isErr()?n:new S(null)}if(c.length===0){k.info(`No icons found in ${r.output}`);let t=await j(r.hooks?.postList,e.cwd);return t.isErr()?t:new S(null)}k.success(`Found ${c.length} icon(s) in ${r.output}`),k.break(),k.info(`Icons:`);for(let e of c)k.info(` • ${e.name}`);let l=await j(r.hooks?.postList,e.cwd);return l.isErr()?l:new S(null)}const pe=new e().name(`remove`).description(`Remove icons from your project`).argument(`<icons...>`,`Icon component names (e.g., Home Check)`).aliases([`rm`,`delete`,`del`]).option(`-c, --cwd <cwd>`,`The working directory. Defaults to the current directory.`,process.cwd()).action(async(e,t)=>{i(`denji remove`);let n=await me(e,t);n.isErr()&&A(n.error),o(`Removed ${e.length} icon(s)`)});async function me(e,n){if(!await E(n.cwd))return new C(`Directory does not exist: ${n.cwd}`);let r=await D(n.cwd);if(r.isErr())return r;let i=r.value,a=t.resolve(n.cwd,i.output);if(!await E(a))return new C(`Icons file not found: ${i.output}. Run "denji init" first.`);let o=await T(a,`utf-8`);if(o.isErr())return new C(`Failed to read icons file: ${i.output}`);let s=o.value,c=H(s),l=[];for(let t of e)c.includes(t)||l.push(t);if(l.length>0)return new C(`Icon(s) not found: ${l.join(`, `)}`);let u=await j(i.hooks?.preRemove,n.cwd);if(u.isErr())return u;if(c.length-e.length===0){if((await w(a,X(i))).isErr())return new C(`Failed to write icons file: ${i.output}`);for(let t of e)k.success(`Removed ${t}`);let t=await j(i.hooks?.postRemove,n.cwd);return t.isErr()?t:new S(null)}for(let t of e)s=G(s,t),k.success(`Removed ${t}`);if((await w(a,s)).isErr())return new C(`Failed to write icons file: ${i.output}`);let d=await j(i.hooks?.postRemove,n.cwd);return d.isErr()?d:new S(null)}const Q=()=>process.exit(0);process.on(`SIGINT`,Q),process.on(`SIGTERM`,Q);const $=new e().name(h).description(g).version(_,`-v, --version`,`Display the version number.`);$.addCommand(ie),$.addCommand(oe),$.addCommand(ce),$.addCommand(Z),$.addCommand(pe),$.parse();export{};
|
|
2
|
+
import{Command as e}from"commander";import t from"node:path";import{cancel as n,confirm as r,intro as i,isCancel as a,multiselect as o,outro as s,select as c,text as l}from"@clack/prompts";import{_default as u,array as d,boolean as f,describe as p,discriminatedUnion as m,enum as h,intersection as g,literal as _,meta as v,object as y,optional as b,partial as x,string as S,union as ee}from"zod/mini";import C from"node:fs/promises";import{spawn as te}from"node:child_process";import w from"picocolors";import{parseSync as ne}from"oxc-parser";var re=`denji`,ie=`A CLI tool to manage your SVG icons`,ae=`0.3.0`;const T=y({forwardRef:u(f(),!1).check(p(`Wrap icon components with forwardRef`))}).check(p(`Preact-specific configuration options`)),E=y({forwardRef:u(f(),!1).check(p(`Wrap icon components with forwardRef`))}).check(p(`React-specific configuration options`)),D=y({}).check(p(`Solid-specific configuration options`)),O=ee([h([`hidden`,`img`,`title`,`presentation`]),_(!1)]).check(p(`Accessibility strategy for SVG icons (hidden: aria-hidden, img: role=img with aria-label, title: <title> element, presentation: role=presentation, false: no a11y attrs)`)),k=g(y({$schema:u(b(S()),`https://denji-docs.vercel.app/configuration_schema.json`).check(p(`The URL of the JSON Schema for this configuration file (e.g., './node_modules/denji/configuration_schema.json')`)),output:S().check(p(`The output file path for generated icon components (e.g., './src/icons.tsx')`)),typescript:u(f(),!0).check(p(`Whether to generate TypeScript code`)),a11y:b(O),trackSource:u(f(),!0).check(p(`Add data-icon attribute with Iconify source name (enables update command, debugging, and identifying icon collections)`)),hooks:b(x(y({preAdd:d(S()).check(p(`Scripts to run before adding icons`)),postAdd:d(S()).check(p(`Scripts to run after adding icons`)),preRemove:d(S()).check(p(`Scripts to run before removing icons`)),postRemove:d(S()).check(p(`Scripts to run after removing icons`)),preClear:d(S()).check(p(`Scripts to run before clearing all icons`)),postClear:d(S()).check(p(`Scripts to run after clearing all icons`)),preList:d(S()).check(p(`Scripts to run before listing icons`)),postList:d(S()).check(p(`Scripts to run after listing icons`))}))).check(p(`Hooks to run at various stages`))}),m(`framework`,[y({framework:_(`react`).check(p(`React framework`)),react:b(E)}),y({framework:_(`preact`).check(p(`Preact framework`)),preact:b(T)}),y({framework:_(`solid`).check(p(`Solid framework`)),solid:b(D)})])).check(v({title:`Denji Configuration Schema`})).check(p(`Schema for Denji configuration file`)),oe=h([`react`,`preact`,`solid`]),A=`denji.json`;async function se(e){switch(e){case`react`:{let{reactStrategy:e}=await import(`./strategy-bLzeYhwz.mjs`);return e}case`preact`:{let{preactStrategy:e}=await import(`./strategy-HhTswc-Y.mjs`);return e}case`solid`:{let{solidStrategy:e}=await import(`./strategy-C4DjhM4A.mjs`);return e}default:throw Error(`Unknown framework: ${e}`)}}var j=class{constructor(e){this.value=e}isOk(){return!0}isErr(){return!1}},M=class{constructor(e){this.error=e}isOk(){return!1}isErr(){return!0}};async function ce(e,t,n){try{return await C.writeFile(e,t,n),new j(null)}catch{return new M(`Failed to write file.`)}}async function N(e,t){try{return new j(await C.readFile(e,t))}catch{return new M(`Failed to read file.`)}}async function le(e,t){try{return await C.mkdir(e,t),new j(null)}catch{return new M(`Failed to create directory.`)}}async function P(e,t){try{return await C.access(e,t),!0}catch{return!1}}async function ue(e){let n=t.join(e,A);if(!await P(n))return new M(`${A} not found. Run "denji init" first.`);let r=await N(n,`utf-8`);if(r.isErr())return new M(`Failed to read ${A}`);try{let e=k.safeParse(JSON.parse(r.value));return e.success?new j(e.data):new M(`Invalid ${A}: ${e.error.message}`)}catch{return new M(`Invalid JSON in ${A}`)}}const F=console,I={error(e){F.error(w.red(e))},warn(e){F.warn(w.yellow(e))},info(e){F.info(w.cyan(e))},success(e){F.info(w.green(e))},break(){F.info(``)}};async function de(e,t){if(!e||e.length===0)return new j(null);for(let n of e){I.info(`Running: ${n}`);let e=await fe(n,t);if(e.isErr())return e}return new j(null)}function fe(e,t){return new Promise(n=>{let r=te(e,{cwd:t,shell:!0,stdio:`inherit`});r.on(`error`,e=>{n(new M(`Hook failed: ${e.message}`))}),r.on(`close`,t=>{n(t===0?new j(null):new M(`Hook "${e}" exited with code ${t}`))})})}const L=/^[a-z0-9]+(-[a-z0-9]+)*$/;function pe(e){let t=e.split(`:`);if(t.length!==2)return new M(`Invalid icon format "${e}". Expected "prefix:name" (e.g., mdi:home)`);let[n,r]=t;return L.test(n)?L.test(r)?new j({prefix:n,name:r}):new M(`Invalid name "${r}". Must match: lowercase letters, numbers, hyphens`):new M(`Invalid prefix "${n}". Must match: lowercase letters, numbers, hyphens`)}const me=/[-_]/,R=/^\d/;function z(e){let t=e.split(`:`)[1];if(!t)throw Error(`Invalid icon format: ${e}`);let n=t.split(me).filter(Boolean).map(e=>e.at(0)?.toUpperCase()+e.slice(1).toLowerCase()),r=n[0];for(;r&&R.test(r);)n.push(n.shift()??``),r=n[0];return n.join(``)}async function he(e){try{let{loadIcon:t,buildIcon:n}=await import(`iconify-icon`),r=await t(e);if(!r)return new M(`Icon "${e}" not found`);let i=n(r,{height:`1em`});return i?new j(`<svg xmlns="http://www.w3.org/2000/svg" ${Object.entries(i.attributes).map(([e,t])=>`${e}="${t}"`).join(` `)}>${i.body}</svg>`):new M(`Failed to build icon "${e}"`)}catch{return new M(`Icon "${e}" not found`)}}function B(e){let t=ne(`icons.tsx`,e).program,n=[],r=0,i=0;for(let e of t.body)if(e.type===`ExportNamedDeclaration`&&e.declaration?.type===`VariableDeclaration`){for(let t of e.declaration.declarations)if(t.id?.type===`Identifier`&&t.id.name===`Icons`){let e=t.init;if(e?.type===`TSSatisfiesExpression`&&(e=e.expression),e?.type===`TSAsExpression`&&(e=e.expression),e?.type===`ObjectExpression`){r=e.start+1,i=e.end-1;for(let t of e.properties)t.type!==`SpreadElement`&&t.key?.type===`Identifier`&&n.push({name:t.key.name,start:t.start,end:t.end})}}}return{icons:n,objectStart:r,objectEnd:i}}function ge(e){let{icons:t}=B(e);return t.map(e=>e.name)}function _e(e,t,n){let{icons:r,objectStart:i,objectEnd:a}=B(e),o=r.findIndex(e=>e.name.localeCompare(t)>0);if(r.length===0)return`${e.slice(0,i)}\n ${n},\n${e.slice(a)}`;if(o===-1){let t=r.at(-1);if(!t)throw Error(`Failed to find last icon`);return`${e.slice(0,t.end)},\n ${n}${e.slice(t.end)}`}if(o===0){let t=r[0];if(!t)throw Error(`Failed to find first icon`);return`${e.slice(0,t.start)}${n},\n ${e.slice(t.start)}`}let s=r[o];if(!s)throw Error(`Failed to find target icon`);let c=s.start;return`${e.slice(0,c)}${n},\n ${e.slice(c)}`}function ve(e,t,n){let{icons:r}=B(e),i=r.find(e=>e.name===t);return i?`${e.slice(0,i.start)}${n}${e.slice(i.end)}`:e}function ye(e,t){let{icons:n}=B(e),r=n.findIndex(e=>e.name===t);if(r===-1)return e;let i=n[r];if(!i)return e;let a=r===n.length-1;if(n.length===1)return`${e.slice(0,i.start).trimEnd()}${e.slice(i.end).trimStart()}`;if(a){let t=n[r-1];return t?e.slice(t.end,i.start).indexOf(`,`)===-1?`${e.slice(0,t.end)}\n${e.slice(i.end).trimStart()}`:`${e.slice(0,t.end)}${e.slice(i.end)}`:e}let o=n[r+1];return o?`${e.slice(0,i.start)}${e.slice(o.start)}`:e}const V=`Operation cancelled.`;async function H(e,t=V){let i=await r(e);return a(i)&&(n(t),process.exit(0)),i}async function be(e,t=V){let r=await c(e);return a(r)&&(n(t),process.exit(0)),r}async function xe(e,t=V){let r=await o(e);return a(r)&&(n(t),process.exit(0)),r}async function Se(e,t=V){let r=await l({...e,placeholder:e.placeholder??e.defaultValue});return a(r)&&(n(t),process.exit(0)),r}const U={access:P,readFile:N,writeFile:ce,mkdir:le},W={loadConfig:ue},G={runHooks:de},K={fetchIcon:he,validateIconName:pe,toComponentName:z,parseIconsFile:B,getExistingIconNames:ge,insertIconAlphabetically:_e,replaceIcon:ve,removeIcon:ye},q={confirm:H,select:be,text:Se,multiselect:xe},J=I,Y={createStrategy:se},Ce={fs:U,config:W,hooks:G,icons:K,logger:J},we={fs:U,config:W,hooks:G,icons:K,prompts:q,logger:J,frameworks:Y},Te={fs:U,config:W,hooks:G,icons:K,prompts:q,logger:J,frameworks:Y},Ee={fs:U,prompts:q,logger:J,frameworks:Y},De={fs:U,config:W,hooks:G,icons:K,prompts:q,logger:J,frameworks:Y};function X(e){I.error(`Something went wrong. Please check the error below for more details.`),I.error(`If the problem persists, please open an issue on GitHub.`),I.break(),I.error(e),I.break(),process.exit(1)}var Oe=class{constructor(e){this.deps=e}async run(e,n){let{fs:r,config:i,hooks:a,icons:o,prompts:s,logger:c,frameworks:l}=this.deps;if(!await r.access(n.cwd))return new M(`Directory does not exist: ${n.cwd}`);if(n.name&&e.length>1)return new M(`--name can only be used with a single icon`);for(let t of e){let e=o.validateIconName(t);if(e.isErr())return e}let u;if(n.a11y!==void 0){let e=n.a11y===`false`?!1:n.a11y,t=O.safeParse(e);if(!t.success)return new M(`Invalid a11y strategy: ${n.a11y}. Use: hidden, img, title, presentation, false`);u=t.data}let d=await i.loadConfig(n.cwd);if(d.isErr())return d;let f=d.value,p=await l.createStrategy(f.framework),m=await a.runHooks(f.hooks?.preAdd,n.cwd);if(m.isErr())return m;let h=t.resolve(n.cwd,f.output);if(!await r.access(h))return new M(`Icons file not found: ${f.output}. Run "denji init" first.`);let g=await r.readFile(h,`utf-8`);if(g.isErr())return new M(`Failed to read icons file: ${f.output}`);let _=g.value,v=o.getExistingIconNames(_),y=0,b=f[p.getConfigKey()]??{},x=p.isForwardRefEnabled(b);for(let t of e){let e=n.name??o.toComponentName(t);if(v.includes(e)&&!await s.confirm({message:`Icon "${e}" already exists. Overwrite?`,initialValue:!1})){c.info(`Skipped ${e}`);continue}let r=await o.fetchIcon(t);if(r.isErr()){c.error(`Failed to fetch ${t}: ${r.error}`);continue}let i=await p.transformSvg(r.value,{a11y:u??f.a11y,trackSource:f.trackSource??!0,iconName:t,componentName:e},b);if(x&&v.length===0&&y===0){let e=p.getForwardRefImportSource();_.includes(`import { forwardRef } from "${e}"`)||(_=`import { forwardRef } from "${e}";\n\n${_}`)}v.includes(e)?(_=o.replaceIcon(_,e,i),c.success(`Replaced ${e}`)):(_=o.insertIconAlphabetically(_,e,i),v.push(e),c.success(`Added ${e}`)),y++}if(y>0){if((await r.writeFile(h,_)).isErr())return new M(`Failed to write icons file: ${f.output}`);let e=await a.runHooks(f.hooks?.postAdd,n.cwd);if(e.isErr())return e}return new j(null)}};function ke(){return new Oe(De)}const Ae=new e().name(`add`).description(`Add icons to your project`).argument(`<icons...>`,`Icon names (e.g., mdi:home lucide:check)`).option(`--name <name>`,`Custom component name (single icon only)`).option(`--a11y <strategy>`,`Accessibility strategy (overrides config)`).option(`-c, --cwd <cwd>`,`The working directory. Defaults to the current directory.`,process.cwd()).action(async(e,t)=>{i(`denji add`);let n=await ke().run(e,t);n.isErr()&&X(n.error),s(`Added ${e.length} icon(s)`)});var je=class{constructor(e){this.deps=e}async run(e){let{fs:r,config:i,hooks:a,icons:o,prompts:s,logger:c,frameworks:l}=this.deps;if(!await r.access(e.cwd))return new M(`Directory does not exist: ${e.cwd}`);let u=await i.loadConfig(e.cwd);if(u.isErr())return u;let d=u.value,f=await l.createStrategy(d.framework),p=t.resolve(e.cwd,d.output);if(!await r.access(p))return new M(`Icons file not found: ${d.output}. Run "denji init" first.`);let m=await r.readFile(p,`utf-8`);if(m.isErr())return new M(`Failed to read icons file: ${d.output}`);let h=m.value,g=o.getExistingIconNames(h);if(g.length===0)return c.info(`No icons to remove`),new j(null);e.yes||await s.confirm({message:`Remove all ${g.length} icon(s)?`,initialValue:!1})||n(V);let _=await a.runHooks(d.hooks?.preClear,e.cwd);if(_.isErr())return _;let v=d[f.getConfigKey()]??{},y=f.getIconsTemplate({typescript:d.typescript,frameworkOptions:v});if((await r.writeFile(p,y)).isErr())return new M(`Failed to write icons file: ${d.output}`);let b=await a.runHooks(d.hooks?.postClear,e.cwd);return b.isErr()?b:new j(null)}};function Me(){return new je(we)}const Ne=new e().name(`clear`).description(`Remove all icons from your project`).aliases([`clr`,`reset`]).option(`-c, --cwd <cwd>`,`The working directory. Defaults to the current directory.`,process.cwd()).option(`-y, --yes`,`Skip confirmation prompt`,!1).action(async e=>{i(`denji clear`);let t=await Me().run(e);t.isErr()&&X(t.error),s(`All icons removed`)}),Pe=[{value:`react`,label:`React`},{value:`preact`,label:`Preact`},{value:`solid`,label:`Solid`}];function Fe(){return Pe.map(({value:e,label:t})=>({value:e,label:t}))}var Ie=class{constructor(e){this.deps=e}async run(e){let{fs:n,logger:r}=this.deps;if(!await n.access(e.cwd))return new M(`Directory does not exist: ${e.cwd}`);let i=t.join(e.cwd,A);if(await n.access(i))return new M(`${A} already exists in ${e.cwd}`);let a=await this.resolveConfig(e);if(a.isErr())return a;let{config:o,strategy:s}=a.value,c=this.validateExtension(o,s);if(c.isErr())return c;let l=t.resolve(e.cwd,o.output);if(await n.access(l))return new M(`Output file already exists: ${l}`);let u=t.dirname(l);if(!await n.access(u)&&(await n.mkdir(u,{recursive:!0})).isErr())return new M(`Failed to create directory: ${u}`);let d=JSON.stringify(o,null,2);if((await n.writeFile(i,d)).isErr())return new M(`Failed to write ${A}`);r.success(`Created ${A}`);let f=o[s.getConfigKey()]??{},p=s.getIconsTemplate({typescript:o.typescript,frameworkOptions:f});return(await n.writeFile(l,p)).isErr()?new M(`Failed to write ${o.output}`):(r.success(`Created ${o.output}`),new j(null))}async resolveConfig(e){let{prompts:t,frameworks:n}=this.deps,r=e.output??await t.text({message:`Where should icons be created?`,defaultValue:`./src/icons.tsx`}),i=e.framework??await t.select({message:`Which framework are you using?`,options:Fe(),initialValue:`react`}),a=oe.safeParse(i);if(!a.success)return new M(`Invalid framework: ${i}. Use: react, preact`);let o=a.data,s=await n.createStrategy(o),c=e.typescript??await t.confirm({message:`Use TypeScript?`,initialValue:!0}),l=e.a11y??await t.select({message:`Which accessibility strategy should be used?`,options:[{value:`hidden`,label:`aria-hidden`,hint:`Hide from screen readers (decorative icons)`},{value:`img`,label:`role="img"`,hint:`Meaningful icon with aria-label`},{value:`title`,label:`title`,hint:`Add <title> element inside SVG`},{value:`presentation`,label:`presentation`,hint:`role=presentation (older pattern)`},{value:!1,label:`false`,hint:`No accessibility attributes`}],initialValue:`hidden`}),u=O.safeParse(l);if(!u.success)return new M(`Invalid a11y strategy: ${l}. Use: hidden, img, title, presentation, false`);let d=u.data,f=e.trackSource??await t.confirm({message:`Track Iconify source names? (for update command, debugging, identifying collections)`,initialValue:!0}),p=await s.promptOptions({forwardRef:e.forwardRef});return new j({config:k.parse({output:r,framework:o,typescript:c,a11y:d,trackSource:f,[s.getConfigKey()]:p}),strategy:s})}validateExtension(e,n){let r=t.extname(e.output),i=e.typescript?n.fileExtensions.typescript:n.fileExtensions.javascript;if(r!==i){let t=e.typescript?`TypeScript`:`JavaScript`;return new M(`Invalid extension "${r}" for ${n.name} + ${t}. Use "${i}"`)}return new j(null)}};function Z(){return new Ie(Ee)}const Le=new e().name(`init`).description(`Initialize a new denji project`).option(`-c, --cwd <cwd>`,`The working directory. Defaults to the current directory.`,process.cwd()).option(`--output <file>`,`Output file path for icons`).option(`--framework <framework>`,`Framework to use`).option(`--typescript`,`Use TypeScript`).option(`--no-typescript`,`Use JavaScript`).option(`--a11y <strategy>`,`Accessibility strategy for SVG icons`).option(`--track-source`,`Track Iconify source names`).option(`--no-track-source`,`Don't track Iconify source names`).option(`--forward-ref`,`Use forwardRef for React icon components`).option(`--no-forward-ref`,`Don't use forwardRef for React icon components`).action(async e=>{i(`denji init`);let t=await Z().run(e);t.isErr()&&X(t.error),s(`Project initialized successfully!`)});var Re=class{constructor(e){this.deps=e}async run(e){let{fs:n,config:r,hooks:i,icons:a,logger:o}=this.deps;if(!await n.access(e.cwd))return new M(`Directory does not exist: ${e.cwd}`);let s=await r.loadConfig(e.cwd);if(s.isErr())return s;let c=s.value,l=t.resolve(e.cwd,c.output);if(!await n.access(l))return new M(`Icons file not found: ${c.output}. Run "denji init" first.`);let u=await n.readFile(l,`utf-8`);if(u.isErr())return new M(`Failed to read icons file: ${c.output}`);let d=await i.runHooks(c.hooks?.preList,e.cwd);if(d.isErr())return d;let f=u.value,{icons:p}=a.parseIconsFile(f);if(e.json){let t={count:p.length,output:c.output,icons:p.map(e=>e.name)};console.info(JSON.stringify(t,null,2));let n=await i.runHooks(c.hooks?.postList,e.cwd);return n.isErr()?n:new j(null)}if(p.length===0){o.info(`No icons found in ${c.output}`);let t=await i.runHooks(c.hooks?.postList,e.cwd);return t.isErr()?t:new j(null)}o.success(`Found ${p.length} icon(s) in ${c.output}`),o.break(),o.info(`Icons:`);for(let e of p)o.info(` • ${e.name}`);let m=await i.runHooks(c.hooks?.postList,e.cwd);return m.isErr()?m:new j(null)}};function ze(){return new Re(Ce)}const Be=new e().name(`list`).description(`List all icons in your project`).option(`-c, --cwd <cwd>`,`The working directory. Defaults to the current directory.`,process.cwd()).option(`--json`,`Output icons as JSON`).action(async e=>{e.json||i(`denji list`);let t=await ze().run(e);t.isErr()&&X(t.error),e.json||s(`Done`)});var Ve=class{constructor(e){this.deps=e}async run(e,n){let{fs:r,config:i,hooks:a,icons:o,logger:s,frameworks:c}=this.deps;if(!await r.access(n.cwd))return new M(`Directory does not exist: ${n.cwd}`);let l=await i.loadConfig(n.cwd);if(l.isErr())return l;let u=l.value,d=await c.createStrategy(u.framework),f=t.resolve(n.cwd,u.output);if(!await r.access(f))return new M(`Icons file not found: ${u.output}. Run "denji init" first.`);let p=await r.readFile(f,`utf-8`);if(p.isErr())return new M(`Failed to read icons file: ${u.output}`);let m=p.value,h=o.getExistingIconNames(m),g=[];for(let t of e)h.includes(t)||g.push(t);if(g.length>0)return new M(`Icon(s) not found: ${g.join(`, `)}`);let _=await a.runHooks(u.hooks?.preRemove,n.cwd);if(_.isErr())return _;if(h.length-e.length===0){let t=u[d.getConfigKey()]??{},i=d.getIconsTemplate({typescript:u.typescript,frameworkOptions:t});if((await r.writeFile(f,i)).isErr())return new M(`Failed to write icons file: ${u.output}`);for(let t of e)s.success(`Removed ${t}`);let o=await a.runHooks(u.hooks?.postRemove,n.cwd);return o.isErr()?o:new j(null)}for(let t of e)m=o.removeIcon(m,t),s.success(`Removed ${t}`);if((await r.writeFile(f,m)).isErr())return new M(`Failed to write icons file: ${u.output}`);let v=await a.runHooks(u.hooks?.postRemove,n.cwd);return v.isErr()?v:new j(null)}};function He(){return new Ve(Te)}const Ue=new e().name(`remove`).description(`Remove icons from your project`).argument(`<icons...>`,`Icon component names (e.g., Home Check)`).aliases([`rm`,`delete`,`del`]).option(`-c, --cwd <cwd>`,`The working directory. Defaults to the current directory.`,process.cwd()).action(async(e,t)=>{i(`denji remove`);let n=await He().run(e,t);n.isErr()&&X(n.error),s(`Removed ${e.length} icon(s)`)}),Q=()=>process.exit(0);process.on(`SIGINT`,Q),process.on(`SIGTERM`,Q);const $=new e().name(re).description(ie).version(ae,`-v, --version`,`Display the version number.`);$.addCommand(Ae),$.addCommand(Ne),$.addCommand(Le),$.addCommand(Be),$.addCommand(Ue),$.parse();export{T as i,D as n,E as r,H as t};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import{n as e}from"./index.mjs";import{a as t,i as n,o as r,r as i,s as a,t as o}from"./svg-Dnt5ibPB.mjs";a.loadTemplate(`@solid/icons`,`<% if (it.typescript) { -%>
|
|
2
|
+
import type { ComponentProps, JSX } from "solid-js";
|
|
3
|
+
|
|
4
|
+
export type IconProps = ComponentProps<"svg">;
|
|
5
|
+
export type Icon = (props: IconProps) => JSX.Element;
|
|
6
|
+
|
|
7
|
+
export const Icons = {} as const satisfies Record<string, Icon>;
|
|
8
|
+
|
|
9
|
+
export type IconName = keyof typeof Icons;
|
|
10
|
+
<% } else { -%>
|
|
11
|
+
export const Icons = {};
|
|
12
|
+
<% } -%>
|
|
13
|
+
`);function s(e){return a.render(`@solid/icons`,{typescript:e.typescript})}function c(e,a){let{a11y:s,trackSource:c,iconName:l,componentName:u}=a,d=t(e),f=i(s,u);if(c&&l&&(f[`data-icon`]=l),Object.keys(f).length>0){let e=Object.entries(f).map(([e,t])=>`${e}="${t}"`).join(` `);d=d.replace(o,`<svg$1 ${e}>`)}if(s===`title`){let e=r(u);d=n(d,e)}return d=d.replace(o,`<svg$1 {...props}>`),Promise.resolve(`${u}: (props) => (${d})`)}const l={name:`solid`,fileExtensions:{typescript:`.tsx`,javascript:`.jsx`},optionsSchema:e,supportsRef:!0,getIconsTemplate:s,getImports(e){return[]},getForwardRefImportSource(){return`solid-js`},isForwardRefEnabled(e){return!1},promptOptions(){return Promise.resolve({})},getConfigKey(){return`solid`},transformSvg:c};export{l as solidStrategy};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import{i as e,t}from"./index.mjs";import{n,o as r,r as i,s as a,t as o}from"./svg-Dnt5ibPB.mjs";import{transform as s}from"@svgr/core";a.loadTemplate(`@preact/icons`,`<% if (it.typescript) { -%>
|
|
2
|
+
import type * as preact from "preact/compat";
|
|
3
|
+
|
|
4
|
+
export type IconProps = preact.ComponentProps<"svg">;
|
|
5
|
+
<% if (it.forwardRef) { -%>
|
|
6
|
+
export type Icon = preact.ForwardRefExoticComponent<IconProps & preact.RefAttributes<SVGSVGElement>>;
|
|
7
|
+
<% } else { -%>
|
|
8
|
+
export type Icon = (props: IconProps) => preact.JSX.Element;
|
|
9
|
+
<% } -%>
|
|
10
|
+
|
|
11
|
+
export const Icons = {} as const satisfies Record<string, Icon>;
|
|
12
|
+
|
|
13
|
+
export type IconName = keyof typeof Icons;
|
|
14
|
+
<% } else { -%>
|
|
15
|
+
export const Icons = {};
|
|
16
|
+
<% } -%>
|
|
17
|
+
`);function c(e){let t=e.frameworkOptions?.forwardRef??!1;return a.render(`@preact/icons`,{typescript:e.typescript,forwardRef:t})}async function l(e,t,a){let{a11y:c,trackSource:l,iconName:u,componentName:d}=t,f=a?.forwardRef??!1,p=i(c,d);l&&u&&(p[`data-icon`]=u);let m=(await s(e,{plugins:[`@svgr/plugin-svgo`,`@svgr/plugin-jsx`],svgoConfig:{plugins:[`preset-default`,`convertStyleToAttrs`,`sortAttrs`,`mergePaths`]},jsxRuntime:`automatic`,typescript:!1,expandProps:`end`,svgProps:p,titleProp:c===`title`},{componentName:`Icon`})).match(n);if(!m)throw Error(`Failed to extract SVG from SVGR output`);let h=m[0];if(c===`title`){let e=r(d);h=h.replace(o,`<svg$1><title>${e}</title>`)}return f?(h=h.replace(o,`<svg$1 ref={ref}>`),`${d}: forwardRef<SVGSVGElement, IconProps>((props, ref) => (${h}))`):`${d}: (props) => (${h})`}const u={name:`preact`,fileExtensions:{typescript:`.tsx`,javascript:`.jsx`},optionsSchema:e,supportsRef:!0,getIconsTemplate:c,getImports(e){return e?.forwardRef?[`import { forwardRef } from "preact/compat";`]:[]},getForwardRefImportSource(){return`preact/compat`},isForwardRefEnabled(e){return e?.forwardRef===!0},async promptOptions(e){return{forwardRef:e.forwardRef??await t({message:`Use forwardRef for icon components?`,initialValue:!1})}},getConfigKey(){return`preact`},transformSvg:l};export{u as preactStrategy};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import{r as e,t}from"./index.mjs";import{n,o as r,r as i,s as a,t as o}from"./svg-Dnt5ibPB.mjs";import{transform as s}from"@svgr/core";a.loadTemplate(`@react/icons`,`<% if (it.typescript) { -%>
|
|
2
|
+
export type IconProps = React.ComponentProps<"svg">;
|
|
3
|
+
<% if (it.forwardRef) { -%>
|
|
4
|
+
export type Icon = React.ForwardRefExoticComponent<IconProps & React.ComponentRef<"svg">>;
|
|
5
|
+
<% } else { -%>
|
|
6
|
+
export type Icon = (props: IconProps) => React.JSX.Element;
|
|
7
|
+
<% } -%>
|
|
8
|
+
|
|
9
|
+
export const Icons = {} as const satisfies Record<string, Icon>;
|
|
10
|
+
|
|
11
|
+
export type IconName = keyof typeof Icons;
|
|
12
|
+
<% } else { -%>
|
|
13
|
+
export const Icons = {};
|
|
14
|
+
<% } -%>
|
|
15
|
+
`);function c(e){let t=e.frameworkOptions?.forwardRef??!1;return a.render(`@react/icons`,{typescript:e.typescript,forwardRef:t})}async function l(e,t,a){let{a11y:c,trackSource:l,iconName:u,componentName:d}=t,f=a?.forwardRef??!1,p=i(c,d);l&&u&&(p[`data-icon`]=u);let m=(await s(e,{plugins:[`@svgr/plugin-svgo`,`@svgr/plugin-jsx`],svgoConfig:{plugins:[`preset-default`,`convertStyleToAttrs`,`sortAttrs`,`mergePaths`]},jsxRuntime:`automatic`,typescript:!1,expandProps:`end`,svgProps:p,titleProp:c===`title`},{componentName:`Icon`})).match(n);if(!m)throw Error(`Failed to extract SVG from SVGR output`);let h=m[0];if(c===`title`){let e=r(d);h=h.replace(o,`<svg$1><title>${e}</title>`)}return f?(h=h.replace(o,`<svg$1 ref={ref}>`),`${d}: forwardRef<SVGSVGElement, IconProps>((props, ref) => (${h}))`):`${d}: (props) => (${h})`}const u={name:`react`,fileExtensions:{typescript:`.tsx`,javascript:`.jsx`},optionsSchema:e,supportsRef:!0,getIconsTemplate:c,getImports(e){return e?.forwardRef?[`import { forwardRef } from "react";`]:[]},getForwardRefImportSource(){return`react`},isForwardRefEnabled(e){return e?.forwardRef===!0},async promptOptions(e){return{forwardRef:e.forwardRef??await t({message:`Use forwardRef for icon components?`,initialValue:!1})}},getConfigKey(){return`react`},transformSvg:l};export{u as reactStrategy};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Eta as e}from"eta";import{optimize as t}from"svgo";const n=new e({cache:!0,autoEscape:!1,autoTrim:!1}),r={plugins:[`preset-default`,`convertStyleToAttrs`,`sortAttrs`,`mergePaths`]};function i(e){return t(e,r).data}function a(e){return e.replace(/([a-z])([A-Z])/g,`$1 $2`)}function o(e,t){switch(e){case`hidden`:return{"aria-hidden":`true`};case`img`:return{role:`img`,"aria-label":a(t)};case`presentation`:return{role:`presentation`};default:return{}}}const s=/<svg[\s\S]*<\/svg>/,c=/<svg([^>]*)>/;function l(e,t){return e.replace(c,`<svg$1><title>${t}</title>`)}export{i as a,l as i,s as n,a as o,o as r,n as s,c as t};
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "A CLI tool to manage your SVG icons",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.3.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Fellipe Utaka",
|
|
9
9
|
"email": "fellipeutaka@gmail.com",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/fellipeutaka/denji.git"
|
|
15
|
+
"url": "git+https://github.com/fellipeutaka/denji.git",
|
|
16
|
+
"directory": "apps/cli"
|
|
16
17
|
},
|
|
17
18
|
"main": "./dist/index.mjs",
|
|
18
19
|
"module": "./dist/index.mjs",
|
|
@@ -37,18 +38,20 @@
|
|
|
37
38
|
"lint:doctor": "ultracite doctor"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
|
-
"@clack/prompts": "^0.
|
|
41
|
+
"@clack/prompts": "^1.0.0",
|
|
41
42
|
"@svgr/core": "^8.1.0",
|
|
42
43
|
"@svgr/plugin-jsx": "^8.1.0",
|
|
43
44
|
"@svgr/plugin-svgo": "^8.1.0",
|
|
44
|
-
"commander": "^14.0.
|
|
45
|
+
"commander": "^14.0.3",
|
|
46
|
+
"eta": "^4.5.0",
|
|
45
47
|
"iconify-icon": "^3.0.2",
|
|
46
|
-
"oxc-parser": "^0.
|
|
48
|
+
"oxc-parser": "^0.112.0",
|
|
47
49
|
"picocolors": "^1.1.1",
|
|
48
|
-
"
|
|
50
|
+
"svgo": "^4.0.0",
|
|
51
|
+
"zod": "^4.3.6"
|
|
49
52
|
},
|
|
50
53
|
"devDependencies": {
|
|
51
54
|
"@iconify/types": "^2.0.0",
|
|
52
|
-
"tsdown": "^0.20.
|
|
55
|
+
"tsdown": "^0.20.1"
|
|
53
56
|
}
|
|
54
57
|
}
|