📦 deps(thirdparty): update snapshots
This commit is contained in:
Executable
+216
@@ -0,0 +1,216 @@
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import prompts from 'prompts';
|
||||
import type { AIType } from '../types/index.js';
|
||||
import { AI_TYPES } from '../types/index.js';
|
||||
import { copyFolders, installFromZip, createTempDir, cleanup } from '../utils/extract.js';
|
||||
import { generatePlatformFiles, generateAllPlatformFiles } from '../utils/template.js';
|
||||
import { detectAIType, getAITypeDescription } from '../utils/detect.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import {
|
||||
getLatestRelease,
|
||||
getAssetUrl,
|
||||
downloadRelease,
|
||||
GitHubRateLimitError,
|
||||
GitHubDownloadError,
|
||||
} from '../utils/github.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
// From dist/index.js -> ../assets (one level up to cli/, then assets/)
|
||||
const ASSETS_DIR = join(__dirname, '..', 'assets');
|
||||
|
||||
interface InitOptions {
|
||||
ai?: AIType;
|
||||
force?: boolean;
|
||||
offline?: boolean;
|
||||
legacy?: boolean; // Use old ZIP-based install
|
||||
global?: boolean; // Install to home directory (global mode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to install from GitHub release (legacy method)
|
||||
* Returns the copied folders if successful, null if failed
|
||||
*/
|
||||
async function tryGitHubInstall(
|
||||
targetDir: string,
|
||||
aiType: AIType,
|
||||
spinner: ReturnType<typeof ora>
|
||||
): Promise<string[] | null> {
|
||||
let tempDir: string | null = null;
|
||||
|
||||
try {
|
||||
spinner.text = 'Fetching latest release from GitHub...';
|
||||
const release = await getLatestRelease();
|
||||
const assetUrl = getAssetUrl(release);
|
||||
|
||||
if (!assetUrl) {
|
||||
throw new GitHubDownloadError('No ZIP asset found in latest release');
|
||||
}
|
||||
|
||||
spinner.text = `Downloading ${release.tag_name}...`;
|
||||
tempDir = await createTempDir();
|
||||
const zipPath = join(tempDir, 'release.zip');
|
||||
|
||||
await downloadRelease(assetUrl, zipPath);
|
||||
|
||||
spinner.text = 'Extracting and installing files...';
|
||||
const { copiedFolders, tempDir: extractedTempDir } = await installFromZip(
|
||||
zipPath,
|
||||
targetDir,
|
||||
aiType
|
||||
);
|
||||
|
||||
// Cleanup temp directory
|
||||
await cleanup(extractedTempDir);
|
||||
|
||||
return copiedFolders;
|
||||
} catch (error) {
|
||||
// Cleanup temp directory on error
|
||||
if (tempDir) {
|
||||
await cleanup(tempDir);
|
||||
}
|
||||
|
||||
if (error instanceof GitHubRateLimitError) {
|
||||
spinner.warn('GitHub rate limit reached, using template generation...');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (error instanceof GitHubDownloadError) {
|
||||
spinner.warn('GitHub download failed, using template generation...');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Network errors or other fetch failures
|
||||
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||
spinner.warn('Network error, using template generation...');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Unknown errors - still fall back
|
||||
spinner.warn('Download failed, using template generation...');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install using template generation (new method)
|
||||
*/
|
||||
async function templateInstall(
|
||||
targetDir: string,
|
||||
aiType: AIType,
|
||||
spinner: ReturnType<typeof ora>,
|
||||
isGlobal = false
|
||||
): Promise<string[]> {
|
||||
spinner.text = isGlobal
|
||||
? 'Generating skill files globally...'
|
||||
: 'Generating skill files from templates...';
|
||||
|
||||
if (aiType === 'all') {
|
||||
return generateAllPlatformFiles(targetDir, isGlobal);
|
||||
}
|
||||
|
||||
return generatePlatformFiles(targetDir, aiType, isGlobal);
|
||||
}
|
||||
|
||||
export async function initCommand(options: InitOptions): Promise<void> {
|
||||
logger.title('UI/UX Pro Max Installer');
|
||||
|
||||
let aiType = options.ai;
|
||||
|
||||
// Auto-detect or prompt for AI type
|
||||
if (!aiType) {
|
||||
const { detected, suggested } = detectAIType();
|
||||
|
||||
if (detected.length > 0) {
|
||||
logger.info(`Detected: ${detected.map(t => chalk.cyan(t)).join(', ')}`);
|
||||
}
|
||||
|
||||
const response = await prompts({
|
||||
type: 'select',
|
||||
name: 'aiType',
|
||||
message: 'Select AI assistant to install for:',
|
||||
choices: AI_TYPES.map(type => ({
|
||||
title: getAITypeDescription(type),
|
||||
value: type,
|
||||
})),
|
||||
initial: suggested ? AI_TYPES.indexOf(suggested) : 0,
|
||||
});
|
||||
|
||||
if (!response.aiType) {
|
||||
logger.warn('Installation cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
aiType = response.aiType as AIType;
|
||||
}
|
||||
|
||||
const isGlobal = !!options.global;
|
||||
const modeLabel = isGlobal ? ' (global)' : '';
|
||||
logger.info(`Installing for: ${chalk.cyan(getAITypeDescription(aiType))}${modeLabel}`);
|
||||
|
||||
const spinner = ora('Installing files...').start();
|
||||
const cwd = process.cwd();
|
||||
let copiedFolders: string[] = [];
|
||||
let installMethod = 'template';
|
||||
|
||||
try {
|
||||
// Use legacy ZIP-based install if --legacy flag is set
|
||||
if (options.legacy) {
|
||||
if (isGlobal) {
|
||||
spinner.warn('--global is not supported with --legacy mode, installing locally instead');
|
||||
}
|
||||
// Try GitHub download first (unless offline mode)
|
||||
if (!options.offline) {
|
||||
const githubResult = await tryGitHubInstall(cwd, aiType, spinner);
|
||||
if (githubResult) {
|
||||
copiedFolders = githubResult;
|
||||
installMethod = 'github';
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to bundled assets if GitHub failed or offline mode
|
||||
if (installMethod !== 'github') {
|
||||
spinner.text = 'Installing from bundled assets...';
|
||||
copiedFolders = await copyFolders(ASSETS_DIR, cwd, aiType);
|
||||
installMethod = 'bundled';
|
||||
}
|
||||
} else {
|
||||
// Use new template-based generation (default)
|
||||
copiedFolders = await templateInstall(cwd, aiType, spinner, isGlobal);
|
||||
installMethod = 'template';
|
||||
}
|
||||
|
||||
const methodMessage = {
|
||||
github: 'Installed from GitHub release!',
|
||||
bundled: 'Installed from bundled assets!',
|
||||
template: 'Generated from templates!',
|
||||
}[installMethod];
|
||||
|
||||
spinner.succeed(methodMessage);
|
||||
|
||||
// Summary
|
||||
console.log();
|
||||
logger.info('Installed folders:');
|
||||
copiedFolders.forEach(folder => {
|
||||
console.log(` ${chalk.green('+')} ${folder}`);
|
||||
});
|
||||
|
||||
console.log();
|
||||
logger.success('UI/UX Pro Max installed successfully!');
|
||||
|
||||
// Next steps
|
||||
console.log();
|
||||
console.log(chalk.bold('Next steps:'));
|
||||
console.log(chalk.dim(' 1. Restart your AI coding assistant'));
|
||||
console.log(chalk.dim(' 2. Try: "Build a landing page for a SaaS product"'));
|
||||
console.log();
|
||||
} catch (error) {
|
||||
spinner.fail('Installation failed');
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import { rm, stat } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import prompts from 'prompts';
|
||||
import type { AIType } from '../types/index.js';
|
||||
import { AI_TYPES, AI_FOLDERS } from '../types/index.js';
|
||||
import { detectAIType, getAITypeDescription } from '../utils/detect.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
interface UninstallOptions {
|
||||
ai?: AIType;
|
||||
global?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove skill directory for a given AI type
|
||||
*/
|
||||
async function removeSkillDir(baseDir: string, aiType: Exclude<AIType, 'all'>): Promise<string[]> {
|
||||
const folders = AI_FOLDERS[aiType];
|
||||
const removed: string[] = [];
|
||||
|
||||
for (const folder of folders) {
|
||||
const skillDir = join(baseDir, folder, 'skills', 'ui-ux-pro-max');
|
||||
try {
|
||||
await stat(skillDir);
|
||||
await rm(skillDir, { recursive: true, force: true });
|
||||
removed.push(`${folder}/skills/ui-ux-pro-max`);
|
||||
} catch (err: unknown) {
|
||||
// Skip non-existent dirs; re-throw permission or other errors
|
||||
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
export async function uninstallCommand(options: UninstallOptions): Promise<void> {
|
||||
logger.title('UI/UX Pro Max Uninstaller');
|
||||
|
||||
const isGlobal = !!options.global;
|
||||
const baseDir = isGlobal ? homedir() : process.cwd();
|
||||
const locationLabel = isGlobal ? '~/ (global)' : process.cwd();
|
||||
|
||||
let aiType = options.ai;
|
||||
const { detected: initialDetected } = detectAIType(baseDir);
|
||||
|
||||
// Auto-detect or prompt for AI type
|
||||
if (!aiType) {
|
||||
const detected = initialDetected;
|
||||
|
||||
if (detected.length === 0) {
|
||||
logger.warn('No installed AI skill directories detected.');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Detected installations: ${detected.map(t => chalk.cyan(t)).join(', ')}`);
|
||||
|
||||
const choices = [
|
||||
...detected.map(type => ({
|
||||
title: getAITypeDescription(type),
|
||||
value: type,
|
||||
})),
|
||||
{ title: 'All detected', value: 'all' as AIType },
|
||||
];
|
||||
|
||||
const response = await prompts({
|
||||
type: 'select',
|
||||
name: 'aiType',
|
||||
message: 'Select which AI skill to uninstall:',
|
||||
choices,
|
||||
});
|
||||
|
||||
if (!response.aiType) {
|
||||
logger.warn('Uninstall cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
aiType = response.aiType as AIType;
|
||||
}
|
||||
|
||||
// Confirm before removing
|
||||
const { confirmed } = await prompts({
|
||||
type: 'confirm',
|
||||
name: 'confirmed',
|
||||
message: `Remove UI/UX Pro Max skill for ${chalk.cyan(getAITypeDescription(aiType))} from ${locationLabel}?`,
|
||||
initial: false,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
logger.warn('Uninstall cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
const spinner = ora('Removing skill files...').start();
|
||||
|
||||
try {
|
||||
const allRemoved: string[] = [];
|
||||
|
||||
if (aiType === 'all') {
|
||||
// Remove for all detected platforms
|
||||
for (const type of initialDetected) {
|
||||
const removed = await removeSkillDir(baseDir, type);
|
||||
allRemoved.push(...removed);
|
||||
}
|
||||
} else {
|
||||
const removed = await removeSkillDir(baseDir, aiType);
|
||||
allRemoved.push(...removed);
|
||||
}
|
||||
|
||||
if (allRemoved.length === 0) {
|
||||
spinner.warn('No skill files found to remove');
|
||||
return;
|
||||
}
|
||||
|
||||
spinner.succeed('Skill files removed!');
|
||||
|
||||
console.log();
|
||||
logger.info('Removed:');
|
||||
allRemoved.forEach(folder => {
|
||||
console.log(` ${chalk.red('-')} ${folder}`);
|
||||
});
|
||||
|
||||
console.log();
|
||||
logger.success('UI/UX Pro Max uninstalled successfully!');
|
||||
console.log();
|
||||
} catch (error) {
|
||||
spinner.fail('Uninstall failed');
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import { getLatestRelease } from '../utils/github.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { initCommand } from './init.js';
|
||||
import type { AIType } from '../types/index.js';
|
||||
|
||||
interface UpdateOptions {
|
||||
ai?: AIType;
|
||||
}
|
||||
|
||||
export async function updateCommand(options: UpdateOptions): Promise<void> {
|
||||
logger.title('UI/UX Pro Max Updater');
|
||||
|
||||
const spinner = ora('Checking for updates...').start();
|
||||
|
||||
try {
|
||||
const release = await getLatestRelease();
|
||||
spinner.succeed(`Latest version: ${chalk.cyan(release.tag_name)}`);
|
||||
|
||||
console.log();
|
||||
logger.info('Running update (same as init with latest version)...');
|
||||
console.log();
|
||||
|
||||
await initCommand({
|
||||
ai: options.ai,
|
||||
force: true,
|
||||
});
|
||||
} catch (error) {
|
||||
spinner.fail('Update check failed');
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import { fetchReleases } from '../utils/github.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
export async function versionsCommand(): Promise<void> {
|
||||
const spinner = ora('Fetching available versions...').start();
|
||||
|
||||
try {
|
||||
const releases = await fetchReleases();
|
||||
|
||||
if (releases.length === 0) {
|
||||
spinner.warn('No releases found');
|
||||
return;
|
||||
}
|
||||
|
||||
spinner.succeed(`Found ${releases.length} version(s)\n`);
|
||||
|
||||
console.log(chalk.bold('Available versions:\n'));
|
||||
|
||||
releases.forEach((release, index) => {
|
||||
const isLatest = index === 0;
|
||||
const tag = release.tag_name;
|
||||
const date = new Date(release.published_at).toLocaleDateString();
|
||||
|
||||
if (isLatest) {
|
||||
console.log(` ${chalk.green('*')} ${chalk.bold(tag)} ${chalk.dim(`(${date})`)} ${chalk.green('[latest]')}`);
|
||||
} else {
|
||||
console.log(` ${tag} ${chalk.dim(`(${date})`)}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log();
|
||||
logger.dim('Use: uipro init --version <tag> to install a specific version');
|
||||
} catch (error) {
|
||||
spinner.fail('Failed to fetch versions');
|
||||
if (error instanceof Error) {
|
||||
logger.error(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user