@svgicons-com/cli 0.1.0-alpha.3 → 0.1.0-alpha.4
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 +22 -1
- package/package.json +1 -1
- package/src/api.js +19 -7
- package/src/cli.js +137 -4
- package/src/downloads.js +19 -0
package/README.md
CHANGED
|
@@ -60,6 +60,7 @@ svgicons icon show 33716
|
|
|
60
60
|
svgicons icon url 33716-arrow-circle-up-fill
|
|
61
61
|
svgicons icon raw 33716-arrow-circle-up-fill
|
|
62
62
|
svgicons icon download 33716-arrow-circle-up-fill --output ./icons
|
|
63
|
+
svgicons icon png 33716-arrow-circle-up-fill --size 512 --output ./icons
|
|
63
64
|
svgicons collection list
|
|
64
65
|
svgicons collection create --name "Dashboard icons" --description "Navigation and status icons"
|
|
65
66
|
svgicons collection show "Dashboard icons" --icons
|
|
@@ -69,6 +70,7 @@ svgicons collection add "Dashboard icons" 33716 240297
|
|
|
69
70
|
svgicons collection remove "Dashboard icons" 33716-arrow-circle-up-fill
|
|
70
71
|
svgicons collection delete "Dashboard icons" --yes
|
|
71
72
|
svgicons collection export "Dashboard icons" --formats react-ts,vue --color-policy currentColor --output ./exports
|
|
73
|
+
svgicons collection export "Dashboard icons" --formats png-pack --png-sizes 24,48,512 --output ./exports
|
|
72
74
|
svgicons collection export "Dashboard icons" --formats react-ts --no-size-props --no-typescript --output ./exports
|
|
73
75
|
svgicons export status 55 --collection "Dashboard icons"
|
|
74
76
|
svgicons export download 55 --collection "Dashboard icons" --output ./exports
|
|
@@ -116,6 +118,16 @@ The icon reference must include both the numeric ID and the icon name, such as `
|
|
|
116
118
|
|
|
117
119
|
This command uses the MCP `get_icon` tool with raw SVG output, so the token needs `mcp:use` and `icons:read`. Existing files are not overwritten unless you add `--force`.
|
|
118
120
|
|
|
121
|
+
Export a Pro PNG asset from a single icon:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
svgicons icon png 33716-arrow-circle-up-fill --size 512 --output ./icons
|
|
125
|
+
svgicons icon png 33716-arrow-circle-up-fill --sizes 128,256,512 --density 1,2 --zip --output ./icons
|
|
126
|
+
svgicons icon png 33716-arrow-circle-up-fill --size 512 --color black --background solid --background-color "#ffffff"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Single-icon PNG export uses the Pro REST API, not the public anonymous search API. The token needs `icons:read` and `exports:create`. The icon reference must include both ID and name, and the server verifies that the name matches before rendering.
|
|
130
|
+
|
|
119
131
|
Read-only icon commands accept a numeric ID, an `id-name` reference, or a full svgicons.com icon URL:
|
|
120
132
|
|
|
121
133
|
```bash
|
|
@@ -174,13 +186,22 @@ The command polls every 2 seconds for up to 180 seconds by default. Use `--timeo
|
|
|
174
186
|
Supported export flags:
|
|
175
187
|
|
|
176
188
|
```bash
|
|
177
|
-
--formats react-ts,vue
|
|
189
|
+
--formats react-ts,vue,png-pack
|
|
178
190
|
--color-policy currentColor|preserve|strip
|
|
179
191
|
--naming-policy kebab|pascal|camel
|
|
180
192
|
--size-props / --no-size-props
|
|
181
193
|
--typescript / --no-typescript
|
|
194
|
+
--png-sizes 24,48,512
|
|
195
|
+
--png-densities 1,2
|
|
196
|
+
--png-background transparent|solid
|
|
197
|
+
--png-background-color "#ffffff"
|
|
198
|
+
--png-color preserve|black|white|custom
|
|
199
|
+
--png-icon-color "#2563eb"
|
|
200
|
+
--png-padding 48
|
|
182
201
|
```
|
|
183
202
|
|
|
203
|
+
Use `--formats png` as a shorthand for `--formats png-pack`. PNG pack exports are generated by the server queue worker and downloaded as part of the collection ZIP.
|
|
204
|
+
|
|
184
205
|
Collection commands accept a numeric ID, exact slug, or exact case-insensitive collection name. The legacy `kit` alias remains available for old scripts.
|
|
185
206
|
|
|
186
207
|
Lifecycle commands are available for collection maintenance:
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -79,14 +79,26 @@ export async function proApiDownload(pathOrUrl, options = {}) {
|
|
|
79
79
|
let response;
|
|
80
80
|
|
|
81
81
|
try {
|
|
82
|
-
|
|
82
|
+
const headers = {
|
|
83
|
+
Accept: 'image/png, application/zip, application/octet-stream',
|
|
84
|
+
Authorization: `Bearer ${token}`,
|
|
85
|
+
'User-Agent': 'svgicons-cli/0.1.0-alpha',
|
|
86
|
+
...options.headers,
|
|
87
|
+
};
|
|
88
|
+
const init = {
|
|
83
89
|
method: options.method || 'GET',
|
|
84
|
-
headers
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
headers,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (options.body !== undefined) {
|
|
94
|
+
headers['Content-Type'] = headers['Content-Type'] || 'application/json';
|
|
95
|
+
init.body = typeof options.body === 'string' || options.body instanceof Buffer
|
|
96
|
+
? options.body
|
|
97
|
+
: JSON.stringify(options.body);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
response = await fetch(url, {
|
|
101
|
+
...init,
|
|
90
102
|
});
|
|
91
103
|
} catch (error) {
|
|
92
104
|
throw networkError(error, url);
|
package/src/cli.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
parseIconReference,
|
|
23
23
|
resolveArchiveDownloadPath,
|
|
24
24
|
resolveIconDownloadPath,
|
|
25
|
+
resolvePngDownloadPath,
|
|
25
26
|
sanitizeFileSegment,
|
|
26
27
|
writeBinaryFile,
|
|
27
28
|
writeIconSvg,
|
|
@@ -56,10 +57,15 @@ const KNOWN_SCOPES = [
|
|
|
56
57
|
'exports:create',
|
|
57
58
|
'mcp:use',
|
|
58
59
|
];
|
|
59
|
-
const EXPORT_FORMATS = ['svg-folder', 'svg-sprite', 'json-manifest', 'license-manifest', 'react-ts', 'vue'];
|
|
60
|
+
const EXPORT_FORMATS = ['svg-folder', 'svg-sprite', 'json-manifest', 'license-manifest', 'react-ts', 'vue', 'png-pack'];
|
|
60
61
|
const FRAMEWORKS = ['svg', 'react-ts', 'vue', 'sprite'];
|
|
61
62
|
const COLOR_POLICIES = ['currentColor', 'preserve', 'strip'];
|
|
62
63
|
const NAMING_POLICIES = ['kebab', 'pascal', 'camel'];
|
|
64
|
+
const PNG_SIZES = [16, 24, 32, 48, 96, 128, 256, 512, 1024];
|
|
65
|
+
const PNG_DENSITIES = [1, 2, 3, 4];
|
|
66
|
+
const PNG_BACKGROUNDS = ['transparent', 'solid'];
|
|
67
|
+
const PNG_COLORS = ['preserve', 'black', 'white', 'custom'];
|
|
68
|
+
const PNG_FITS = ['contain'];
|
|
63
69
|
|
|
64
70
|
export async function run(argv) {
|
|
65
71
|
const { args, options: globalOptions } = extractGlobalOptions(argv);
|
|
@@ -633,7 +639,12 @@ async function icon(args) {
|
|
|
633
639
|
return;
|
|
634
640
|
}
|
|
635
641
|
|
|
636
|
-
|
|
642
|
+
if (subcommand === 'png') {
|
|
643
|
+
await iconPng(rest);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
throw new Error('Unknown icon command. Use: show, raw, url, open, download, png.');
|
|
637
648
|
}
|
|
638
649
|
|
|
639
650
|
async function iconShow(args) {
|
|
@@ -715,6 +726,40 @@ async function iconDownload(args) {
|
|
|
715
726
|
console.log(` ${outputPath}`);
|
|
716
727
|
}
|
|
717
728
|
|
|
729
|
+
async function iconPng(args) {
|
|
730
|
+
const options = parseOptions(args);
|
|
731
|
+
const [iconReference] = options._;
|
|
732
|
+
const reference = parseIconReference(iconReference);
|
|
733
|
+
const payload = buildIconPngPayload(reference, options);
|
|
734
|
+
const download = await proApiDownload(`/api/pro/icons/${encodeURIComponent(reference.id)}/png-export`, {
|
|
735
|
+
method: 'POST',
|
|
736
|
+
body: payload,
|
|
737
|
+
headers: {
|
|
738
|
+
'Content-Type': 'application/json',
|
|
739
|
+
},
|
|
740
|
+
});
|
|
741
|
+
const outputPath = resolvePngDownloadPath(options.output || options.o, download.filename, process.cwd());
|
|
742
|
+
|
|
743
|
+
await writeBinaryFile(outputPath, download.bytes, booleanOption(options.force, false));
|
|
744
|
+
|
|
745
|
+
if (options.json) {
|
|
746
|
+
printJson({
|
|
747
|
+
icon: {
|
|
748
|
+
id: reference.id,
|
|
749
|
+
name: reference.slug,
|
|
750
|
+
},
|
|
751
|
+
outputPath,
|
|
752
|
+
contentType: download.contentType,
|
|
753
|
+
filename: download.filename,
|
|
754
|
+
options: payload,
|
|
755
|
+
});
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
console.log(`Downloaded PNG export`);
|
|
760
|
+
console.log(` ${outputPath}`);
|
|
761
|
+
}
|
|
762
|
+
|
|
718
763
|
async function collection(args) {
|
|
719
764
|
const [subcommand, ...rest] = args;
|
|
720
765
|
|
|
@@ -1830,17 +1875,104 @@ function extensionSet(value) {
|
|
|
1830
1875
|
}
|
|
1831
1876
|
|
|
1832
1877
|
function buildExportOptions(options) {
|
|
1878
|
+
const formats = exportFormatsOption(options.formats);
|
|
1879
|
+
const png = buildCollectionPngOptions(options);
|
|
1880
|
+
const includesPng = formats?.includes('png-pack') || Object.keys(png).length > 0;
|
|
1881
|
+
const normalizedFormats = includesPng && formats === undefined
|
|
1882
|
+
? ['png-pack']
|
|
1883
|
+
: formats;
|
|
1884
|
+
|
|
1833
1885
|
return {
|
|
1834
|
-
formats:
|
|
1886
|
+
formats: normalizedFormats,
|
|
1835
1887
|
options: {
|
|
1836
1888
|
colorPolicy: enumOption(options['color-policy'], 'color policy', COLOR_POLICIES),
|
|
1837
1889
|
namingPolicy: enumOption(options['naming-policy'], 'naming policy', NAMING_POLICIES),
|
|
1838
1890
|
sizeProps: booleanFlagPair(options, 'size-props', 'no-size-props'),
|
|
1839
1891
|
typescript: booleanFlagPair(options, 'typescript', 'no-typescript'),
|
|
1892
|
+
png: includesPng ? png : undefined,
|
|
1840
1893
|
},
|
|
1841
1894
|
};
|
|
1842
1895
|
}
|
|
1843
1896
|
|
|
1897
|
+
function buildIconPngPayload(reference, options) {
|
|
1898
|
+
return {
|
|
1899
|
+
iconName: reference.slug,
|
|
1900
|
+
sizes: integerCsvOption(options.sizes ?? options.size, 'PNG size', PNG_SIZES, [512]),
|
|
1901
|
+
densities: integerCsvOption(options.densities ?? options.density, 'PNG density', PNG_DENSITIES, [1]),
|
|
1902
|
+
backgroundType: enumOption(options.background, 'PNG background', PNG_BACKGROUNDS),
|
|
1903
|
+
backgroundColor: options['background-color'],
|
|
1904
|
+
iconColorMode: enumOption(options.color, 'PNG icon color mode', PNG_COLORS),
|
|
1905
|
+
iconColor: options['icon-color'],
|
|
1906
|
+
padding: integerOption(options.padding, 'PNG padding'),
|
|
1907
|
+
fit: enumOption(options.fit, 'PNG fit mode', PNG_FITS),
|
|
1908
|
+
filename: options.filename,
|
|
1909
|
+
zip: booleanOption(options.zip, undefined),
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
function buildCollectionPngOptions(options) {
|
|
1914
|
+
return stripUndefined({
|
|
1915
|
+
sizes: integerCsvOption(options['png-sizes'] ?? options['png-size'], 'PNG size', PNG_SIZES, undefined),
|
|
1916
|
+
densities: integerCsvOption(options['png-densities'] ?? options['png-density'], 'PNG density', [1, 2, 3], undefined),
|
|
1917
|
+
backgroundType: enumOption(options['png-background'], 'PNG background', PNG_BACKGROUNDS),
|
|
1918
|
+
backgroundColor: options['png-background-color'],
|
|
1919
|
+
iconColorMode: enumOption(options['png-color'], 'PNG icon color mode', PNG_COLORS),
|
|
1920
|
+
iconColor: options['png-icon-color'],
|
|
1921
|
+
padding: integerOption(options['png-padding'], 'PNG padding'),
|
|
1922
|
+
fit: enumOption(options['png-fit'], 'PNG fit mode', PNG_FITS),
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
function exportFormatsOption(value) {
|
|
1927
|
+
const values = csvOption(value);
|
|
1928
|
+
|
|
1929
|
+
if (!values) {
|
|
1930
|
+
return undefined;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
const normalized = values.map((item) => item === 'png' ? 'png-pack' : item);
|
|
1934
|
+
const invalid = normalized.filter((item) => !EXPORT_FORMATS.includes(item));
|
|
1935
|
+
if (invalid.length > 0) {
|
|
1936
|
+
throw usageError(`Invalid export format: ${invalid.join(', ')}. Allowed values: ${EXPORT_FORMATS.join(', ')}, png`);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
return [...new Set(normalized)];
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
function integerCsvOption(value, label, allowed, fallback) {
|
|
1943
|
+
const values = csvOption(value);
|
|
1944
|
+
|
|
1945
|
+
if (!values) {
|
|
1946
|
+
return fallback;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
const parsed = values.map((item) => Number(item));
|
|
1950
|
+
const invalid = parsed.filter((item) => !Number.isInteger(item) || !allowed.includes(item));
|
|
1951
|
+
|
|
1952
|
+
if (invalid.length > 0) {
|
|
1953
|
+
throw usageError(`Invalid ${label}: ${invalid.join(', ')}. Allowed values: ${allowed.join(', ')}`);
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
return [...new Set(parsed)];
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
function integerOption(value, label) {
|
|
1960
|
+
if (value === undefined || value === true || value === '') {
|
|
1961
|
+
return undefined;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
const parsed = Number(value);
|
|
1965
|
+
if (!Number.isInteger(parsed)) {
|
|
1966
|
+
throw usageError(`Invalid ${label}: ${value}. Expected an integer.`);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
return parsed;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
function stripUndefined(value) {
|
|
1973
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1844
1976
|
function enumOption(value, label, allowed) {
|
|
1845
1977
|
if (value === undefined || value === true || value === '') {
|
|
1846
1978
|
return undefined;
|
|
@@ -1894,6 +2026,7 @@ Usage:
|
|
|
1894
2026
|
svgicons icon url <icon-ref>
|
|
1895
2027
|
svgicons icon open <icon-ref>
|
|
1896
2028
|
svgicons icon download <icon-id-name> [--output ./icons] [--force]
|
|
2029
|
+
svgicons icon png <icon-id-name> [--size 512] [--density 1,2] [--output ./icons] [--force]
|
|
1897
2030
|
svgicons collection list [--json]
|
|
1898
2031
|
svgicons collection create --name "Dashboard icons"
|
|
1899
2032
|
svgicons collection show <collection-id-or-name> [--icons] [--json]
|
|
@@ -1902,7 +2035,7 @@ Usage:
|
|
|
1902
2035
|
svgicons collection add <collection-id-or-name> <icon-id...>
|
|
1903
2036
|
svgicons collection remove <collection-id-or-name> <icon-id...>
|
|
1904
2037
|
svgicons collection delete <collection-id-or-name> [--yes]
|
|
1905
|
-
svgicons collection export <collection-id-or-name> [--formats react-ts,vue] [--no-size-props] [--no-typescript] [--output ./exports]
|
|
2038
|
+
svgicons collection export <collection-id-or-name> [--formats react-ts,vue,png-pack] [--png-sizes 24,48,512] [--no-size-props] [--no-typescript] [--output ./exports]
|
|
1906
2039
|
svgicons export status <export-id> --collection <collection-id-or-name>
|
|
1907
2040
|
svgicons export download <export-id> --collection <collection-id-or-name> [--output ./exports]
|
|
1908
2041
|
svgicons init [--collection <collection-id-or-name>] [--output svgicons-icons]
|
package/src/downloads.js
CHANGED
|
@@ -95,6 +95,25 @@ export function resolveArchiveDownloadPath(output, filename, cwd = process.cwd()
|
|
|
95
95
|
return join(target, safeFilename);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
export function resolvePngDownloadPath(output, filename, cwd = process.cwd()) {
|
|
99
|
+
const safeFilename = sanitizeFileName(filename || 'svgicons-icon.png', 'png');
|
|
100
|
+
|
|
101
|
+
if (!output || output === true) {
|
|
102
|
+
return resolve(cwd, safeFilename);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const target = isAbsolute(String(output))
|
|
106
|
+
? String(output)
|
|
107
|
+
: resolve(cwd, String(output));
|
|
108
|
+
|
|
109
|
+
const extension = extname(target).toLowerCase();
|
|
110
|
+
if (extension === '.png' || extension === '.zip') {
|
|
111
|
+
return target;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return join(target, safeFilename);
|
|
115
|
+
}
|
|
116
|
+
|
|
98
117
|
export async function writeIconSvg(path, svg, force = false) {
|
|
99
118
|
if (!svg || typeof svg !== 'string') {
|
|
100
119
|
throw new Error('The icon response did not include SVG markup.');
|