@khoaha/spek-cli 1.0.4 ā 1.0.6
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 +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +210 -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 -2
- package/bin/cli.js +0 -7
- 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/scripts/publish.sh
DELETED
|
@@ -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
|
-
}
|
package/src/config/manager.ts
DELETED
|
@@ -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
|
-
}
|
package/src/directus/client.ts
DELETED
|
@@ -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
|
-
}
|
package/src/download/handler.ts
DELETED
|
@@ -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
|
-
}
|