@svgforge/svgforge-cli 2.0.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/LICENSE +22 -0
- package/README.md +115 -0
- package/bin/config.yaml +427 -0
- package/bin/svgforge.js +523 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Felix Müller
|
|
4
|
+
Based on svg-sprite — Copyright © 2018 Joschi Kuphal
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# @svgforge/svgforge-cli
|
|
2
|
+
|
|
3
|
+
[![npm version][npm-image]][npm-url] [![Build Status][ci-image]][ci-url] [![Coverage Status][coveralls-image]][coveralls-url] [![npm downloads][npm-downloads]][npm-url]
|
|
4
|
+
|
|
5
|
+
[Command line interface](https://github.com/svgforge/svgforge-cli) for [svgforge](https://github.com/svgforge/svgforge) — create optimized **SVG sprites** of several types along with accompanying stylesheet resources and example documents.
|
|
6
|
+
|
|
7
|
+
The configuration options are [documented in the svgforge](https://github.com/svgforge/svgforge/blob/main/docs/configuration.md) library.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Install globally to use `svgforge` anywhere:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install --global @svgforge/svgforge-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or as a project dependency (e.g. for usage in npm scripts):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install --save-dev @svgforge/svgforge-cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
svgforge [options] files
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Run `svgforge --help` to get a full list of all available options, or consult the [command line reference](docs/command-line.md).
|
|
30
|
+
|
|
31
|
+
### Examples
|
|
32
|
+
|
|
33
|
+
Create a `view` sprite of all SVG files in `assets/` and write the result — along with an example HTML document — to the `out/` directory:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
svgforge --view --view-example --view-bust=false --dest=out assets/*.svg
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Create a `symbol` sprite with an accompanying CSS stylesheet and example document:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
svgforge --symbol --symbol-render-css --symbol-example --dest=out assets/*.svg
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Add a 10px padding around all shapes of a `stack` sprite:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
svgforge --stack -p 10 assets/*.svg
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Instead of passing options on the command line you can also use an external JSON config file:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
svgforge --config config.json assets/*.svg
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> **Tip:** A config file can be generated with AI DeepWiki [online configurator](https://deepwiki.com/svgforge/svgforge-cli).
|
|
58
|
+
|
|
59
|
+
### Sprite modes
|
|
60
|
+
|
|
61
|
+
The CLI supports all four sprite modes of svgforge. Activate them individually or combine them in a single run (e.g. `--view --symbol`):
|
|
62
|
+
|
|
63
|
+
| Mode | Flag | Description |
|
|
64
|
+
| ---- | ---- | ----------- |
|
|
65
|
+
| `view` | `--view` / `-v` | SVG view-based sprite |
|
|
66
|
+
| `symbol` | `--symbol` / `-s` | Sprite of `<symbol>` elements for inline embedding |
|
|
67
|
+
| `stack` | `--stack` / `-S` | Stacked sprite with `:target` CSS |
|
|
68
|
+
| `defs` | `--defs` / `-d` | Sprite of `<defs>` elements (legacy) |
|
|
69
|
+
|
|
70
|
+
### Advanced globbing
|
|
71
|
+
|
|
72
|
+
Some shells don't support the double-star character `**`. Wrap your glob expression in single quotes so that Node.js handles the matching instead:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
svgforge --config config.json 'assets/**/*.svg'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The CLI typically derives shape IDs from the file basename. To start ID traversal from a base directory, add a symbolic link to that directory (`./`) to your pattern:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
svgforge --config config.json 'assets/./**/*.svg'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This results in shape IDs like `path--to--source` (assuming the default shape ID generator is used).
|
|
85
|
+
|
|
86
|
+
## Development
|
|
87
|
+
|
|
88
|
+
Requirements: Node.js >= 18, [pnpm](https://pnpm.io).
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pnpm install
|
|
92
|
+
pnpm lint # Run XO linter
|
|
93
|
+
pnpm test # Run the test suite (node --test)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The test suite spawns the CLI as a child process against fixtures in `test/fixtures/` and asserts the generated sprite files and their contents.
|
|
97
|
+
|
|
98
|
+
## Related
|
|
99
|
+
|
|
100
|
+
- [svgforge](https://github.com/svgforge/svgforge) — the underlying sprite creation library
|
|
101
|
+
- [svgforge docs](https://github.com/svgforge/svgforge/tree/main/docs) — detailed documentation of all sprite modes
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
[MIT](LICENSE) © Felix Müller
|
|
106
|
+
[npm-url]: https://www.npmjs.com/package/@svgforge/svgforge-cli
|
|
107
|
+
|
|
108
|
+
[npm-image]: https://img.shields.io/npm/v/@svgforge/svgforge-cli?logo=npm&logoColor=fff
|
|
109
|
+
|
|
110
|
+
[npm-downloads]: https://img.shields.io/npm/dm/@svgforge/svgforge-cli
|
|
111
|
+
[ci-url]: https://github.com/svgforge/svgforge-cli/actions/workflows/test.yml?query=branch%3Amain
|
|
112
|
+
[ci-image]: https://img.shields.io/github/actions/workflow/status/svgforge/svgforge-cli/test.yml?branch=main&label=CI&logo=github
|
|
113
|
+
|
|
114
|
+
[coveralls-url]: https://coveralls.io/github/svgforge/svgforge-cli?branch=main
|
|
115
|
+
[coveralls-image]: https://img.shields.io/coveralls/github/svgforge/svgforge-cli/main?logo=coveralls
|
package/bin/config.yaml
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
dest:
|
|
2
|
+
description: Main output directory (base path)
|
|
3
|
+
alias: D
|
|
4
|
+
default: .
|
|
5
|
+
map: dest
|
|
6
|
+
|
|
7
|
+
config:
|
|
8
|
+
description: Path to external JSON config file
|
|
9
|
+
alias: C
|
|
10
|
+
|
|
11
|
+
log:
|
|
12
|
+
description: Logging verbosity ("info", "verbose" or "debug")
|
|
13
|
+
alias: l
|
|
14
|
+
map: log
|
|
15
|
+
|
|
16
|
+
shape:
|
|
17
|
+
id:
|
|
18
|
+
separator:
|
|
19
|
+
description: Separator for traversing a directory structure into a shape ID
|
|
20
|
+
default: --
|
|
21
|
+
map: shape.id.separator
|
|
22
|
+
|
|
23
|
+
generator:
|
|
24
|
+
description: ID generation callback [via CLI only template strings]
|
|
25
|
+
default: "%s"
|
|
26
|
+
map: shape.id.generator
|
|
27
|
+
|
|
28
|
+
pseudo:
|
|
29
|
+
description: Separator for CSS pseudo classes
|
|
30
|
+
default: "~"
|
|
31
|
+
map: shape.id.pseudo
|
|
32
|
+
|
|
33
|
+
whitespace:
|
|
34
|
+
description: Whitespace replacement string for shape IDs
|
|
35
|
+
default: "_"
|
|
36
|
+
map: shape.id.whitespace
|
|
37
|
+
|
|
38
|
+
dim:
|
|
39
|
+
width:
|
|
40
|
+
description: Maximum shape width in pixels
|
|
41
|
+
default: 2000
|
|
42
|
+
alias: w
|
|
43
|
+
map: shape.dimension.maxWidth
|
|
44
|
+
|
|
45
|
+
height:
|
|
46
|
+
description: Maximum shape height in pixels
|
|
47
|
+
default: 2000
|
|
48
|
+
alias: h
|
|
49
|
+
map: shape.dimension.maxHeight
|
|
50
|
+
|
|
51
|
+
precision:
|
|
52
|
+
description: Precision (decimal places) for dimension calculations
|
|
53
|
+
default: 2
|
|
54
|
+
map: shape.dimension.precision
|
|
55
|
+
|
|
56
|
+
attributes:
|
|
57
|
+
description: Whether to add width and height attributes to the shapes
|
|
58
|
+
default: false
|
|
59
|
+
map: shape.dimension.attributes
|
|
60
|
+
|
|
61
|
+
spacing:
|
|
62
|
+
padding:
|
|
63
|
+
description: Padding around shape (up to 4 x comma-separated)
|
|
64
|
+
default: 0,0,0,0
|
|
65
|
+
alias: p
|
|
66
|
+
map: shape.spacing.padding
|
|
67
|
+
|
|
68
|
+
box:
|
|
69
|
+
description: Box sizing strategy ("content", "padding" or "icon")
|
|
70
|
+
default: content
|
|
71
|
+
alias: b
|
|
72
|
+
map: shape.spacing.box
|
|
73
|
+
|
|
74
|
+
meta:
|
|
75
|
+
description: Path to YAML file with meta information
|
|
76
|
+
alias: m
|
|
77
|
+
map: shape.meta
|
|
78
|
+
|
|
79
|
+
align:
|
|
80
|
+
description: Path to YAML file with alignment information
|
|
81
|
+
alias: a
|
|
82
|
+
map: shape.align
|
|
83
|
+
|
|
84
|
+
dest:
|
|
85
|
+
description: Path to output directory for intermediate SVG files
|
|
86
|
+
alias: ims
|
|
87
|
+
map: shape.dest
|
|
88
|
+
|
|
89
|
+
transform:
|
|
90
|
+
description: Comma-separated list of predefined transformers (see docs)
|
|
91
|
+
default: svgo
|
|
92
|
+
map: shape.transform
|
|
93
|
+
|
|
94
|
+
"*":
|
|
95
|
+
description: External JSON config files for named transformers
|
|
96
|
+
|
|
97
|
+
svg:
|
|
98
|
+
xmldecl:
|
|
99
|
+
description: Whether to include an XML declaration in SVG files
|
|
100
|
+
default: true
|
|
101
|
+
map: svg.xmlDeclaration
|
|
102
|
+
|
|
103
|
+
doctype:
|
|
104
|
+
description: Whether to include a doctype declaration in SVG files
|
|
105
|
+
default: true
|
|
106
|
+
map: svg.doctypeDeclaration
|
|
107
|
+
|
|
108
|
+
namespace:
|
|
109
|
+
ids:
|
|
110
|
+
description: Whether to apply ID namespacing to the sprite
|
|
111
|
+
default: true
|
|
112
|
+
map: svg.namespaceIDs
|
|
113
|
+
|
|
114
|
+
prefix:
|
|
115
|
+
description: What, if any, prefix to apply to the automatically generated id
|
|
116
|
+
default: ""
|
|
117
|
+
map: svg.namespaceIDPrefix
|
|
118
|
+
|
|
119
|
+
classnames:
|
|
120
|
+
description: Whether to apply CSS class namespacing to the sprite
|
|
121
|
+
default: true
|
|
122
|
+
map: svg.namespaceClassnames
|
|
123
|
+
|
|
124
|
+
dimattrs:
|
|
125
|
+
description: Whether to add width and height attributes to the sprite
|
|
126
|
+
default: true
|
|
127
|
+
map: svg.dimensionAttributes
|
|
128
|
+
|
|
129
|
+
rootattrs:
|
|
130
|
+
description: Custom root attributes for the outermost <svg> element (external JSON file)
|
|
131
|
+
map: svg.rootAttributes
|
|
132
|
+
|
|
133
|
+
precision:
|
|
134
|
+
description: Floating point precision for CSS positioning values
|
|
135
|
+
default: -1
|
|
136
|
+
map: svg.precision
|
|
137
|
+
|
|
138
|
+
view:
|
|
139
|
+
description: Activates the «view» mode
|
|
140
|
+
default: false
|
|
141
|
+
alias: v
|
|
142
|
+
|
|
143
|
+
dest:
|
|
144
|
+
description: Mode specific output directory
|
|
145
|
+
default: view
|
|
146
|
+
map: mode.view.dest
|
|
147
|
+
|
|
148
|
+
sprite:
|
|
149
|
+
description: Sprite path and filename (relative to --view-dest)
|
|
150
|
+
default: svg/sprite.view.svg
|
|
151
|
+
alias: vs
|
|
152
|
+
map: mode.view.sprite
|
|
153
|
+
|
|
154
|
+
bust:
|
|
155
|
+
description: Enable cache busting
|
|
156
|
+
default: true
|
|
157
|
+
map: mode.view.bust
|
|
158
|
+
|
|
159
|
+
render:
|
|
160
|
+
css:
|
|
161
|
+
description: Whether to render a CSS stylesheet with shape dimension classes
|
|
162
|
+
default: false
|
|
163
|
+
alias: vcss
|
|
164
|
+
|
|
165
|
+
template:
|
|
166
|
+
description: CSS stylesheet Mustache template (relative to svgforge basedir)
|
|
167
|
+
default: tmpl/common/sprite.css
|
|
168
|
+
map: mode.view.render.css.template
|
|
169
|
+
|
|
170
|
+
dest:
|
|
171
|
+
description: CSS stylesheet destination (relative to the --view-dest)
|
|
172
|
+
default: sprite.css
|
|
173
|
+
map: mode.view.render.css.dest
|
|
174
|
+
|
|
175
|
+
"*":
|
|
176
|
+
description: Custom output renderings
|
|
177
|
+
template:
|
|
178
|
+
description: Custom output Mustache template (relative to svgforge basedir)
|
|
179
|
+
|
|
180
|
+
dest:
|
|
181
|
+
description: Custom output destination (relative to the --view-dest)
|
|
182
|
+
|
|
183
|
+
example:
|
|
184
|
+
description: Whether to render an example HTML document
|
|
185
|
+
default: false
|
|
186
|
+
alias: vx
|
|
187
|
+
|
|
188
|
+
template:
|
|
189
|
+
description: HTML document Mustache template (relative to svgforge basedir)
|
|
190
|
+
default: tmpl/view/sprite.html
|
|
191
|
+
map: mode.view.example.template
|
|
192
|
+
|
|
193
|
+
dest:
|
|
194
|
+
description: HTML document destination (relative to the --view-dest)
|
|
195
|
+
default: sprite.view.html
|
|
196
|
+
map: mode.view.example.dest
|
|
197
|
+
|
|
198
|
+
defs:
|
|
199
|
+
description: Activates the «defs» mode
|
|
200
|
+
default: false
|
|
201
|
+
alias: d
|
|
202
|
+
|
|
203
|
+
dest:
|
|
204
|
+
description: Mode specific output directory
|
|
205
|
+
default: defs
|
|
206
|
+
map: mode.defs.dest
|
|
207
|
+
|
|
208
|
+
prefix:
|
|
209
|
+
description: CSS selector prefix for all shapes (including placeholders)
|
|
210
|
+
default: .svg-%s
|
|
211
|
+
map: mode.defs.prefix
|
|
212
|
+
|
|
213
|
+
dimensions:
|
|
214
|
+
description: CSS selector suffix for shape dimension rules ("" for inline)
|
|
215
|
+
default: -dims
|
|
216
|
+
map: mode.defs.dimensions
|
|
217
|
+
|
|
218
|
+
sprite:
|
|
219
|
+
description: Sprite path and filename (relative to --defs-dest)
|
|
220
|
+
default: svg/sprite.css.svg
|
|
221
|
+
alias: ds
|
|
222
|
+
map: mode.defs.sprite
|
|
223
|
+
|
|
224
|
+
bust:
|
|
225
|
+
description: Enable cache busting
|
|
226
|
+
default: false
|
|
227
|
+
map: mode.defs.bust
|
|
228
|
+
|
|
229
|
+
render:
|
|
230
|
+
css:
|
|
231
|
+
description: Whether to render a CSS stylesheet
|
|
232
|
+
default: false
|
|
233
|
+
alias: dcss
|
|
234
|
+
|
|
235
|
+
template:
|
|
236
|
+
description: CSS stylesheet Mustache template (relative to svgforge basedir)
|
|
237
|
+
default: tmpl/common/sprite.css
|
|
238
|
+
map: mode.defs.render.css.template
|
|
239
|
+
|
|
240
|
+
dest:
|
|
241
|
+
description: CSS stylesheet destination (relative to the --defs-dest)
|
|
242
|
+
default: sprite.css
|
|
243
|
+
map: mode.defs.render.css.dest
|
|
244
|
+
|
|
245
|
+
"*":
|
|
246
|
+
description: Custom output renderings
|
|
247
|
+
template:
|
|
248
|
+
description: Custom output Mustache template (relative to svgforge basedir)
|
|
249
|
+
|
|
250
|
+
dest:
|
|
251
|
+
description: Custom output destination (relative to the --defs-dest)
|
|
252
|
+
|
|
253
|
+
inline:
|
|
254
|
+
description: Create sprite variant suitable for inline embedding
|
|
255
|
+
default: false
|
|
256
|
+
alias: di
|
|
257
|
+
map: mode.defs.inline
|
|
258
|
+
|
|
259
|
+
example:
|
|
260
|
+
description: Whether to render an example HTML document
|
|
261
|
+
default: false
|
|
262
|
+
alias: dx
|
|
263
|
+
|
|
264
|
+
template:
|
|
265
|
+
description: HTML document Mustache template (relative to svgforge basedir)
|
|
266
|
+
default: tmpl/defs/sprite.html
|
|
267
|
+
map: mode.defs.example.template
|
|
268
|
+
|
|
269
|
+
dest:
|
|
270
|
+
description: HTML document destination (relative to the --defs-dest)
|
|
271
|
+
default: sprite.defs.html
|
|
272
|
+
map: mode.defs.example.dest
|
|
273
|
+
|
|
274
|
+
symbol:
|
|
275
|
+
description: Activates the «symbol» mode
|
|
276
|
+
default: false
|
|
277
|
+
alias: s
|
|
278
|
+
|
|
279
|
+
dest:
|
|
280
|
+
description: Mode specific output directory
|
|
281
|
+
default: symbol
|
|
282
|
+
map: mode.symbol.dest
|
|
283
|
+
|
|
284
|
+
prefix:
|
|
285
|
+
description: CSS selector prefix for all shapes (including placeholders)
|
|
286
|
+
default: .svg-%s
|
|
287
|
+
map: mode.symbol.prefix
|
|
288
|
+
|
|
289
|
+
dimensions:
|
|
290
|
+
description: CSS selector suffix for shape dimension rules ("" for inline)
|
|
291
|
+
default: -dims
|
|
292
|
+
map: mode.symbol.dimensions
|
|
293
|
+
|
|
294
|
+
sprite:
|
|
295
|
+
description: Sprite path and filename (relative to --symbol-dest)
|
|
296
|
+
default: svg/sprite.css.svg
|
|
297
|
+
alias: ss
|
|
298
|
+
map: mode.symbol.sprite
|
|
299
|
+
|
|
300
|
+
bust:
|
|
301
|
+
description: Enable cache busting
|
|
302
|
+
default: false
|
|
303
|
+
map: mode.symbol.bust
|
|
304
|
+
|
|
305
|
+
render:
|
|
306
|
+
css:
|
|
307
|
+
description: Whether to render a CSS stylesheet
|
|
308
|
+
default: false
|
|
309
|
+
alias: sc
|
|
310
|
+
|
|
311
|
+
template:
|
|
312
|
+
description: CSS stylesheet Mustache template (relative to svgforge basedir)
|
|
313
|
+
default: tmpl/common/sprite.css
|
|
314
|
+
map: mode.symbol.render.css.template
|
|
315
|
+
|
|
316
|
+
dest:
|
|
317
|
+
description: CSS stylesheet destination (relative to the --symbol-dest)
|
|
318
|
+
default: sprite.css
|
|
319
|
+
map: mode.symbol.render.css.dest
|
|
320
|
+
|
|
321
|
+
"*":
|
|
322
|
+
description: Custom output renderings
|
|
323
|
+
template:
|
|
324
|
+
description: Custom output Mustache template (relative to svgforge basedir)
|
|
325
|
+
|
|
326
|
+
dest:
|
|
327
|
+
description: Custom output destination (relative to the --symbol-dest)
|
|
328
|
+
|
|
329
|
+
inline:
|
|
330
|
+
description: Create sprite variant suitable for inline embedding
|
|
331
|
+
default: false
|
|
332
|
+
alias: si
|
|
333
|
+
map: mode.symbol.inline
|
|
334
|
+
|
|
335
|
+
example:
|
|
336
|
+
description: Whether to render an example HTML document
|
|
337
|
+
default: false
|
|
338
|
+
alias: sx
|
|
339
|
+
|
|
340
|
+
template:
|
|
341
|
+
description: HTML document Mustache template (relative to svgforge basedir)
|
|
342
|
+
default: tmpl/symbol/sprite.html
|
|
343
|
+
map: mode.symbol.example.template
|
|
344
|
+
|
|
345
|
+
dest:
|
|
346
|
+
description: HTML document destination (relative to the --symbol-dest)
|
|
347
|
+
default: sprite.symbol.html
|
|
348
|
+
map: mode.symbol.example.dest
|
|
349
|
+
|
|
350
|
+
stack:
|
|
351
|
+
description: Activates the «stack» mode
|
|
352
|
+
default: false
|
|
353
|
+
alias: S
|
|
354
|
+
|
|
355
|
+
dest:
|
|
356
|
+
description: Mode specific output directory
|
|
357
|
+
default: stack
|
|
358
|
+
map: mode.stack.dest
|
|
359
|
+
|
|
360
|
+
prefix:
|
|
361
|
+
description: CSS selector prefix for all shapes (including placeholders)
|
|
362
|
+
default: .svg-%s
|
|
363
|
+
map: mode.stack.prefix
|
|
364
|
+
|
|
365
|
+
dimensions:
|
|
366
|
+
description: CSS selector suffix for shape dimension rules ("" for inline)
|
|
367
|
+
default: -dims
|
|
368
|
+
map: mode.stack.dimensions
|
|
369
|
+
|
|
370
|
+
sprite:
|
|
371
|
+
description: Sprite path and filename (relative to --stack-dest)
|
|
372
|
+
default: svg/sprite.css.svg
|
|
373
|
+
alias: Ss
|
|
374
|
+
map: mode.stack.sprite
|
|
375
|
+
|
|
376
|
+
bust:
|
|
377
|
+
description: Enable cache busting
|
|
378
|
+
default: false
|
|
379
|
+
map: mode.stack.bust
|
|
380
|
+
|
|
381
|
+
rootviewbox:
|
|
382
|
+
description: Add viewBox attribute to root svg automatically
|
|
383
|
+
default: true
|
|
384
|
+
map: mode.stack.rootviewbox
|
|
385
|
+
|
|
386
|
+
render:
|
|
387
|
+
css:
|
|
388
|
+
description: Whether to render a CSS stylesheet
|
|
389
|
+
default: false
|
|
390
|
+
alias: Sc
|
|
391
|
+
|
|
392
|
+
template:
|
|
393
|
+
description: CSS stylesheet Mustache template (relative to svgforge basedir)
|
|
394
|
+
default: tmpl/common/sprite.css
|
|
395
|
+
map: mode.stack.render.css.template
|
|
396
|
+
|
|
397
|
+
dest:
|
|
398
|
+
description: CSS stylesheet destination (relative to the --stack-dest)
|
|
399
|
+
default: sprite.css
|
|
400
|
+
map: mode.stack.render.css.dest
|
|
401
|
+
|
|
402
|
+
"*":
|
|
403
|
+
description: Custom output renderings
|
|
404
|
+
template:
|
|
405
|
+
description: Custom output Mustache template (relative to svgforge basedir)
|
|
406
|
+
|
|
407
|
+
dest:
|
|
408
|
+
description: Custom output destination (relative to the --stack-dest)
|
|
409
|
+
|
|
410
|
+
example:
|
|
411
|
+
description: Whether to render an example HTML document
|
|
412
|
+
default: false
|
|
413
|
+
alias: Sx
|
|
414
|
+
|
|
415
|
+
template:
|
|
416
|
+
description: HTML document Mustache template (relative to svgforge basedir)
|
|
417
|
+
default: tmpl/stack/sprite.html
|
|
418
|
+
map: mode.stack.example.template
|
|
419
|
+
|
|
420
|
+
dest:
|
|
421
|
+
description: HTML document destination (relative to the --stack-dest)
|
|
422
|
+
default: sprite.stack.html
|
|
423
|
+
map: mode.stack.example.dest
|
|
424
|
+
|
|
425
|
+
variables:
|
|
426
|
+
description: Path to external JSON file with Mustache variable definitions
|
|
427
|
+
map: "variables"
|
package/bin/svgforge.js
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Svgforge is a Node.js module for creating SVG sprites
|
|
5
|
+
|
|
6
|
+
Based on the command line interface originally written for svg-sprite
|
|
7
|
+
by Joschi Kuphal — this is a standalone fork/package of that CLI.
|
|
8
|
+
|
|
9
|
+
@see https://github.com/joeda1/svgforge-cli
|
|
10
|
+
@author Joschi Kuphal <joschi@kuphal.net> (https://github.com/jkphl)
|
|
11
|
+
@author Felix Müller
|
|
12
|
+
@copyright © 2018 Joschi Kuphal
|
|
13
|
+
@copyright © 2026 Felix Müller
|
|
14
|
+
@license MIT https://github.com/joeda1/svgforge-cli/blob/main/LICENSE
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import process from 'node:process';
|
|
20
|
+
import {fileURLToPath} from 'node:url';
|
|
21
|
+
import {load} from 'js-yaml';
|
|
22
|
+
import yargs from 'yargs';
|
|
23
|
+
import SVGSpriter from '@svgforge/svgforge';
|
|
24
|
+
import {deepMerge, isObject, zipObject} from '@svgforge/svgforge/lib/svg-sprite/utils/index.js';
|
|
25
|
+
|
|
26
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const {version} = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'package.json'), 'utf8'));
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
All supported sprite modes
|
|
31
|
+
|
|
32
|
+
@type {string[]}
|
|
33
|
+
*/
|
|
34
|
+
const MODES = ['view', 'defs', 'symbol', 'stack'];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
All supported stylesheet render types
|
|
38
|
+
|
|
39
|
+
@type {string[]}
|
|
40
|
+
*/
|
|
41
|
+
const RENDER_TYPES = ['css'];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
Yargs argument parser instance
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
// eslint-disable-next-line jsdoc/imports-as-dependencies -- `yargs` is a declared dependency; dynamic import() is required in JSDoc types.
|
|
48
|
+
/** @typedef {import('yargs').Argv} Yargs */
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
CLI option definition (subset of the bundled YAML configuration)
|
|
52
|
+
|
|
53
|
+
@typedef {object} OptionDefinition
|
|
54
|
+
@property {string} [description] Option description
|
|
55
|
+
@property {string} [alias] Short option alias
|
|
56
|
+
@property {unknown} [default] Default value applied when the option is omitted
|
|
57
|
+
@property {boolean} [required] Whether the option is mandatory
|
|
58
|
+
@property {string} [map] Dot-separated config key the option maps to
|
|
59
|
+
@property {object} [children] Nested option definitions keyed by their suffix
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
Recursively nested map of vinyl files
|
|
64
|
+
|
|
65
|
+
@typedef {{[key: string]: File | FileMap}} FileMap
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
Spriter configuration (subset of the SVGSpriter configuration object)
|
|
70
|
+
|
|
71
|
+
@typedef {object} SpriterConfig
|
|
72
|
+
@property {string} [dest] Main output directory
|
|
73
|
+
@property {string} [log] Logging verbosity ("info", "verbose" or "debug")
|
|
74
|
+
@property {object} [shape] SVG shape configuration
|
|
75
|
+
@property {object} [svg] SVG output configuration
|
|
76
|
+
@property {object} [mode] Sprite mode configuration
|
|
77
|
+
@property {unknown} [variables] Mustache template variables (or the path to their JSON file)
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
Global spriter configuration accumulated from the CLI options
|
|
82
|
+
|
|
83
|
+
@type {SpriterConfig}
|
|
84
|
+
*/
|
|
85
|
+
const config = {};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
External JSON configuration ("--config" file) used to detect explicitly
|
|
89
|
+
requested modes, render types and examples
|
|
90
|
+
|
|
91
|
+
@type {object}
|
|
92
|
+
*/
|
|
93
|
+
let JSONConfig = {mode: {}};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
Maps dot-separated config keys to their CLI option names
|
|
97
|
+
|
|
98
|
+
@type {{[key: string]: string}}
|
|
99
|
+
*/
|
|
100
|
+
const optionsMap = {};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
Resolve a path relative to the installed svgforge library
|
|
104
|
+
|
|
105
|
+
Default template files (e.g. "tmpl/stack/sprite.html") ship with the
|
|
106
|
+
svgforge library package, so they are resolved against its install
|
|
107
|
+
location rather than against this CLI package.
|
|
108
|
+
|
|
109
|
+
@param {string} target Path to resolve
|
|
110
|
+
@returns {string} Resolved absolute path
|
|
111
|
+
*/
|
|
112
|
+
function resolveSvgForgePath(target) {
|
|
113
|
+
const libEntry = fileURLToPath(import.meta.resolve('@svgforge/svgforge'));
|
|
114
|
+
const libRoot = path.dirname(path.dirname(libEntry));
|
|
115
|
+
return path.resolve(libRoot, target);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
Add a command line option and recursively register all of its children
|
|
120
|
+
|
|
121
|
+
@param {Yargs} parser Yargs instance to extend
|
|
122
|
+
@param {string} name Hyphenated option name (e.g. "defs-render-css")
|
|
123
|
+
@param {OptionDefinition} option Option configuration
|
|
124
|
+
@returns {Yargs} The extended yargs instance
|
|
125
|
+
*/
|
|
126
|
+
function addOption(parser, name, option) {
|
|
127
|
+
let alias = name;
|
|
128
|
+
|
|
129
|
+
// If this is an option itself
|
|
130
|
+
if ('description' in option) {
|
|
131
|
+
if ('alias' in option) {
|
|
132
|
+
alias = option.alias;
|
|
133
|
+
parser = parser.alias(alias, name);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
parser = parser.describe(alias, option.description);
|
|
137
|
+
|
|
138
|
+
if ('default' in option) {
|
|
139
|
+
const templated = name.endsWith('-template');
|
|
140
|
+
const defaultValue = templated ? resolveSvgForgePath(option.default) : option.default;
|
|
141
|
+
parser = parser.default(alias, defaultValue);
|
|
142
|
+
|
|
143
|
+
if (option.default === true || option.default === false) {
|
|
144
|
+
parser = parser.boolean(name);
|
|
145
|
+
}
|
|
146
|
+
} else if (option.required) {
|
|
147
|
+
parser = parser.require(alias);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if ('map' in option) {
|
|
151
|
+
optionsMap[option.map] = name;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const {description, alias: optAlias, default: optDefault, map, required, ...children} = option;
|
|
156
|
+
for (const [key, child] of Object.entries(children)) {
|
|
157
|
+
if (isObject(child)) {
|
|
158
|
+
parser = addOption(parser, `${name}-${key}`, child);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return parser;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
Add a value at the given key path of a configuration object
|
|
167
|
+
|
|
168
|
+
@param {object} store Configuration object to modify
|
|
169
|
+
@param {string[]} keyPath Path segments ending at the target key
|
|
170
|
+
@param {unknown} value Value to store
|
|
171
|
+
@returns {void}
|
|
172
|
+
*/
|
|
173
|
+
function addConfigMap(store, keyPath, value) {
|
|
174
|
+
const key = keyPath.shift();
|
|
175
|
+
|
|
176
|
+
if (keyPath.length > 0) {
|
|
177
|
+
if (!Object.hasOwn(store, key) || !isObject(store[key])) {
|
|
178
|
+
store[key] = {};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
addConfigMap(store[key], keyPath, value);
|
|
182
|
+
} else {
|
|
183
|
+
store[key] = value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
Recursively write files to disk
|
|
189
|
+
|
|
190
|
+
@param {FileMap} files Nested map of vinyl files
|
|
191
|
+
@returns {number} Number of written files
|
|
192
|
+
*/
|
|
193
|
+
function writeFiles(files) {
|
|
194
|
+
let written = 0;
|
|
195
|
+
|
|
196
|
+
for (const file of Object.values(files)) {
|
|
197
|
+
if (!isObject(file)) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (typeof file.path === 'string') {
|
|
202
|
+
fs.mkdirSync(path.dirname(file.path), {recursive: true});
|
|
203
|
+
fs.writeFileSync(file.path, file.contents);
|
|
204
|
+
++written;
|
|
205
|
+
} else {
|
|
206
|
+
written += writeFiles(file);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return written;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
Compile the spriter and write all generated files to disk
|
|
215
|
+
|
|
216
|
+
@param {SVGSpriter} spriter Spriter instance to compile
|
|
217
|
+
@returns {Promise<number>} Promise resolving to the number of written files
|
|
218
|
+
*/
|
|
219
|
+
function compile(spriter) {
|
|
220
|
+
return new Promise((resolve, reject) => {
|
|
221
|
+
spriter.compile((error, result) => {
|
|
222
|
+
if (error) {
|
|
223
|
+
reject(error);
|
|
224
|
+
} else {
|
|
225
|
+
resolve(writeFiles(result));
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
Register all CLI options from the bundled YAML configuration
|
|
233
|
+
|
|
234
|
+
@returns {Yargs} The fully configured yargs instance
|
|
235
|
+
*/
|
|
236
|
+
function registerOptions() {
|
|
237
|
+
const parser = yargs(process.argv.slice(2))
|
|
238
|
+
.usage('Create one or multiple sprites of the given SVG files, optionally along with some stylesheet resources.\nUsage: $0 [options] files')
|
|
239
|
+
.version(version)
|
|
240
|
+
.help('help', 'Display this help information')
|
|
241
|
+
.wrap(null)
|
|
242
|
+
.example('$0 --view --view-example --dest=out assets/*.svg', 'Create a view sprite of the given SVG files including example document to the subdirectory "out"')
|
|
243
|
+
.example('$0 --defs --defs-render-css --defs-example --dest=out assets/*.svg', 'Create a defs sprite of the given SVG files including a CSS stylesheet and example document')
|
|
244
|
+
.example('$0 -S -p 10 assets/*.svg', 'Create a stack sprite and add 10px padding around all shapes')
|
|
245
|
+
.showHelpOnFail(true)
|
|
246
|
+
.demandCommand(1);
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const options = load(fs.readFileSync(path.resolve(__dirname, 'config.yaml'), 'utf8'));
|
|
250
|
+
for (const [key, option] of Object.entries(options)) {
|
|
251
|
+
addOption(parser, key, option);
|
|
252
|
+
}
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.log(error);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return parser;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
Apply all CLI arguments to the global configuration object
|
|
262
|
+
|
|
263
|
+
@param {SpriterConfig} cfg Configuration object to modify
|
|
264
|
+
@param {{[key: string]: string}} optMap Config keys mapped to option names
|
|
265
|
+
@param {object} argv Parsed command line arguments
|
|
266
|
+
@returns {void}
|
|
267
|
+
*/
|
|
268
|
+
function applyCliOptions(cfg, optMap, argv) {
|
|
269
|
+
for (const [configKey, optionName] of Object.entries(optMap)) {
|
|
270
|
+
if (!Object.hasOwn(argv, optionName)) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
addConfigMap(cfg, configKey.split('.'), argv[optionName]);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
Load an external JSON configuration file ("--config") and merge it in
|
|
280
|
+
|
|
281
|
+
@param {SpriterConfig} cfg Configuration object to merge into
|
|
282
|
+
@param {object} argv Parsed command line arguments
|
|
283
|
+
@returns {void}
|
|
284
|
+
*/
|
|
285
|
+
function loadExternalConfig(cfg, argv) {
|
|
286
|
+
if (!argv.config) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
const configFile = argv.config;
|
|
292
|
+
delete argv.config;
|
|
293
|
+
delete argv.C;
|
|
294
|
+
|
|
295
|
+
const JSONConfigContent = fs.readFileSync(path.resolve(configFile));
|
|
296
|
+
/** @type {SpriterConfig} */
|
|
297
|
+
const externalConfig = JSON.parse(JSONConfigContent);
|
|
298
|
+
|
|
299
|
+
// Keep an un-merged clone for the later option-removal checks
|
|
300
|
+
JSONConfig = JSON.parse(JSONConfigContent);
|
|
301
|
+
if (!('mode' in JSONConfig)) {
|
|
302
|
+
JSONConfig.mode = {};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Expand shorthand mode definitions
|
|
306
|
+
if ('mode' in externalConfig && isObject(externalConfig.mode)) {
|
|
307
|
+
for (const [mode, modeConfig] of Object.entries(externalConfig.mode)) {
|
|
308
|
+
if (modeConfig !== true) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
externalConfig.mode[mode] = {render: {css: true}};
|
|
313
|
+
JSONConfig.mode[mode] = {render: {css: true}};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
deepMerge(cfg, externalConfig);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error('[ERROR] Skipping --config file due to errors ("%s")', error.message.trim());
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
Read a shape transform configuration file to a plain object
|
|
325
|
+
|
|
326
|
+
@param {string} file Path to the transform configuration file
|
|
327
|
+
@returns {object|undefined} Parsed configuration, or nothing on error
|
|
328
|
+
*/
|
|
329
|
+
function readTransformConfig(file) {
|
|
330
|
+
try {
|
|
331
|
+
if (fs.existsSync(file)) {
|
|
332
|
+
const contents = fs.readFileSync(file, 'utf8');
|
|
333
|
+
return contents.trim() ? JSON.parse(contents) : {};
|
|
334
|
+
}
|
|
335
|
+
} catch {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
Refine the shape related configuration options
|
|
344
|
+
|
|
345
|
+
@param {SpriterConfig} cfg Configuration object to modify
|
|
346
|
+
@param {object} argv Parsed command line arguments
|
|
347
|
+
@returns {void}
|
|
348
|
+
*/
|
|
349
|
+
function refineShapeConfig(cfg, argv) {
|
|
350
|
+
// Refine particular config options
|
|
351
|
+
cfg.shape.spacing.padding = String(cfg.shape.spacing.padding).trim();
|
|
352
|
+
cfg.shape.spacing.padding = cfg.shape.spacing.padding.length > 0
|
|
353
|
+
// eslint-disable-next-line unicorn/prefer-number-coercion -- Padding values may carry unit suffixes (e.g. "48px"); parseFloat is the required semantics.
|
|
354
|
+
? cfg.shape.spacing.padding.split(',').map(dimension => Number.parseFloat(dimension || 0))
|
|
355
|
+
: [];
|
|
356
|
+
|
|
357
|
+
if (cfg.svg.rootAttributes && typeof cfg.svg.rootAttributes === 'string') {
|
|
358
|
+
try {
|
|
359
|
+
const attributesPath = path.resolve(cfg.svg.rootAttributes);
|
|
360
|
+
cfg.svg.rootAttributes = JSON.parse(fs.readFileSync(attributesPath, 'utf8'));
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('[ERROR] Skipping --svg-rootattrs file due to errors ("%s")', error.message.trim());
|
|
363
|
+
cfg.svg.rootAttributes = {};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Expand transformation options
|
|
368
|
+
if (typeof cfg.shape.transform === 'string') {
|
|
369
|
+
const transforms = String(cfg.shape.transform).trim();
|
|
370
|
+
cfg.shape.transform = [];
|
|
371
|
+
|
|
372
|
+
if (transforms.length > 0) {
|
|
373
|
+
for (const raw of transforms.split(',')) {
|
|
374
|
+
const transform = String(raw).trim();
|
|
375
|
+
if (transform.length === 0) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (Object.hasOwn(argv, `shape-transform-${transform}`)) {
|
|
380
|
+
const transformConfig = readTransformConfig(argv[`shape-transform-${transform}`]);
|
|
381
|
+
// eslint-disable-next-line max-depth -- Per-transform config lookup nests the mode/shape refinement walk one level too deep.
|
|
382
|
+
if (transformConfig !== undefined) {
|
|
383
|
+
cfg.shape.transform.push(zipObject([transform], [transformConfig]));
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
cfg.shape.transform.push(transform);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
Refine the sprite mode configuration and remove inactive render types
|
|
395
|
+
|
|
396
|
+
@param {SpriterConfig} cfg Configuration object to modify
|
|
397
|
+
@param {object} argv Parsed command line arguments
|
|
398
|
+
@returns {void}
|
|
399
|
+
*/
|
|
400
|
+
function refineSpriteModes(cfg, argv) {
|
|
401
|
+
// Run through all sprite modes
|
|
402
|
+
for (const mode of MODES) {
|
|
403
|
+
if (argv[mode] !== true && !Object.hasOwn(JSONConfig.mode, mode)) {
|
|
404
|
+
delete cfg.mode[mode];
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const {render} = cfg.mode[mode];
|
|
409
|
+
|
|
410
|
+
// Remove excessive render types
|
|
411
|
+
if (render) {
|
|
412
|
+
for (const renderType of RENDER_TYPES) {
|
|
413
|
+
const arg = `${mode}-render-${renderType}`;
|
|
414
|
+
if (
|
|
415
|
+
Object.hasOwn(render, renderType)
|
|
416
|
+
&& argv[arg] !== true
|
|
417
|
+
&& (!Object.hasOwn(JSONConfig.mode, mode)
|
|
418
|
+
|| !Object.hasOwn(JSONConfig.mode[mode], 'render')
|
|
419
|
+
|| !Object.hasOwn(JSONConfig.mode[mode].render, renderType))
|
|
420
|
+
) {
|
|
421
|
+
delete render[renderType];
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (Array.isArray(cfg.mode[mode].dimensions) && cfg.mode[mode].dimensions.length === 0) {
|
|
427
|
+
cfg.mode[mode].dimensions = true;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
Remove excessive example options from the sprite modes
|
|
434
|
+
|
|
435
|
+
@param {SpriterConfig} cfg Configuration object to modify
|
|
436
|
+
@param {object} argv Parsed command line arguments
|
|
437
|
+
@returns {void}
|
|
438
|
+
*/
|
|
439
|
+
function removeExcessiveExamples(cfg, argv) {
|
|
440
|
+
for (const [mode, modeConfig] of Object.entries(cfg.mode)) {
|
|
441
|
+
const example = `${mode}-example`;
|
|
442
|
+
if (argv[example] !== true && (!Object.hasOwn(JSONConfig.mode, mode) || !Object.hasOwn(JSONConfig.mode[mode], 'example')) && Object.hasOwn(modeConfig, 'example')) {
|
|
443
|
+
delete modeConfig.example;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
Read and parse the Mustache variables JSON file
|
|
450
|
+
|
|
451
|
+
@param {SpriterConfig} cfg Configuration object to modify
|
|
452
|
+
@returns {void}
|
|
453
|
+
*/
|
|
454
|
+
function loadVariables(cfg) {
|
|
455
|
+
if (!Object.hasOwn(cfg, 'variables')) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const variables = String(cfg.variables).trim();
|
|
460
|
+
delete cfg.variables;
|
|
461
|
+
|
|
462
|
+
if (variables.length > 0) {
|
|
463
|
+
const variablesFile = path.resolve(variables);
|
|
464
|
+
if (fs.existsSync(variablesFile)) {
|
|
465
|
+
try {
|
|
466
|
+
cfg.variables = JSON.parse(fs.readFileSync(variablesFile, 'utf8'));
|
|
467
|
+
} catch (error) {
|
|
468
|
+
console.error('[ERROR] Skipping --variables file due to errors ("%s")', error.message.trim());
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
Run the command line interface
|
|
476
|
+
|
|
477
|
+
@returns {Promise<void>} Promise resolving after all sprites were written
|
|
478
|
+
*/
|
|
479
|
+
async function main() {
|
|
480
|
+
const parser = registerOptions();
|
|
481
|
+
const argv = parser.parse();
|
|
482
|
+
|
|
483
|
+
// Map all arguments to a global configuration object
|
|
484
|
+
applyCliOptions(config, optionsMap, argv);
|
|
485
|
+
|
|
486
|
+
// Load external JSON config file
|
|
487
|
+
loadExternalConfig(config, argv);
|
|
488
|
+
|
|
489
|
+
// Refine particular config options
|
|
490
|
+
refineShapeConfig(config, argv);
|
|
491
|
+
refineSpriteModes(config, argv);
|
|
492
|
+
removeExcessiveExamples(config, argv);
|
|
493
|
+
loadVariables(config);
|
|
494
|
+
|
|
495
|
+
const spriter = new SVGSpriter(config);
|
|
496
|
+
const files = argv._.flatMap(filePattern => fs.globSync(filePattern).toSorted((a, b) => b.localeCompare(a)));
|
|
497
|
+
|
|
498
|
+
for (const filePattern of files) {
|
|
499
|
+
// Glob >= 9 returns paths without the "./" prefix, so detect relative
|
|
500
|
+
// patterns from the original glob result to preserve directory structure
|
|
501
|
+
// in the shape identifiers (e.g. "nested/leaf.svg" -> "nested--leaf")
|
|
502
|
+
const isRelative = !path.isAbsolute(filePattern) && !filePattern.startsWith('../');
|
|
503
|
+
const file = path.resolve(filePattern);
|
|
504
|
+
const stat = fs.lstatSync(file);
|
|
505
|
+
let basename;
|
|
506
|
+
|
|
507
|
+
if (stat.isSymbolicLink()) {
|
|
508
|
+
basename = path.basename(fs.readlinkSync(file));
|
|
509
|
+
} else {
|
|
510
|
+
basename = isRelative ? filePattern : path.basename(file);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
spriter.add(file, basename, fs.readFileSync(file));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
try {
|
|
517
|
+
await compile(spriter);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
console.error(error);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
await main();
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@svgforge/svgforge-cli",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"author": "Felix Müller (https://github.com/joeda1)",
|
|
5
|
+
"contributors": [
|
|
6
|
+
"Joschi Kuphal <joschi@kuphal.net> (https://github.com/jkphl)"
|
|
7
|
+
],
|
|
8
|
+
"description": "Command line interface for svgforge — create SVG sprites & stacks with stylesheet resources (CSS, Sass, etc.)",
|
|
9
|
+
"homepage": "https://github.com/svgforge/svgforge-cli",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/svgforge/svgforge-cli.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/svgforge/svgforge-cli/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"svg",
|
|
19
|
+
"stack",
|
|
20
|
+
"generator",
|
|
21
|
+
"css",
|
|
22
|
+
"cli",
|
|
23
|
+
"command-line"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=24"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"svgforge": "./bin/svgforge.js"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"js-yaml": "^5.4.1",
|
|
38
|
+
"@svgforge/svgforge": "^2.0.0",
|
|
39
|
+
"vinyl": "^3.0.1",
|
|
40
|
+
"yargs": "^18.1.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"eslint-plugin-jsdoc": "^64.3.2",
|
|
44
|
+
"xo": "^4.0.0"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"lint": "xo",
|
|
48
|
+
"fix": "xo --fix",
|
|
49
|
+
"test": "node --test test/*.test.js",
|
|
50
|
+
"test:coverage": "mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage/lcov.info"
|
|
51
|
+
}
|
|
52
|
+
}
|