@khoaha/spek-cli 1.0.4 → 1.0.5
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/dist/commands/svg-to-vd/handler.d.ts +14 -0
- package/dist/commands/svg-to-vd/handler.d.ts.map +1 -0
- package/dist/commands/svg-to-vd/handler.js +119 -0
- package/dist/commands/svg-to-vd/handler.js.map +1 -0
- package/dist/config/manager.d.ts +18 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +53 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/directus/client.d.ts +14 -0
- package/dist/directus/client.d.ts.map +1 -0
- package/dist/directus/client.js +81 -0
- package/dist/directus/client.js.map +1 -0
- package/dist/download/handler.d.ts +6 -0
- package/dist/download/handler.d.ts.map +1 -0
- package/dist/download/handler.js +100 -0
- package/dist/download/handler.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +209 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/index.d.ts +14 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +69 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/types/index.d.ts +67 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/file-utils.d.ts +28 -0
- package/dist/utils/file-utils.d.ts.map +1 -0
- package/dist/utils/file-utils.js +61 -0
- package/dist/utils/file-utils.js.map +1 -0
- package/dist/utils/svg-converter.d.ts +44 -0
- package/dist/utils/svg-converter.d.ts.map +1 -0
- package/dist/utils/svg-converter.js +149 -0
- package/dist/utils/svg-converter.js.map +1 -0
- package/package.json +7 -1
- package/docs/ARCHITECTURE.md +0 -286
- package/docs/PUBLISH_QUICK_REFERENCE.md +0 -135
- package/docs/SVG_TO_VECTOR_DRAWABLE.md +0 -186
- package/docs/TESTING.md +0 -429
- package/docs/USAGE_EXAMPLES.md +0 -520
- package/docs/WINDOWS_DEVELOPMENT.md +0 -487
- package/docs/WORKFLOW.md +0 -479
- package/scripts/publish.ps1 +0 -193
- package/scripts/publish.sh +0 -170
- package/src/commands/svg-to-vd/handler.ts +0 -131
- package/src/config/manager.ts +0 -58
- package/src/directus/client.ts +0 -101
- package/src/download/handler.ts +0 -116
- package/src/index.ts +0 -231
- package/src/prompts/index.ts +0 -76
- package/src/types/index.ts +0 -72
- package/src/utils/file-utils.ts +0 -69
- package/src/utils/svg-converter.ts +0 -196
- package/tsconfig.json +0 -20
package/src/index.ts
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { configExists, loadConfig, saveConfig, getConfigPath } from './config/manager.js';
|
|
3
|
-
import { promptForConfig, promptForFileId } from './prompts/index.js';
|
|
4
|
-
import { handleDownload } from './download/handler.js';
|
|
5
|
-
import { handleSvgToVd } from './commands/svg-to-vd/handler.js';
|
|
6
|
-
import type { CliOptions, SvgToVdOptions } from './types/index.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Parse command line arguments
|
|
10
|
-
*/
|
|
11
|
-
function parseArgs(): CliOptions {
|
|
12
|
-
const args = process.argv.slice(2);
|
|
13
|
-
const options: CliOptions = {};
|
|
14
|
-
|
|
15
|
-
// Check for command
|
|
16
|
-
if (args.length > 0 && !args[0].startsWith('-')) {
|
|
17
|
-
if (args[0] === 'svg-to-vd') {
|
|
18
|
-
options.command = 'svg-to-vd';
|
|
19
|
-
options.svgToVd = parseSvgToVdArgs(args.slice(1));
|
|
20
|
-
return options;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Parse download options
|
|
25
|
-
for (let i = 0; i < args.length; i++) {
|
|
26
|
-
const arg = args[i];
|
|
27
|
-
|
|
28
|
-
if (arg === '-d' || arg === '--download') {
|
|
29
|
-
options.command = 'download';
|
|
30
|
-
options.fileId = args[i + 1];
|
|
31
|
-
i++; // Skip next arg
|
|
32
|
-
} else if (arg === '-h' || arg === '--help') {
|
|
33
|
-
showHelp();
|
|
34
|
-
process.exit(0);
|
|
35
|
-
} else if (arg === '-v' || arg === '--version') {
|
|
36
|
-
showVersion();
|
|
37
|
-
process.exit(0);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return options;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Parse svg-to-vd command arguments
|
|
46
|
-
*/
|
|
47
|
-
function parseSvgToVdArgs(args: string[]): SvgToVdOptions {
|
|
48
|
-
const options: Partial<SvgToVdOptions> = {};
|
|
49
|
-
|
|
50
|
-
for (let i = 0; i < args.length; i++) {
|
|
51
|
-
const arg = args[i];
|
|
52
|
-
|
|
53
|
-
if (arg === '-i' || arg === '--input') {
|
|
54
|
-
options.input = args[i + 1];
|
|
55
|
-
i++;
|
|
56
|
-
} else if (arg === '-o' || arg === '--output') {
|
|
57
|
-
options.output = args[i + 1];
|
|
58
|
-
i++;
|
|
59
|
-
} else if (arg === '--tint') {
|
|
60
|
-
options.tint = args[i + 1];
|
|
61
|
-
i++;
|
|
62
|
-
} else if (arg === '-h' || arg === '--help') {
|
|
63
|
-
showSvgToVdHelp();
|
|
64
|
-
process.exit(0);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return options as SvgToVdOptions;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Show help message
|
|
73
|
-
*/
|
|
74
|
-
function showHelp(): void {
|
|
75
|
-
console.log(`
|
|
76
|
-
${chalk.bold('spek-cli')} - Download and extract Figma design specs from Directus
|
|
77
|
-
|
|
78
|
-
${chalk.bold('USAGE')}
|
|
79
|
-
npx spek-cli [command] [options]
|
|
80
|
-
|
|
81
|
-
${chalk.bold('COMMANDS')}
|
|
82
|
-
svg-to-vd Convert SVG to Android Vector Drawable
|
|
83
|
-
(no command) Interactive download mode
|
|
84
|
-
|
|
85
|
-
${chalk.bold('OPTIONS')}
|
|
86
|
-
-d, --download <file-id> Download and extract file by ID
|
|
87
|
-
-h, --help Show this help message
|
|
88
|
-
-v, --version Show version
|
|
89
|
-
|
|
90
|
-
${chalk.bold('EXAMPLES')}
|
|
91
|
-
${chalk.dim('# Interactive mode (prompts for file ID)')}
|
|
92
|
-
npx spek-cli
|
|
93
|
-
|
|
94
|
-
${chalk.dim('# Direct download')}
|
|
95
|
-
npx spek-cli -d abc123-def456-ghi789
|
|
96
|
-
|
|
97
|
-
${chalk.dim('# Convert SVG to Vector Drawable')}
|
|
98
|
-
npx spek-cli svg-to-vd -i icon.svg -o icon.xml
|
|
99
|
-
npx spek-cli svg-to-vd --help
|
|
100
|
-
|
|
101
|
-
${chalk.bold('CONFIGURATION')}
|
|
102
|
-
Config is stored at: ${chalk.cyan(getConfigPath())}
|
|
103
|
-
On first run, you'll be prompted to enter:
|
|
104
|
-
- Directus instance URL
|
|
105
|
-
- Access token
|
|
106
|
-
`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Show SVG to Vector Drawable help
|
|
111
|
-
*/
|
|
112
|
-
function showSvgToVdHelp(): void {
|
|
113
|
-
console.log(`
|
|
114
|
-
${chalk.bold('spek-cli svg-to-vd')} - Convert SVG to Android Vector Drawable
|
|
115
|
-
|
|
116
|
-
${chalk.bold('USAGE')}
|
|
117
|
-
npx spek-cli svg-to-vd -i <input> -o <output> [options]
|
|
118
|
-
|
|
119
|
-
${chalk.bold('REQUIRED')}
|
|
120
|
-
-i, --input <input> SVG file path or raw SVG content
|
|
121
|
-
-o, --output <output> Output Vector Drawable XML file path
|
|
122
|
-
|
|
123
|
-
${chalk.bold('OPTIONS')}
|
|
124
|
-
--tint <color> Tint color (e.g., "#FF5722")
|
|
125
|
-
-h, --help Show this help message
|
|
126
|
-
|
|
127
|
-
${chalk.bold('EXAMPLES')}
|
|
128
|
-
${chalk.dim('# Convert SVG file')}
|
|
129
|
-
npx spek-cli svg-to-vd -i ./icons/heart.svg -o ./drawables/heart.xml
|
|
130
|
-
|
|
131
|
-
${chalk.dim('# With tint color')}
|
|
132
|
-
npx spek-cli svg-to-vd -i icon.svg -o icon.xml --tint "#FF5722"
|
|
133
|
-
|
|
134
|
-
${chalk.dim('# Using raw SVG content')}
|
|
135
|
-
npx spek-cli svg-to-vd -i "<svg>...</svg>" -o output.xml
|
|
136
|
-
|
|
137
|
-
${chalk.bold('NOTES')}
|
|
138
|
-
• Input is auto-detected as file path or raw SVG content
|
|
139
|
-
• Output file will be overwritten if it already exists
|
|
140
|
-
• Output directory is created automatically if needed
|
|
141
|
-
• Complex SVG features may not be fully supported
|
|
142
|
-
`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Show version
|
|
147
|
-
*/
|
|
148
|
-
function showVersion(): void {
|
|
149
|
-
// Read version from package.json
|
|
150
|
-
console.log('1.0.0');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Main CLI entry point
|
|
155
|
-
*/
|
|
156
|
-
async function main(): Promise<void> {
|
|
157
|
-
console.log(chalk.bold.blue('\n📦 spek-cli\n'));
|
|
158
|
-
|
|
159
|
-
// Parse arguments
|
|
160
|
-
const options = parseArgs();
|
|
161
|
-
|
|
162
|
-
// Handle svg-to-vd command
|
|
163
|
-
if (options.command === 'svg-to-vd') {
|
|
164
|
-
if (!options.svgToVd) {
|
|
165
|
-
console.log(chalk.red('\n❌ Invalid svg-to-vd command'));
|
|
166
|
-
showSvgToVdHelp();
|
|
167
|
-
process.exit(1);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const result = await handleSvgToVd(options.svgToVd);
|
|
171
|
-
|
|
172
|
-
if (result.success) {
|
|
173
|
-
console.log(chalk.green.bold('\n✓ Conversion complete!\n'));
|
|
174
|
-
process.exit(0);
|
|
175
|
-
} else {
|
|
176
|
-
console.log(chalk.red(`\n❌ Error: ${result.error}\n`));
|
|
177
|
-
process.exit(1);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Check if config exists, if not prompt for setup
|
|
182
|
-
let config = loadConfig();
|
|
183
|
-
|
|
184
|
-
if (!config) {
|
|
185
|
-
const newConfig = await promptForConfig();
|
|
186
|
-
|
|
187
|
-
if (!newConfig) {
|
|
188
|
-
console.log(chalk.yellow('\n⚠️ Setup cancelled'));
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
saveConfig(newConfig);
|
|
193
|
-
config = loadConfig();
|
|
194
|
-
|
|
195
|
-
if (!config) {
|
|
196
|
-
console.log(chalk.red('\n❌ Failed to save configuration'));
|
|
197
|
-
process.exit(1);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
console.log(chalk.green(`\n✓ Configuration saved to ${getConfigPath()}`));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Get file ID (from args or prompt)
|
|
204
|
-
let fileId: string | null | undefined = options.fileId;
|
|
205
|
-
|
|
206
|
-
if (!fileId) {
|
|
207
|
-
fileId = await promptForFileId();
|
|
208
|
-
|
|
209
|
-
if (!fileId) {
|
|
210
|
-
console.log(chalk.yellow('\n⚠️ No file ID provided'));
|
|
211
|
-
process.exit(1);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Download and extract
|
|
216
|
-
const result = await handleDownload(config, fileId);
|
|
217
|
-
|
|
218
|
-
if (result.success) {
|
|
219
|
-
console.log(chalk.green.bold('\n✓ Success!\n'));
|
|
220
|
-
process.exit(0);
|
|
221
|
-
} else {
|
|
222
|
-
console.log(chalk.red(`\n❌ Error: ${result.error}\n`));
|
|
223
|
-
process.exit(1);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Run CLI
|
|
228
|
-
main().catch((error) => {
|
|
229
|
-
console.error(chalk.red(`\n❌ Unexpected error: ${error.message}\n`));
|
|
230
|
-
process.exit(1);
|
|
231
|
-
});
|
package/src/prompts/index.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import prompts from 'prompts';
|
|
2
|
-
import type { VaultConfig } from '../types/index.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Prompt user for Directus configuration
|
|
6
|
-
*/
|
|
7
|
-
export async function promptForConfig(): Promise<Omit<VaultConfig, 'createdAt'> | null> {
|
|
8
|
-
console.log('\n🔧 Configuration not found. Let\'s set it up!\n');
|
|
9
|
-
|
|
10
|
-
const response = await prompts([
|
|
11
|
-
{
|
|
12
|
-
type: 'text',
|
|
13
|
-
name: 'directusUrl',
|
|
14
|
-
message: 'Enter your Directus instance URL:',
|
|
15
|
-
validate: (value: string) => {
|
|
16
|
-
if (!value) return 'URL is required';
|
|
17
|
-
try {
|
|
18
|
-
new URL(value);
|
|
19
|
-
return true;
|
|
20
|
-
} catch {
|
|
21
|
-
return 'Please enter a valid URL (e.g., https://my-instance.directus.app)';
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
type: 'password',
|
|
27
|
-
name: 'accessToken',
|
|
28
|
-
message: 'Enter your Directus access token:',
|
|
29
|
-
validate: (value: string) => (value ? true : 'Access token is required'),
|
|
30
|
-
},
|
|
31
|
-
]);
|
|
32
|
-
|
|
33
|
-
// User cancelled (Ctrl+C)
|
|
34
|
-
if (!response.directusUrl || !response.accessToken) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
directusUrl: response.directusUrl.trim(),
|
|
40
|
-
accessToken: response.accessToken.trim(),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Prompt user for file ID (interactive mode)
|
|
46
|
-
*/
|
|
47
|
-
export async function promptForFileId(): Promise<string | null> {
|
|
48
|
-
const response = await prompts({
|
|
49
|
-
type: 'text',
|
|
50
|
-
name: 'fileId',
|
|
51
|
-
message: 'Enter the file ID to download:',
|
|
52
|
-
validate: (value: string) => (value ? true : 'File ID is required'),
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// User cancelled
|
|
56
|
-
if (!response.fileId) {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return response.fileId.trim();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Prompt user whether to overwrite existing files
|
|
65
|
-
*/
|
|
66
|
-
export async function promptOverwrite(): Promise<boolean> {
|
|
67
|
-
const response = await prompts({
|
|
68
|
-
type: 'confirm',
|
|
69
|
-
name: 'overwrite',
|
|
70
|
-
message: 'Files already exist in current directory. Overwrite?',
|
|
71
|
-
initial: false,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// User cancelled or said no
|
|
75
|
-
return response.overwrite === true;
|
|
76
|
-
}
|
package/src/types/index.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration stored in ~/.spek-cli/config.json
|
|
3
|
-
*/
|
|
4
|
-
export interface VaultConfig {
|
|
5
|
-
/** Directus instance URL (e.g., https://my-instance.directus.app) */
|
|
6
|
-
directusUrl: string;
|
|
7
|
-
/** Directus access token for authentication */
|
|
8
|
-
accessToken: string;
|
|
9
|
-
/** Timestamp when config was created */
|
|
10
|
-
createdAt: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* CLI command options
|
|
15
|
-
*/
|
|
16
|
-
export interface CliOptions {
|
|
17
|
-
/** File ID to download (from -d or --download flag) */
|
|
18
|
-
fileId?: string;
|
|
19
|
-
/** Command to execute */
|
|
20
|
-
command?: 'download' | 'svg-to-vd';
|
|
21
|
-
/** SVG to Vector Drawable conversion options */
|
|
22
|
-
svgToVd?: SvgToVdOptions;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* SVG to Vector Drawable conversion options
|
|
27
|
-
*/
|
|
28
|
-
export interface SvgToVdOptions {
|
|
29
|
-
/** Input: SVG file path or raw SVG content */
|
|
30
|
-
input: string;
|
|
31
|
-
/** Output: Destination file path for Vector Drawable XML */
|
|
32
|
-
output: string;
|
|
33
|
-
/** Optional tint color (e.g., "#FF5722") */
|
|
34
|
-
tint?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* SVG conversion result
|
|
39
|
-
*/
|
|
40
|
-
export interface SvgConversionResult {
|
|
41
|
-
/** Whether conversion was successful */
|
|
42
|
-
success: boolean;
|
|
43
|
-
/** Path where file was written */
|
|
44
|
-
outputPath?: string;
|
|
45
|
-
/** Error message if failed */
|
|
46
|
-
error?: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Download result
|
|
52
|
-
*/
|
|
53
|
-
export interface DownloadResult {
|
|
54
|
-
/** Whether download was successful */
|
|
55
|
-
success: boolean;
|
|
56
|
-
/** Path where files were extracted */
|
|
57
|
-
extractPath?: string;
|
|
58
|
-
/** Error message if failed */
|
|
59
|
-
error?: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* File metadata from Directus
|
|
64
|
-
*/
|
|
65
|
-
export interface FileMetadata {
|
|
66
|
-
/** File ID */
|
|
67
|
-
id: string;
|
|
68
|
-
/** Original filename */
|
|
69
|
-
filename_download: string;
|
|
70
|
-
/** File title */
|
|
71
|
-
title?: string;
|
|
72
|
-
}
|
package/src/utils/file-utils.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, access } from 'fs/promises';
|
|
2
|
-
import { constants } from 'fs';
|
|
3
|
-
import { dirname } from 'path';
|
|
4
|
-
import { mkdir } from 'fs/promises';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Check if a file exists
|
|
8
|
-
*/
|
|
9
|
-
export async function fileExists(filePath: string): Promise<boolean> {
|
|
10
|
-
try {
|
|
11
|
-
await access(filePath, constants.F_OK);
|
|
12
|
-
return true;
|
|
13
|
-
} catch {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Read file content as string
|
|
20
|
-
*/
|
|
21
|
-
export async function readFileContent(filePath: string): Promise<string> {
|
|
22
|
-
return await readFile(filePath, 'utf8');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Write content to file (creates directory if needed)
|
|
27
|
-
*/
|
|
28
|
-
export async function writeFileContent(filePath: string, content: string): Promise<void> {
|
|
29
|
-
const dir = dirname(filePath);
|
|
30
|
-
|
|
31
|
-
// Ensure directory exists
|
|
32
|
-
await mkdir(dir, { recursive: true });
|
|
33
|
-
|
|
34
|
-
// Write file
|
|
35
|
-
await writeFile(filePath, content, 'utf8');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Auto-detect if input is a file path or SVG content
|
|
40
|
-
* Detection logic:
|
|
41
|
-
* 1. If starts with '<svg' -> treat as content
|
|
42
|
-
* 2. If file exists -> read file
|
|
43
|
-
* 3. Fallback -> treat as content
|
|
44
|
-
*/
|
|
45
|
-
export async function detectInputType(input: string): Promise<{ isFile: boolean; content: string }> {
|
|
46
|
-
const trimmedInput = input.trim();
|
|
47
|
-
|
|
48
|
-
// Check if it looks like SVG content
|
|
49
|
-
if (trimmedInput.startsWith('<svg')) {
|
|
50
|
-
return { isFile: false, content: trimmedInput };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Check if it's a file path that exists
|
|
54
|
-
if (await fileExists(input)) {
|
|
55
|
-
const content = await readFileContent(input);
|
|
56
|
-
return { isFile: true, content };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Fallback: treat as content
|
|
60
|
-
return { isFile: false, content: trimmedInput };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Generate output file path by replacing extension
|
|
65
|
-
*/
|
|
66
|
-
export function generateOutputPath(inputPath: string): string {
|
|
67
|
-
// Replace extension with .xml
|
|
68
|
-
return inputPath.replace(/\.[^.]+$/, '.xml');
|
|
69
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SVG to Vector Drawable Conversion Utility
|
|
3
|
-
*
|
|
4
|
-
* Converts SVG content to Android Vector Drawable XML format using vd-tool
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { writeFile, unlink, mkdtemp, readFile } from 'fs/promises';
|
|
8
|
-
import { join } from 'path';
|
|
9
|
-
import { tmpdir } from 'os';
|
|
10
|
-
import { vdConvert } from 'vd-tool';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Validation result for SVG content
|
|
14
|
-
*/
|
|
15
|
-
interface SvgValidationResult {
|
|
16
|
-
isValid: boolean;
|
|
17
|
-
errors: string[];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Conversion result
|
|
22
|
-
*/
|
|
23
|
-
interface ConversionResult {
|
|
24
|
-
vectorDrawable: string;
|
|
25
|
-
warnings: string[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Conversion options
|
|
30
|
-
*/
|
|
31
|
-
interface ConversionOptions {
|
|
32
|
-
/** Optional tint color to apply (e.g., "#FF5722") */
|
|
33
|
-
tint?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Validates SVG content format
|
|
38
|
-
*
|
|
39
|
-
* @param svgContent - The SVG content to validate
|
|
40
|
-
* @returns Validation result with isValid boolean and errors array
|
|
41
|
-
*/
|
|
42
|
-
export function validateSvgContent(svgContent: string): SvgValidationResult {
|
|
43
|
-
const errors: string[] = [];
|
|
44
|
-
|
|
45
|
-
if (!svgContent || typeof svgContent !== 'string') {
|
|
46
|
-
errors.push('SVG content is required and must be a string');
|
|
47
|
-
} else {
|
|
48
|
-
if (svgContent.trim().length === 0) {
|
|
49
|
-
errors.push('SVG content cannot be empty');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!svgContent.includes('<svg')) {
|
|
53
|
-
errors.push('SVG content must contain an opening <svg> tag');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!svgContent.includes('</svg>')) {
|
|
57
|
-
errors.push('SVG content must contain a closing </svg> tag');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Check for basic XML structure
|
|
61
|
-
const openTags = (svgContent.match(/<svg[^>]*>/g) || []).length;
|
|
62
|
-
const closeTags = (svgContent.match(/<\/svg>/g) || []).length;
|
|
63
|
-
|
|
64
|
-
if (openTags !== closeTags) {
|
|
65
|
-
errors.push('SVG content has mismatched <svg> tags');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
isValid: errors.length === 0,
|
|
71
|
-
errors
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Applies customizations to the Vector Drawable XML
|
|
77
|
-
*
|
|
78
|
-
* @param vectorDrawableXml - The Vector Drawable XML content
|
|
79
|
-
* @param options - Customization options
|
|
80
|
-
* @returns Customized Vector Drawable XML
|
|
81
|
-
*/
|
|
82
|
-
function applyCustomizations(vectorDrawableXml: string, options: ConversionOptions): string {
|
|
83
|
-
let customizedXml = vectorDrawableXml;
|
|
84
|
-
|
|
85
|
-
// Apply tint if specified
|
|
86
|
-
if (options.tint) {
|
|
87
|
-
customizedXml = customizedXml.replace(
|
|
88
|
-
/<vector([^>]*)>/,
|
|
89
|
-
`<vector$1 android:tint="${options.tint}">`
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return customizedXml;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Cleans up temporary files and directories
|
|
98
|
-
*
|
|
99
|
-
* @param svgFile - Path to temporary SVG file
|
|
100
|
-
* @param vdFile - Path to temporary Vector Drawable file
|
|
101
|
-
* @param tempDir - Path to temporary directory
|
|
102
|
-
*/
|
|
103
|
-
async function cleanup(svgFile: string | null, vdFile: string | null, tempDir: string | null): Promise<void> {
|
|
104
|
-
try {
|
|
105
|
-
if (svgFile) await unlink(svgFile).catch(() => {});
|
|
106
|
-
if (vdFile) await unlink(vdFile).catch(() => {});
|
|
107
|
-
if (tempDir) {
|
|
108
|
-
const { rm } = await import('fs/promises');
|
|
109
|
-
await rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
110
|
-
}
|
|
111
|
-
} catch (error) {
|
|
112
|
-
// Ignore cleanup errors
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Converts SVG content to Android Vector Drawable XML format
|
|
118
|
-
*
|
|
119
|
-
* @param svgContent - The SVG content to convert
|
|
120
|
-
* @param options - Conversion options
|
|
121
|
-
* @returns Conversion result with vectorDrawable XML and optional warnings
|
|
122
|
-
* @throws Error if conversion fails
|
|
123
|
-
*/
|
|
124
|
-
export async function convertSvgToVectorDrawable(
|
|
125
|
-
svgContent: string,
|
|
126
|
-
options: ConversionOptions = {}
|
|
127
|
-
): Promise<ConversionResult> {
|
|
128
|
-
// Input validation
|
|
129
|
-
if (!svgContent || typeof svgContent !== 'string') {
|
|
130
|
-
throw new Error('SVG content is required and must be a string');
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (svgContent.trim().length === 0) {
|
|
134
|
-
throw new Error('SVG content cannot be empty');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Basic SVG format validation
|
|
138
|
-
if (!svgContent.includes('<svg') || !svgContent.includes('</svg>')) {
|
|
139
|
-
throw new Error('Invalid SVG format: must contain <svg> tags');
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let tempDir: string | null = null;
|
|
143
|
-
let tempSvgFile: string | null = null;
|
|
144
|
-
let tempVdFile: string | null = null;
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
// Create temporary directory
|
|
148
|
-
tempDir = await mkdtemp(join(tmpdir(), 'svg-to-vd-'));
|
|
149
|
-
tempSvgFile = join(tempDir, 'input.svg');
|
|
150
|
-
tempVdFile = join(tempDir, 'input.xml');
|
|
151
|
-
|
|
152
|
-
// Write SVG content to temporary file
|
|
153
|
-
await writeFile(tempSvgFile, svgContent, 'utf8');
|
|
154
|
-
|
|
155
|
-
// Use vd-tool library for conversion
|
|
156
|
-
try {
|
|
157
|
-
await vdConvert(tempSvgFile, {
|
|
158
|
-
outDir: tempDir
|
|
159
|
-
});
|
|
160
|
-
} catch (conversionError: any) {
|
|
161
|
-
const errorMessage = conversionError.message || 'Unknown conversion error';
|
|
162
|
-
throw new Error(`SVG to Vector Drawable conversion failed: ${errorMessage}`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Read the generated Vector Drawable XML
|
|
166
|
-
let vectorDrawableContent: string;
|
|
167
|
-
try {
|
|
168
|
-
vectorDrawableContent = await readFile(tempVdFile, 'utf8');
|
|
169
|
-
} catch (readError) {
|
|
170
|
-
throw new Error('Failed to read converted Vector Drawable file');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Apply customizations if provided
|
|
174
|
-
if (options.tint) {
|
|
175
|
-
vectorDrawableContent = applyCustomizations(vectorDrawableContent, options);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Clean up temporary files
|
|
179
|
-
await cleanup(tempSvgFile, tempVdFile, tempDir);
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
vectorDrawable: vectorDrawableContent,
|
|
183
|
-
warnings: []
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
} catch (error) {
|
|
187
|
-
// Clean up temporary files in case of error
|
|
188
|
-
if (tempSvgFile || tempVdFile || tempDir) {
|
|
189
|
-
await cleanup(tempSvgFile, tempVdFile, tempDir).catch(() => {
|
|
190
|
-
// Ignore cleanup errors
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
throw error;
|
|
195
|
-
}
|
|
196
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ES2022",
|
|
5
|
-
"lib": ["ES2022"],
|
|
6
|
-
"moduleResolution": "node",
|
|
7
|
-
"outDir": "./dist",
|
|
8
|
-
"rootDir": "./src",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"resolveJsonModule": true,
|
|
14
|
-
"declaration": true,
|
|
15
|
-
"declarationMap": true,
|
|
16
|
-
"sourceMap": true
|
|
17
|
-
},
|
|
18
|
-
"include": ["src/**/*"],
|
|
19
|
-
"exclude": ["node_modules", "dist"]
|
|
20
|
-
}
|