@khoaha/spek-cli 1.0.3 → 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.
Files changed (56) hide show
  1. package/dist/commands/svg-to-vd/handler.d.ts +14 -0
  2. package/dist/commands/svg-to-vd/handler.d.ts.map +1 -0
  3. package/dist/commands/svg-to-vd/handler.js +119 -0
  4. package/dist/commands/svg-to-vd/handler.js.map +1 -0
  5. package/dist/config/manager.d.ts +18 -0
  6. package/dist/config/manager.d.ts.map +1 -0
  7. package/dist/config/manager.js +53 -0
  8. package/dist/config/manager.js.map +1 -0
  9. package/dist/directus/client.d.ts +14 -0
  10. package/dist/directus/client.d.ts.map +1 -0
  11. package/dist/directus/client.js +81 -0
  12. package/dist/directus/client.js.map +1 -0
  13. package/dist/download/handler.d.ts +6 -0
  14. package/dist/download/handler.d.ts.map +1 -0
  15. package/dist/download/handler.js +100 -0
  16. package/dist/download/handler.js.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +209 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/prompts/index.d.ts +14 -0
  22. package/dist/prompts/index.d.ts.map +1 -0
  23. package/dist/prompts/index.js +69 -0
  24. package/dist/prompts/index.js.map +1 -0
  25. package/dist/types/index.d.ts +67 -0
  26. package/dist/types/index.d.ts.map +1 -0
  27. package/dist/types/index.js +2 -0
  28. package/dist/types/index.js.map +1 -0
  29. package/dist/utils/file-utils.d.ts +28 -0
  30. package/dist/utils/file-utils.d.ts.map +1 -0
  31. package/dist/utils/file-utils.js +61 -0
  32. package/dist/utils/file-utils.js.map +1 -0
  33. package/dist/utils/svg-converter.d.ts +44 -0
  34. package/dist/utils/svg-converter.d.ts.map +1 -0
  35. package/dist/utils/svg-converter.js +149 -0
  36. package/dist/utils/svg-converter.js.map +1 -0
  37. package/package.json +7 -1
  38. package/docs/ARCHITECTURE.md +0 -286
  39. package/docs/PUBLISH_QUICK_REFERENCE.md +0 -135
  40. package/docs/SVG_TO_VECTOR_DRAWABLE.md +0 -186
  41. package/docs/TESTING.md +0 -429
  42. package/docs/USAGE_EXAMPLES.md +0 -520
  43. package/docs/WINDOWS_DEVELOPMENT.md +0 -487
  44. package/docs/WORKFLOW.md +0 -479
  45. package/scripts/publish.ps1 +0 -193
  46. package/scripts/publish.sh +0 -170
  47. package/src/commands/svg-to-vd/handler.ts +0 -131
  48. package/src/config/manager.ts +0 -58
  49. package/src/directus/client.ts +0 -101
  50. package/src/download/handler.ts +0 -116
  51. package/src/index.ts +0 -231
  52. package/src/prompts/index.ts +0 -76
  53. package/src/types/index.ts +0 -72
  54. package/src/utils/file-utils.ts +0 -69
  55. package/src/utils/svg-converter.ts +0 -196
  56. package/tsconfig.json +0 -20
@@ -1,170 +0,0 @@
1
- #!/bin/bash
2
-
3
- ##############################################################################
4
- # npm Publish Script (Cross-platform compatible)
5
- #
6
- # Usage:
7
- # ./scripts/publish.sh [version]
8
- #
9
- # Arguments:
10
- # version - One of: patch, minor, major, or specific version (e.g., 1.2.3)
11
- # Default: patch
12
- #
13
- # Environment Variables:
14
- # NPM_ACCESS_TOKEN - Required for authentication (set in .env)
15
- #
16
- # Examples:
17
- # ./scripts/publish.sh # Bump patch version (1.0.0 -> 1.0.1)
18
- # ./scripts/publish.sh minor # Bump minor version (1.0.0 -> 1.1.0)
19
- # ./scripts/publish.sh major # Bump major version (1.0.0 -> 2.0.0)
20
- # ./scripts/publish.sh 2.5.3 # Set specific version
21
- ##############################################################################
22
-
23
- set -e # Exit on error
24
-
25
- # Colors for output
26
- RED='\033[0;31m'
27
- GREEN='\033[0;32m'
28
- YELLOW='\033[1;33m'
29
- BLUE='\033[0;34m'
30
- NC='\033[0m' # No Color
31
-
32
- # Script directory
33
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
34
- PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
35
-
36
- # Load .env file if exists
37
- if [ -f "$PROJECT_DIR/.env" ]; then
38
- echo -e "${BLUE}Loading .env file...${NC}"
39
- export $(cat "$PROJECT_DIR/.env" | grep -v '^#' | xargs)
40
- fi
41
-
42
- # Check for NPM_ACCESS_TOKEN
43
- if [ -z "$NPM_ACCESS_TOKEN" ]; then
44
- echo -e "${RED}āŒ Error: NPM_ACCESS_TOKEN not found${NC}"
45
- echo -e "${YELLOW}Please set NPM_ACCESS_TOKEN in .env file${NC}"
46
- exit 1
47
- fi
48
-
49
- # Parse version argument (default: patch)
50
- VERSION_TYPE="${1:-patch}"
51
-
52
- # Validate version type
53
- if [[ ! "$VERSION_TYPE" =~ ^(patch|minor|major|[0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
54
- echo -e "${RED}āŒ Invalid version type: $VERSION_TYPE${NC}"
55
- echo -e "${YELLOW}Use: patch, minor, major, or specific version (e.g., 1.2.3)${NC}"
56
- exit 1
57
- fi
58
-
59
- echo -e "${BLUE}╔══════════════════════════════════════╗${NC}"
60
- echo -e "${BLUE}ā•‘ šŸ“¦ npm Publish Script ā•‘${NC}"
61
- echo -e "${BLUE}ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•${NC}"
62
- echo ""
63
-
64
- # Step 1: Check git status
65
- echo -e "${BLUE}1ļøāƒ£ Checking git status...${NC}"
66
- if [ -n "$(git status --porcelain)" ]; then
67
- echo -e "${YELLOW}āš ļø Warning: You have uncommitted changes${NC}"
68
- read -p "Continue anyway? (y/N): " -n 1 -r
69
- echo
70
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
71
- echo -e "${RED}āŒ Aborted${NC}"
72
- exit 1
73
- fi
74
- fi
75
- echo -e "${GREEN}āœ“ Git status checked${NC}\n"
76
-
77
- # Step 2: Clean install
78
- echo -e "${BLUE}2ļøāƒ£ Clean install dependencies...${NC}"
79
- rm -rf node_modules package-lock.json
80
- npm install
81
- echo -e "${GREEN}āœ“ Dependencies installed${NC}\n"
82
-
83
- # Step 3: Build
84
- echo -e "${BLUE}3ļøāƒ£ Building TypeScript...${NC}"
85
- npm run build
86
- if [ ! -d "dist" ]; then
87
- echo -e "${RED}āŒ Error: dist/ directory not found after build${NC}"
88
- exit 1
89
- fi
90
- echo -e "${GREEN}āœ“ Build successful${NC}\n"
91
-
92
- # Step 4: Test build output
93
- echo -e "${BLUE}4ļøāƒ£ Testing build output...${NC}"
94
- if [ ! -f "dist/index.js" ]; then
95
- echo -e "${RED}āŒ Error: dist/index.js not found${NC}"
96
- exit 1
97
- fi
98
- node dist/index.js --version > /dev/null 2>&1 || {
99
- echo -e "${YELLOW}āš ļø Warning: Version check failed (might be expected)${NC}"
100
- }
101
- echo -e "${GREEN}āœ“ Build output verified${NC}\n"
102
-
103
- # Step 5: Bump version
104
- echo -e "${BLUE}5ļøāƒ£ Bumping version ($VERSION_TYPE)...${NC}"
105
- npm version "$VERSION_TYPE" --no-git-tag-version
106
- NEW_VERSION=$(node -p "require('./package.json').version")
107
- echo -e "${GREEN}āœ“ Version bumped to: $NEW_VERSION${NC}\n"
108
-
109
- # Step 6: Create .npmrc with token
110
- echo -e "${BLUE}6ļøāƒ£ Configuring npm authentication...${NC}"
111
- echo "//registry.npmjs.org/:_authToken=${NPM_ACCESS_TOKEN}" > "$PROJECT_DIR/.npmrc"
112
- echo -e "${GREEN}āœ“ npm authentication configured${NC}\n"
113
-
114
- # Step 7: Dry run publish (optional)
115
- echo -e "${BLUE}7ļøāƒ£ Running dry-run publish...${NC}"
116
- npm publish --dry-run
117
- echo -e "${GREEN}āœ“ Dry-run successful${NC}\n"
118
-
119
- # Step 8: Confirm publish
120
- echo -e "${YELLOW}ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”${NC}"
121
- echo -e "${YELLOW}│ Ready to publish v$NEW_VERSION${NC}"
122
- echo -e "${YELLOW}│ Package: spek-cli │${NC}"
123
- echo -e "${YELLOW}ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜${NC}"
124
- echo ""
125
- read -p "Proceed with publish? (y/N): " -n 1 -r
126
- echo
127
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
128
- echo -e "${RED}āŒ Publish cancelled${NC}"
129
- rm -f "$PROJECT_DIR/.npmrc"
130
- exit 1
131
- fi
132
-
133
- # Step 9: Publish to npm
134
- echo -e "${BLUE}9ļøāƒ£ Publishing to npm...${NC}"
135
- npm publish --access public
136
- echo -e "${GREEN}āœ“ Published successfully!${NC}\n"
137
-
138
- # Step 10: Cleanup
139
- echo -e "${BLUE}🧹 Cleaning up...${NC}"
140
- rm -f "$PROJECT_DIR/.npmrc"
141
- echo -e "${GREEN}āœ“ Cleanup complete${NC}\n"
142
-
143
- # Step 11: Create git tag (optional)
144
- echo -e "${BLUE}šŸ·ļø Creating git tag...${NC}"
145
- git add package.json
146
- git commit -m "chore: bump version to v$NEW_VERSION" || echo "No changes to commit"
147
- git tag "v$NEW_VERSION"
148
- echo -e "${GREEN}āœ“ Git tag created: v$NEW_VERSION${NC}\n"
149
-
150
- # Step 12: Test installation
151
- echo -e "${BLUE}🧪 Testing installation...${NC}"
152
- echo -e "${YELLOW}Run this command to test:${NC}"
153
- echo -e "${GREEN} npx spek-cli@$NEW_VERSION --version${NC}"
154
- echo -e "${YELLOW}Or:${NC}"
155
- echo -e "${GREEN} npx spek-cli --version${NC}\n"
156
-
157
- # Success summary
158
- echo -e "${GREEN}╔══════════════════════════════════════╗${NC}"
159
- echo -e "${GREEN}ā•‘ āœ… Publish Complete! ā•‘${NC}"
160
- echo -e "${GREEN}ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•${NC}"
161
- echo ""
162
- echo -e "${BLUE}Package:${NC} spek-cli"
163
- echo -e "${BLUE}Version:${NC} $NEW_VERSION"
164
- echo -e "${BLUE}Registry:${NC} https://www.npmjs.com/package/spek-cli"
165
- echo ""
166
- echo -e "${YELLOW}Next steps:${NC}"
167
- echo -e " 1. Push git tag: ${GREEN}git push origin v$NEW_VERSION${NC}"
168
- echo -e " 2. Test install: ${GREEN}npx spek-cli@$NEW_VERSION${NC}"
169
- echo -e " 3. Verify on npm: ${GREEN}https://www.npmjs.com/package/spek-cli${NC}"
170
- echo ""
@@ -1,131 +0,0 @@
1
- /**
2
- * SVG to Vector Drawable Command Handler
3
- *
4
- * Handles the svg-to-vd command for converting SVG files/content to Android Vector Drawable XML
5
- */
6
-
7
- import chalk from 'chalk';
8
- import { convertSvgToVectorDrawable, validateSvgContent } from '../../utils/svg-converter.js';
9
- import { detectInputType, writeFileContent } from '../../utils/file-utils.js';
10
- import type { SvgToVdOptions, SvgConversionResult } from '../../types/index.js';
11
-
12
- /**
13
- * Handle SVG to Vector Drawable conversion command
14
- *
15
- * @param options - Conversion options from CLI
16
- * @returns Conversion result
17
- */
18
- export async function handleSvgToVd(options: SvgToVdOptions): Promise<SvgConversionResult> {
19
- try {
20
- console.log(chalk.blue('šŸ”„ Converting SVG to Vector Drawable...\n'));
21
-
22
- // Validate required options
23
- if (!options.input) {
24
- return {
25
- success: false,
26
- error: 'Input is required. Use -i or --input to specify SVG file path or content.\n' +
27
- chalk.dim('Example: spek-cli svg-to-vd -i ./icon.svg -o ./icon.xml')
28
- };
29
- }
30
-
31
- if (!options.output) {
32
- return {
33
- success: false,
34
- error: 'Output path is required. Use -o or --output to specify destination file path.\n' +
35
- chalk.dim('Example: spek-cli svg-to-vd -i ./icon.svg -o ./drawables/icon.xml')
36
- };
37
- }
38
-
39
- // Auto-detect input type (file path or SVG content)
40
- console.log(chalk.dim('šŸ“ Detecting input type...'));
41
- let svgContent: string;
42
- let inputType: string;
43
-
44
- try {
45
- const detection = await detectInputType(options.input);
46
- svgContent = detection.content;
47
- inputType = detection.isFile ? 'file' : 'content';
48
-
49
- if (detection.isFile) {
50
- console.log(chalk.dim(` āœ“ Reading from file: ${options.input}`));
51
- } else {
52
- console.log(chalk.dim(' āœ“ Using raw SVG content'));
53
- }
54
- } catch (error: any) {
55
- return {
56
- success: false,
57
- error: `Failed to read input: ${error.message}\n` +
58
- chalk.dim('Tip: Check if the file path is correct or if the SVG content is valid.')
59
- };
60
- }
61
-
62
- // Validate SVG content
63
- console.log(chalk.dim('šŸ” Validating SVG content...'));
64
- const validation = validateSvgContent(svgContent);
65
-
66
- if (!validation.isValid) {
67
- return {
68
- success: false,
69
- error: `Invalid SVG content:\n${validation.errors.map(e => ` • ${e}`).join('\n')}\n` +
70
- chalk.dim('Tip: Ensure your SVG has proper opening and closing <svg> tags.')
71
- };
72
- }
73
- console.log(chalk.dim(' āœ“ SVG content is valid'));
74
-
75
- // Convert SVG to Vector Drawable
76
- console.log(chalk.dim('āš™ļø Converting to Vector Drawable...'));
77
- const conversionOptions = options.tint ? { tint: options.tint } : {};
78
-
79
- let result;
80
- try {
81
- result = await convertSvgToVectorDrawable(svgContent, conversionOptions);
82
- } catch (error: any) {
83
- return {
84
- success: false,
85
- error: `Conversion failed: ${error.message}\n` +
86
- chalk.dim('Tip: Some complex SVG features may not be supported. Try simplifying your SVG.')
87
- };
88
- }
89
- console.log(chalk.dim(' āœ“ Conversion successful'));
90
-
91
- // Write output file
92
- console.log(chalk.dim(`šŸ’¾ Writing to: ${options.output}`));
93
- try {
94
- await writeFileContent(options.output, result.vectorDrawable);
95
- } catch (error: any) {
96
- return {
97
- success: false,
98
- error: `Failed to write output file: ${error.message}\n` +
99
- chalk.dim('Tip: Check if you have write permissions for the output directory.')
100
- };
101
- }
102
- console.log(chalk.dim(' āœ“ File written successfully'));
103
-
104
- // Show warnings if any
105
- if (result.warnings.length > 0) {
106
- console.log(chalk.yellow('\nāš ļø Warnings:'));
107
- result.warnings.forEach((warning: string) => {
108
- console.log(chalk.yellow(` • ${warning}`));
109
- });
110
- }
111
-
112
- // Show tint info if applied
113
- if (options.tint) {
114
- console.log(chalk.dim(`\nšŸŽØ Applied tint: ${options.tint}`));
115
- }
116
-
117
- console.log(chalk.green(`\nāœ“ Successfully converted to: ${options.output}`));
118
-
119
- return {
120
- success: true,
121
- outputPath: options.output
122
- };
123
-
124
- } catch (error: any) {
125
- return {
126
- success: false,
127
- error: `Unexpected error: ${error.message}\n` +
128
- chalk.dim('Please report this issue if it persists.')
129
- };
130
- }
131
- }
@@ -1,58 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
- import { homedir } from 'os';
3
- import { join } from 'path';
4
- import type { VaultConfig } from '../types/index.js';
5
-
6
- const CONFIG_DIR = join(homedir(), '.spek-cli');
7
- const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
8
-
9
- /**
10
- * Check if configuration file exists
11
- */
12
- export function configExists(): boolean {
13
- return existsSync(CONFIG_FILE);
14
- }
15
-
16
- /**
17
- * Load configuration from ~/.spek-cli/config.json
18
- */
19
- export function loadConfig(): VaultConfig | null {
20
- try {
21
- if (!configExists()) {
22
- return null;
23
- }
24
- const data = readFileSync(CONFIG_FILE, 'utf-8');
25
- return JSON.parse(data) as VaultConfig;
26
- } catch (error) {
27
- console.error('Error loading config:', error);
28
- return null;
29
- }
30
- }
31
-
32
- /**
33
- * Save configuration to ~/.spek-cli/config.json
34
- */
35
- export function saveConfig(config: Omit<VaultConfig, 'createdAt'>): void {
36
- try {
37
- // Ensure config directory exists
38
- if (!existsSync(CONFIG_DIR)) {
39
- mkdirSync(CONFIG_DIR, { recursive: true });
40
- }
41
-
42
- const fullConfig: VaultConfig = {
43
- ...config,
44
- createdAt: new Date().toISOString(),
45
- };
46
-
47
- writeFileSync(CONFIG_FILE, JSON.stringify(fullConfig, null, 2), 'utf-8');
48
- } catch (error) {
49
- throw new Error(`Failed to save config: ${error}`);
50
- }
51
- }
52
-
53
- /**
54
- * Get config file path for display purposes
55
- */
56
- export function getConfigPath(): string {
57
- return CONFIG_FILE;
58
- }
@@ -1,101 +0,0 @@
1
- import { createDirectus, rest, authentication, readAssetRaw, readFile } from '@directus/sdk';
2
- import type { VaultConfig, FileMetadata } from '../types/index.js';
3
-
4
- /**
5
- * Create authenticated Directus client
6
- */
7
- export function createAuthenticatedClient(config: VaultConfig) {
8
- const client = createDirectus(config.directusUrl)
9
- .with(authentication('json'))
10
- .with(rest());
11
-
12
- return client;
13
- }
14
-
15
- /**
16
- * Get file metadata from Directus
17
- */
18
- export async function getFileMetadata(
19
- config: VaultConfig,
20
- fileId: string
21
- ): Promise<FileMetadata> {
22
- try {
23
- const client = createAuthenticatedClient(config);
24
-
25
- // Set the access token
26
- await client.setToken(config.accessToken);
27
-
28
- // Get file metadata
29
- const fileInfo = await client.request(
30
- readFile(fileId, {
31
- fields: ['id', 'filename_download', 'title'],
32
- })
33
- );
34
-
35
- return {
36
- id: fileInfo.id,
37
- filename_download: fileInfo.filename_download || 'download.zip',
38
- title: fileInfo.title,
39
- };
40
- } catch (error: any) {
41
- if (error?.response?.status === 401) {
42
- throw new Error('Authentication failed. Please check your access token.');
43
- }
44
- if (error?.response?.status === 404) {
45
- throw new Error(`File not found: ${fileId}`);
46
- }
47
- throw new Error(`Failed to get file metadata: ${error?.message || error}`);
48
- }
49
- }
50
-
51
- /**
52
- * Download file from Directus using SDK
53
- */
54
- export async function downloadFile(
55
- config: VaultConfig,
56
- fileId: string
57
- ): Promise<Buffer> {
58
- try {
59
- const client = createAuthenticatedClient(config);
60
-
61
- // Set the access token
62
- await client.setToken(config.accessToken);
63
-
64
- // Download the file
65
- const fileData = await client.request(readAssetRaw(fileId));
66
-
67
- // Convert ReadableStream to Buffer
68
- if (fileData instanceof ReadableStream) {
69
- const reader = fileData.getReader();
70
- const chunks: Uint8Array[] = [];
71
-
72
- while (true) {
73
- const { done, value } = await reader.read();
74
- if (done) break;
75
- chunks.push(value);
76
- }
77
-
78
- const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
79
- const result = new Uint8Array(totalLength);
80
- let offset = 0;
81
-
82
- for (const chunk of chunks) {
83
- result.set(chunk, offset);
84
- offset += chunk.length;
85
- }
86
-
87
- return Buffer.from(result);
88
- }
89
-
90
- // If it's already a buffer-like object
91
- return Buffer.from(fileData as ArrayBuffer);
92
- } catch (error: any) {
93
- if (error?.response?.status === 401) {
94
- throw new Error('Authentication failed. Please check your access token.');
95
- }
96
- if (error?.response?.status === 404) {
97
- throw new Error(`File not found: ${fileId}`);
98
- }
99
- throw new Error(`Failed to download file: ${error?.message || error}`);
100
- }
101
- }
@@ -1,116 +0,0 @@
1
- import AdmZip from 'adm-zip';
2
- import { existsSync, mkdirSync, writeFileSync, unlinkSync } from 'fs';
3
- import { join, basename } from 'path';
4
- import { tmpdir } from 'os';
5
- import { downloadFile, getFileMetadata } from '../directus/client.js';
6
- import { promptOverwrite } from '../prompts/index.js';
7
- import type { VaultConfig, DownloadResult } from '../types/index.js';
8
-
9
- /**
10
- * Get folder name from filename (remove .zip extension)
11
- */
12
- function getFolderNameFromFile(filename: string): string {
13
- // Remove .zip extension if present
14
- const name = filename.replace(/\.zip$/i, '');
15
- // Sanitize folder name (remove invalid characters)
16
- return name.replace(/[<>:"/\\|?*]/g, '_');
17
- }
18
-
19
- /**
20
- * Check if target directory exists
21
- */
22
- function checkDirectoryExists(targetPath: string): boolean {
23
- return existsSync(targetPath);
24
- }
25
-
26
- /**
27
- * Download and extract file from Directus
28
- */
29
- export async function handleDownload(
30
- config: VaultConfig,
31
- fileId: string
32
- ): Promise<DownloadResult> {
33
- let tempFilePath: string | null = null;
34
-
35
- try {
36
- console.log(`\nšŸ“„ Downloading file: ${fileId}...`);
37
-
38
- // Get file metadata to get the filename
39
- const metadata = await getFileMetadata(config, fileId);
40
- const folderName = getFolderNameFromFile(metadata.filename_download);
41
-
42
- console.log(`šŸ“ File name: ${metadata.filename_download}`);
43
- console.log(`šŸ“‚ Extract to folder: ${folderName}`);
44
-
45
- // Download file from Directus
46
- const fileBuffer = await downloadFile(config, fileId);
47
-
48
- // Save to temp file
49
- tempFilePath = join(tmpdir(), `spek-cli-${Date.now()}.zip`);
50
- writeFileSync(tempFilePath, Buffer.from(fileBuffer));
51
-
52
- console.log('āœ“ Download complete');
53
-
54
- // Validate ZIP file
55
- console.log('šŸ” Validating ZIP file...');
56
- let zip: AdmZip;
57
- try {
58
- zip = new AdmZip(tempFilePath);
59
- } catch (error) {
60
- throw new Error('Invalid or corrupted ZIP file');
61
- }
62
-
63
- const entries = zip.getEntries();
64
- if (entries.length === 0) {
65
- throw new Error('ZIP file is empty');
66
- }
67
-
68
- console.log('āœ“ ZIP file is valid');
69
-
70
- // Create target directory path
71
- const cwd = process.cwd();
72
- const targetPath = join(cwd, folderName);
73
-
74
- // Check if directory already exists
75
- if (checkDirectoryExists(targetPath)) {
76
- console.log(`\nāš ļø Warning: Directory "${folderName}" already exists`);
77
- const shouldOverwrite = await promptOverwrite();
78
-
79
- if (!shouldOverwrite) {
80
- console.log('\nāŒ Operation cancelled by user');
81
- return {
82
- success: false,
83
- error: 'User cancelled overwrite',
84
- };
85
- }
86
- } else {
87
- // Create directory if it doesn't exist
88
- mkdirSync(targetPath, { recursive: true });
89
- }
90
-
91
- // Extract to target directory
92
- console.log('šŸ“¦ Extracting files...');
93
- zip.extractAllTo(targetPath, true); // true = overwrite
94
-
95
- console.log(`āœ“ Files extracted successfully to: ${folderName}/`);
96
-
97
- return {
98
- success: true,
99
- extractPath: targetPath,
100
- };
101
- } catch (error: any) {
102
- return {
103
- success: false,
104
- error: error?.message || String(error),
105
- };
106
- } finally {
107
- // Cleanup temp file
108
- if (tempFilePath && existsSync(tempFilePath)) {
109
- try {
110
- unlinkSync(tempFilePath);
111
- } catch {
112
- // Ignore cleanup errors
113
- }
114
- }
115
- }
116
- }