@rokkit/cli 1.0.0-next.120

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 ADDED
@@ -0,0 +1,102 @@
1
+ # @rokkit/cli
2
+
3
+ A command-line utility for bundling and converting SVG icons into JSON formats compatible with Iconify.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Install globally
9
+ npm install -g @rokkit/cli
10
+
11
+ # Or use directly with npx
12
+ npx @rokkit/cli [command]
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```bash
18
+ rokkit-cli [command] [options]
19
+ ```
20
+
21
+ ### Commands
22
+
23
+ #### `bundle`
24
+
25
+ Bundle icons in source folders into JSON files.
26
+
27
+ ```bash
28
+ rokkit-cli bundle -i ./icons -o ./dist
29
+ ```
30
+
31
+ This command:
32
+
33
+ 1. Scans the input directory for subdirectories
34
+ 2. Each subdirectory is treated as an icon set
35
+ 3. Creates a JSON bundle for each icon set in the output directory
36
+
37
+ #### `build`
38
+
39
+ Build complete Iconify JSON packages for icon sets.
40
+
41
+ ```bash
42
+ rokkit-cli build -i ./icons -o ./dist
43
+ ```
44
+
45
+ Similar to `bundle`, but creates a full Iconify package structure for each icon set.
46
+
47
+ ### Options
48
+
49
+ | Option | Alias | Description | Default |
50
+ | ---------- | ----- | ---------------------------------- | ------------------- |
51
+ | `--input` | `-i` | Source folder containing icon sets | `./src` |
52
+ | `--output` | `-o` | Target folder for output files | `./build` |
53
+ | `--config` | `-c` | Path to custom config file | `./src/config.json` |
54
+
55
+ ## Configuration File
56
+
57
+ The configuration file should be a JSON file with the following structure:
58
+
59
+ ```json
60
+ {
61
+ "package": {
62
+ "namespace": "@your-namespace",
63
+ "version": "1.0.0",
64
+ "homepage": "https://github.com/your-username/your-repo"
65
+ },
66
+ "icon-set-1": {
67
+ "color": true
68
+ },
69
+ "icon-set-2": {
70
+ "color": false
71
+ }
72
+ }
73
+ ```
74
+
75
+ - `package`: Global package information
76
+ - `namespace`: Namespace for the icon package
77
+ - `version`: Version number
78
+ - `homepage`: Repository URL
79
+ - Each icon set can have specific configuration:
80
+ - `color`: Whether to preserve colors (true) or convert to `currentColor` (false)
81
+
82
+ ## Examples
83
+
84
+ ### Basic usage
85
+
86
+ ```bash
87
+ # Bundle icons
88
+ rokkit-cli bundle -i ./src/icons -o ./dist
89
+
90
+ # Build Iconify packages
91
+ rokkit-cli build -i ./src/icons -o ./dist
92
+ ```
93
+
94
+ ### Using a custom config file
95
+
96
+ ```bash
97
+ rokkit-cli build -c ./my-config.json -i ./src/icons -o ./dist
98
+ ```
99
+
100
+ ## License
101
+
102
+ MIT
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Clean up and optimise SVG icon
3
+ *
4
+ * @param {string} svg SVG code
5
+ * @param {boolean} color True if color should be preserved
6
+ */
7
+ export function cleanAndOptimizeIcon(svg: string, color: boolean): void;
8
+ /**
9
+ * Process icons
10
+ *
11
+ * @param {IconifyTools} iconSet Icon set
12
+ * @param {boolean} color True if color should be preserved
13
+ */
14
+ export function processIcons(iconSet: IconifyTools, color: boolean): void;
15
+ /**
16
+ * Convert individual icons into a json bundle
17
+ *
18
+ * @param {string} folder Folder with icons
19
+ * @param {import('./types').IconBuilderOptions} options Options for conversion
20
+ * @returns {Promise<object>} The generated icon set
21
+ */
22
+ export function bundle(folder: string, options: import("./types").IconBuilderOptions): Promise<object>;
23
+ /**
24
+ * Convert icons to an iconify package
25
+ *
26
+ * @param {string} folder Folder with icons
27
+ * @param {import('./types').IconBuilderOptions} options Options for conversion
28
+ * @returns {Promise<object>} The generated icon set
29
+ */
30
+ export function convert(folder: string, options: import("./types").IconBuilderOptions): Promise<object>;
31
+ /**
32
+ * Read config file from path
33
+ *
34
+ * @param {string} configPath Path to config file
35
+ * @returns {object} Parsed config object
36
+ */
37
+ export function readConfigFile(configPath: string): object;
38
+ /**
39
+ * Get folders from directory
40
+ *
41
+ * @param {string} dir Directory to scan for folders
42
+ * @returns {string[]} List of folder names
43
+ */
44
+ export function getFolderNames(dir: string): string[];
45
+ /**
46
+ * Converts a folder of icons into an icon bundle
47
+ * @param {string[]} folders
48
+ * @param {import('./types').IconBuilderOptions} config
49
+ * @param {import('./types').FolderOptions} opts
50
+ */
51
+ export function bundleFolders(folders: string[], config: import("./types").IconBuilderOptions, opts: import("./types").FolderOptions): void;
52
+ /**
53
+ * Converts a folder of icons into an icon bundle
54
+ * @param {string[]} folders
55
+ * @param {import('./types').IconBuilderOptions} config
56
+ * @param {import('./types').FolderOptions} opts
57
+ */
58
+ export function convertFolders(folders: string[], config: import("./types").IconBuilderOptions, opts: import("./types").FolderOptions): void;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,24 @@
1
+ export type IconPackageInfo = {
2
+ namespace?: string | undefined;
3
+ version?: string | undefined;
4
+ homepage?: string | undefined;
5
+ };
6
+ export type BundleConfig = {
7
+ color?: boolean | undefined;
8
+ };
9
+ export type BundleConfiguration = {
10
+ package?: IconPackageInfo | undefined;
11
+ bundles?: {
12
+ [x: string]: BundleConfig;
13
+ } | undefined;
14
+ };
15
+ export type IconBuilderOptions = {
16
+ target: string;
17
+ prefix: string;
18
+ color: boolean;
19
+ package?: IconPackageInfo | undefined;
20
+ };
21
+ export type FolderOptions = {
22
+ input: string;
23
+ output: string;
24
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@rokkit/cli",
3
+ "version": "1.0.0-next.120",
4
+ "description": "Command line utilities for icon bundling and conversion.",
5
+ "author": "Jerry Thomas <me@jerrythomas.name>",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "rokkit-cli": "src/index.js"
9
+ },
10
+ "type": "module",
11
+ "scripts": {
12
+ "prepublishOnly": "bun clean && bun tsc --project tsconfig.build.json",
13
+ "clean": "rm -rf dist",
14
+ "build": "bun prepublishOnly"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "devDependencies": {
20
+ "iconify": "^1.4.0",
21
+ "vitest": "^1.2.0"
22
+ },
23
+ "dependencies": {
24
+ "@mdi/svg": "^7.4.47",
25
+ "sade": "^1.8.1",
26
+ "@iconify/tools": "4.0.6",
27
+ "@iconify/types": "^2.0.0",
28
+ "ramda": "^0.30.1"
29
+ },
30
+ "files": [
31
+ "lib/**/*",
32
+ "src/**/*.js",
33
+ "dist/**/*.d.ts",
34
+ "README.md",
35
+ "package.json"
36
+ ],
37
+ "exports": {
38
+ "./package.json": "./package.json",
39
+ "./ui.json": "./lib/base.json",
40
+ "./light.json": "./lib/light.json",
41
+ "./solid.json": "./lib/solid.json",
42
+ "./twotone.json": "./lib/twotone.json",
43
+ "./components.json": "./lib/components.json",
44
+ "./auth.json": "./lib/auth.json",
45
+ "./app.json": "./lib/app.json",
46
+ ".": "./lib/base.json",
47
+ "./utils": "./src/convert.js",
48
+ "./cli": "./src/index.js"
49
+ }
50
+ }
package/src/convert.js ADDED
@@ -0,0 +1,211 @@
1
+ /* eslint-disable no-console */
2
+ import {
3
+ importDirectory,
4
+ cleanupSVG,
5
+ runSVGO,
6
+ parseColors,
7
+ isEmptyColor,
8
+ exportJSONPackage
9
+ } from '@iconify/tools'
10
+ import fs from 'fs'
11
+ import path from 'path'
12
+ import { pick } from 'ramda'
13
+ import './types'
14
+ /**
15
+ * Clean up and optimise SVG icon
16
+ *
17
+ * @param {string} svg SVG code
18
+ * @param {boolean} color True if color should be preserved
19
+ */
20
+ export function cleanAndOptimizeIcon(svg, color) {
21
+ // Clean up icon code
22
+ cleanupSVG(svg)
23
+
24
+ if (!color) {
25
+ parseColors(svg, {
26
+ defaultColor: 'currentColor',
27
+ callback: (_, colorStr, value) => {
28
+ return !value || isEmptyColor(value) ? colorStr : 'currentColor'
29
+ }
30
+ })
31
+ }
32
+
33
+ // Optimise
34
+ runSVGO(svg)
35
+ }
36
+
37
+ /**
38
+ * Process icons
39
+ *
40
+ * @param {IconifyTools} iconSet Icon set
41
+ * @param {boolean} color True if color should be preserved
42
+ */
43
+ export function processIcons(iconSet, color) {
44
+ iconSet.forEach((name) => {
45
+ const svg = iconSet.toSVG(name)
46
+
47
+ try {
48
+ cleanAndOptimizeIcon(svg, color)
49
+ } catch (err) {
50
+ console.error(`Error parsing ${name}: ${err.reason}`)
51
+ iconSet.remove(name)
52
+ return
53
+ }
54
+
55
+ iconSet.fromSVG(name, svg)
56
+ })
57
+ }
58
+
59
+ /**
60
+ * Convert individual icons into a json bundle
61
+ *
62
+ * @param {string} folder Folder with icons
63
+ * @param {import('./types').IconBuilderOptions} options Options for conversion
64
+ * @returns {Promise<object>} The generated icon set
65
+ */
66
+ export async function bundle(folder, options) {
67
+ const { prefix, color = false, target = '.' } = options
68
+
69
+ // Ensure the target directory exists
70
+ const targetDir = path.resolve(target)
71
+ if (!fs.existsSync(targetDir)) {
72
+ fs.mkdirSync(targetDir, { recursive: true })
73
+ }
74
+
75
+ const iconSet = await importDirectory(folder, { prefix })
76
+
77
+ // Validate, clean up, fix palette and optimise
78
+ processIcons(iconSet, color)
79
+
80
+ // Export
81
+ const collection = JSON.stringify(iconSet.export(), null, 2)
82
+ const outputPath = path.join(targetDir, `${prefix}.json`)
83
+ fs.writeFileSync(outputPath, collection, 'utf8')
84
+
85
+ console.info(`> Bundle created: ${outputPath}`)
86
+ return iconSet
87
+ }
88
+
89
+ /**
90
+ *
91
+ * @param {*} options
92
+ * @returns
93
+ */
94
+ function extractPackageInfo(options) {
95
+ const namespace = options.package?.namespace
96
+ // Create package info with sensible defaults
97
+ return {
98
+ // Use prefix as package name if no namespace provided
99
+ name: [namespace, options.prefix].filter(Boolean).join('/'),
100
+ // Default version is 1.0.0
101
+ version: options.package?.version || '1.0.0',
102
+ // Homepage is optional
103
+ ...pick(['homepage'], options.package ?? {})
104
+ }
105
+ }
106
+ /**
107
+ * Convert icons to an iconify package
108
+ *
109
+ * @param {string} folder Folder with icons
110
+ * @param {import('./types').IconBuilderOptions} options Options for conversion
111
+ * @returns {Promise<object>} The generated icon set
112
+ */
113
+ export async function convert(folder, options) {
114
+ // First bundle the icons
115
+ const iconSet = await bundle(folder, options)
116
+
117
+ // Create the target directory if it doesn't exist
118
+ const targetDir = path.resolve(options.target)
119
+ if (!fs.existsSync(targetDir)) {
120
+ fs.mkdirSync(targetDir, { recursive: true })
121
+ }
122
+
123
+ // Export as iconify package
124
+ const packageTarget = path.join(targetDir, iconSet.prefix)
125
+ await exportJSONPackage(iconSet, {
126
+ target: packageTarget,
127
+ package: extractPackageInfo(options),
128
+ cleanup: true
129
+ })
130
+
131
+ console.info(`> Package created: ${packageTarget}`)
132
+ return iconSet
133
+ }
134
+
135
+ /**
136
+ * Read config file from path
137
+ *
138
+ * @param {string} configPath Path to config file
139
+ * @returns {object} Parsed config object
140
+ */
141
+ export function readConfigFile(configPath) {
142
+ try {
143
+ const configFilePath = path.resolve(configPath)
144
+ if (fs.existsSync(configFilePath)) {
145
+ const config = JSON.parse(fs.readFileSync(configFilePath, 'utf8'))
146
+ return config
147
+ }
148
+ console.warn(`Config file not found: ${configFilePath}, using defaults`)
149
+ return {}
150
+ } catch (err) {
151
+ console.error(`Error reading config file: ${err.message}`)
152
+ return {}
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Get folders from directory
158
+ *
159
+ * @param {string} dir Directory to scan for folders
160
+ * @returns {string[]} List of folder names
161
+ */
162
+ export function getFolderNames(dir) {
163
+ try {
164
+ const dirPath = path.resolve(dir)
165
+ return fs
166
+ .readdirSync(dirPath, { withFileTypes: true })
167
+ .filter((dirent) => dirent.isDirectory())
168
+ .map((dirent) => dirent.name)
169
+ } catch (err) {
170
+ console.error(`Error reading directory: ${err.message}`)
171
+ return []
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Converts a folder of icons into an icon bundle
177
+ * @param {string[]} folders
178
+ * @param {import('./types').IconBuilderOptions} config
179
+ * @param {import('./types').FolderOptions} opts
180
+ */
181
+ export function bundleFolders(folders, config, opts) {
182
+ for (const folder of folders) {
183
+ const folderPath = path.join(opts.input, folder)
184
+ const folderConfig = config.bundles?.[folder] || {}
185
+
186
+ bundle(folderPath, {
187
+ target: opts.output,
188
+ color: folderConfig.color || false,
189
+ prefix: folder
190
+ })
191
+ }
192
+ }
193
+ /**
194
+ * Converts a folder of icons into an icon bundle
195
+ * @param {string[]} folders
196
+ * @param {import('./types').IconBuilderOptions} config
197
+ * @param {import('./types').FolderOptions} opts
198
+ */
199
+ export function convertFolders(folders, config, opts) {
200
+ for (const folder of folders) {
201
+ const folderPath = path.join(opts.input, folder)
202
+ const folderConfig = config.bundles[folder] || {}
203
+
204
+ convert(folderPath, {
205
+ target: opts.output,
206
+ color: folderConfig.color || false,
207
+ prefix: folder,
208
+ package: config.package || {}
209
+ })
210
+ }
211
+ }
package/src/index.js ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ import sade from 'sade'
4
+ import { bundleFolders, convertFolders, readConfigFile, getFolderNames } from './convert.js'
5
+ // import path from 'path'
6
+ import pkg from '../package.json' with { type: 'json' }
7
+
8
+ const prog = sade(pkg.name)
9
+
10
+ prog
11
+ .version(pkg.version)
12
+ .option('-c, --config', 'Provide path to custom config', 'config.json')
13
+ .option('-i, --input', 'Specify the source folder', './src')
14
+ .option('-o, --output', 'Specify the output folder', './lib')
15
+
16
+ prog
17
+ .command('bundle')
18
+ .describe('Bundle icons in the source folder into json files.')
19
+ .example(`${pkg.name} bundle -i src -o build --config my-conf.json`)
20
+ .action((opts) => {
21
+ console.info(`> Bundling from ${opts.input} to ${opts.output}`)
22
+ // Read the configuration
23
+ const config = readConfigFile(`${opts.input}/${opts.config}`)
24
+ // Find folders under input as bundles
25
+ const folders = getFolderNames(opts.input)
26
+
27
+ if (folders.length === 0) {
28
+ console.warn(`No folders found in ${opts.input}`)
29
+ return
30
+ }
31
+
32
+ console.info(`> Found folders: ${folders.join(', ')}`)
33
+ bundleFolders(folders, config, opts)
34
+ // Process each folder as an icon bundle
35
+ })
36
+
37
+ prog
38
+ .command('build')
39
+ .describe('Build a complete iconify json package for each icon folder')
40
+ .example(`${pkg.name} build -i src -o build --config my-conf.json`)
41
+ .action((opts) => {
42
+ console.info(`> Building iconify packages from ${opts.input} to ${opts.output}`)
43
+ const config = readConfigFile(`${opts.input}/${opts.config}`)
44
+ const folders = getFolderNames(opts.input)
45
+
46
+ if (folders.length === 0) {
47
+ console.warn(`No folders found in ${opts.input}`)
48
+ return
49
+ }
50
+
51
+ console.info(`> Found folders: ${folders.join(', ')}`)
52
+ // Process each folder as an icon set
53
+ convertFolders(folders, config, opts)
54
+ })
55
+
56
+ prog.parse(process.argv)
package/src/types.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @typedef {Object} IconPackageInfo
3
+ * @property {string} [namespace]
4
+ * @property {string} [version]
5
+ * @property {string} [homepage]
6
+ */
7
+ /**
8
+ * @typedef {Object} BundleConfig
9
+ * @property {boolean} [color]
10
+ */
11
+
12
+ /**
13
+ * @typedef {Object} BundleConfiguration
14
+ * @property {IconPackageInfo} [package]
15
+ * @property {Object<string,BundleConfig} [bundles]
16
+ */
17
+
18
+ /**
19
+ * @typedef {Object} IconBuilderOptions
20
+ * @property {string} target
21
+ * @property {string} prefix
22
+ * @property {boolean} color
23
+ * @property {IconPackageInfo} [package]
24
+ */
25
+
26
+ /**
27
+ * @typedef {Object} FolderOptions
28
+ * @property {string} input
29
+ * @property {string} output
30
+ */
31
+ export {}