📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-06-25 16:06:49 +00:00
parent 1da991f912
commit a225b70c17
376 changed files with 27320 additions and 4607 deletions
+8 -5
View File
@@ -13,6 +13,7 @@ import {
getLatestRelease,
getAssetUrl,
downloadRelease,
getGitHubTokenGuidance,
GitHubRateLimitError,
GitHubDownloadError,
} from '../utils/github.js';
@@ -27,6 +28,7 @@ interface InitOptions {
offline?: boolean;
legacy?: boolean; // Use old ZIP-based install
global?: boolean; // Install to home directory (global mode)
token?: string; // GitHub PAT for higher API rate limits
}
/**
@@ -36,13 +38,14 @@ interface InitOptions {
async function tryGitHubInstall(
targetDir: string,
aiType: AIType,
spinner: ReturnType<typeof ora>
spinner: ReturnType<typeof ora>,
token?: string
): Promise<string[] | null> {
let tempDir: string | null = null;
try {
spinner.text = 'Fetching latest release from GitHub...';
const release = await getLatestRelease();
const release = await getLatestRelease(token);
const assetUrl = getAssetUrl(release);
if (!assetUrl) {
@@ -53,7 +56,7 @@ async function tryGitHubInstall(
tempDir = await createTempDir();
const zipPath = join(tempDir, 'release.zip');
await downloadRelease(assetUrl, zipPath);
await downloadRelease(assetUrl, zipPath, token);
spinner.text = 'Extracting and installing files...';
const { copiedFolders, tempDir: extractedTempDir } = await installFromZip(
@@ -73,7 +76,7 @@ async function tryGitHubInstall(
}
if (error instanceof GitHubRateLimitError) {
spinner.warn('GitHub rate limit reached, using template generation...');
spinner.warn(`GitHub rate limit reached, falling back to bundled assets.\n${getGitHubTokenGuidance()}`);
return null;
}
@@ -164,7 +167,7 @@ export async function initCommand(options: InitOptions): Promise<void> {
}
// Try GitHub download first (unless offline mode)
if (!options.offline) {
const githubResult = await tryGitHubInstall(cwd, aiType, spinner);
const githubResult = await tryGitHubInstall(cwd, aiType, spinner, options.token);
if (githubResult) {
copiedFolders = githubResult;
installMethod = 'github';
+34 -13
View File
@@ -1,12 +1,13 @@
import { rm, stat } from 'node:fs/promises';
import { join } from 'node:path';
import { join, dirname } 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 type { AIType, ConcreteAIType } from '../types/index.js';
import { AI_TYPES, AI_FOLDERS } from '../types/index.js';
import { detectAIType, getAITypeDescription } from '../utils/detect.js';
import { listBundledSubSkills, loadPlatformConfig } from '../utils/template.js';
import { logger } from '../utils/logger.js';
interface UninstallOptions {
@@ -17,19 +18,39 @@ interface UninstallOptions {
/**
* Remove skill directory for a given AI type
*/
async function removeSkillDir(baseDir: string, aiType: Exclude<AIType, 'all'>): Promise<string[]> {
const folders = AI_FOLDERS[aiType];
async function removeSkillDir(baseDir: string, aiType: ConcreteAIType): Promise<string[]> {
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;
// The orchestrator plus the bundled sibling sub-skills installed by init.
const skillNames = ['ui-ux-pro-max', ...(await listBundledSubSkills())];
// Parent directories to clean. Derive the real install location from the
// platform config's skillPath (same source the installer uses), so
// non-`skills/` platforms are handled — copilot installs under
// `.github/prompts/`, kiro under `.kiro/steering/`. Also clean the legacy
// `<folder>/skills/` layout (incl. `.shared/`) so older installs are removed.
const parents = new Set<string>();
try {
const { folderStructure } = await loadPlatformConfig(aiType);
parents.add(join(folderStructure.root, dirname(folderStructure.skillPath)));
} catch {
// No platform config — fall back to the legacy folders below.
}
for (const folder of AI_FOLDERS[aiType]) {
parents.add(join(folder, 'skills'));
}
for (const parent of parents) {
for (const name of skillNames) {
const skillDir = join(baseDir, parent, name);
try {
await stat(skillDir);
await rm(skillDir, { recursive: true, force: true });
removed.push(`${parent.replaceAll('\\', '/')}/${name}`);
} catch (err: unknown) {
// Skip non-existent dirs; re-throw permission or other errors
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;
}
}
}
+59 -2
View File
@@ -1,3 +1,7 @@
import { execFileSync } from 'node:child_process';
import { readFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import chalk from 'chalk';
import ora from 'ora';
import { getLatestRelease } from '../utils/github.js';
@@ -5,8 +9,21 @@ import { logger } from '../utils/logger.js';
import { initCommand } from './init.js';
import type { AIType } from '../types/index.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
interface UpdateOptions {
ai?: AIType;
token?: string;
}
async function getPackageVersion(): Promise<string> {
const packagePath = join(__dirname, '..', 'package.json');
const pkg = JSON.parse(await readFile(packagePath, 'utf-8')) as { version: string };
return pkg.version;
}
function normalizeTagVersion(tagName: string): string {
return tagName.replace(/^v/i, '');
}
export async function updateCommand(options: UpdateOptions): Promise<void> {
@@ -15,16 +32,56 @@ export async function updateCommand(options: UpdateOptions): Promise<void> {
const spinner = ora('Checking for updates...').start();
try {
const release = await getLatestRelease();
const release = await getLatestRelease(options.token);
const currentVersion = await getPackageVersion();
const latestVersion = normalizeTagVersion(release.tag_name);
spinner.succeed(`Latest version: ${chalk.cyan(release.tag_name)}`);
if (currentVersion !== latestVersion) {
console.log();
// Only auto-run with a well-formed semver, so nothing unexpected can
// reach the shell that npm.cmd requires on Windows.
if (!/^\d+\.\d+\.\d+([.-][0-9A-Za-z.-]+)?$/.test(latestVersion)) {
logger.warn(`Installed CLI is ${chalk.cyan(currentVersion)}; latest release is ${chalk.cyan(release.tag_name)}.`);
logger.info(`Update the CLI package: ${chalk.cyan(`npm install -g uipro-cli@${latestVersion}`)}`);
logger.info('Then rerun: uipro init --ai <platform> --force');
return;
}
logger.info(`Updating CLI from ${chalk.cyan(currentVersion)} to ${chalk.cyan(latestVersion)}...`);
console.log();
const isWindows = process.platform === 'win32';
try {
// execFileSync with an explicit args array — no shell string to expand.
// On Windows npm is npm.cmd, which Node only spawns via a shell.
execFileSync(
isWindows ? 'npm.cmd' : 'npm',
['install', '-g', `uipro-cli@${latestVersion}`],
{ stdio: 'inherit', shell: isWindows }
);
} catch {
console.log();
logger.error('Automatic update failed (you may need elevated/admin permissions).');
logger.info(`Update manually: ${chalk.cyan(`npm install -g uipro-cli@${latestVersion}`)}`);
process.exit(1);
}
console.log();
logger.success(`Updated to ${chalk.cyan(latestVersion)}.`);
logger.info(`Now rerun ${chalk.cyan('uipro init --ai <platform> --force')} to refresh your skill files.`);
return;
}
console.log();
logger.info('Running update (same as init with latest version)...');
logger.info('Refreshing installed skill files from this CLI package...');
console.log();
await initCommand({
ai: options.ai,
force: true,
token: options.token,
});
} catch (error) {
spinner.fail('Update check failed');
+7 -3
View File
@@ -3,11 +3,15 @@ import ora from 'ora';
import { fetchReleases } from '../utils/github.js';
import { logger } from '../utils/logger.js';
export async function versionsCommand(): Promise<void> {
interface VersionsOptions {
token?: string;
}
export async function versionsCommand(options: VersionsOptions = {}): Promise<void> {
const spinner = ora('Fetching available versions...').start();
try {
const releases = await fetchReleases();
const releases = await fetchReleases(options.token);
if (releases.length === 0) {
spinner.warn('No releases found');
@@ -31,7 +35,7 @@ export async function versionsCommand(): Promise<void> {
});
console.log();
logger.dim('Use: uipro init --version <tag> to install a specific version');
logger.dim('Update the CLI package first, then run: uipro init --ai <platform>');
} catch (error) {
spinner.fail('Failed to fetch versions');
if (error instanceof Error) {
+6 -1
View File
@@ -27,8 +27,9 @@ program
.description('Install UI/UX Pro Max skill to current project')
.option('-a, --ai <type>', `AI assistant type (${AI_TYPES.join(', ')})`)
.option('-f, --force', 'Overwrite existing files')
.option('-o, --offline', 'Skip GitHub download, use bundled assets only')
.option('-o, --offline', 'Compatibility flag; template installs use bundled assets')
.option('-g, --global', 'Install globally to home directory (~/) instead of current project')
.option('-t, --token <token>', 'GitHub Personal Access Token for higher API rate limits')
.action(async (options) => {
if (options.ai && !AI_TYPES.includes(options.ai)) {
console.error(`Invalid AI type: ${options.ai}`);
@@ -40,18 +41,21 @@ program
force: options.force,
offline: options.offline,
global: options.global,
token: options.token,
});
});
program
.command('versions')
.description('List available versions')
.option('-t, --token <token>', 'GitHub Personal Access Token for higher API rate limits')
.action(versionsCommand);
program
.command('update')
.description('Update UI/UX Pro Max to latest version')
.option('-a, --ai <type>', `AI assistant type (${AI_TYPES.join(', ')})`)
.option('-t, --token <token>', 'GitHub Personal Access Token for higher API rate limits')
.action(async (options) => {
if (options.ai && !AI_TYPES.includes(options.ai)) {
console.error(`Invalid AI type: ${options.ai}`);
@@ -60,6 +64,7 @@ program
}
await updateCommand({
ai: options.ai as AIType | undefined,
token: options.token,
});
});
+2 -1
View File
@@ -1,4 +1,5 @@
export type AIType = 'claude' | 'cursor' | 'windsurf' | 'antigravity' | 'copilot' | 'kiro' | 'roocode' | 'codex' | 'qoder' | 'gemini' | 'trae' | 'opencode' | 'continue' | 'codebuddy' | 'droid' | 'kilocode' | 'warp' | 'augment' | 'all';
export type ConcreteAIType = Exclude<AIType, 'all'>;
export type InstallType = 'full' | 'reference';
@@ -46,7 +47,7 @@ export const AI_TYPES: AIType[] = ['claude', 'cursor', 'windsurf', 'antigravity'
// Legacy folder mapping for backward compatibility with ZIP-based installs.
// Note: .shared is included for platforms that used ZIP installs. Post-ZIP platforms
// (kilocode, warp, augment) include .shared as a no-op for consistent uninstall behavior.
export const AI_FOLDERS: Record<Exclude<AIType, 'all'>, string[]> = {
export const AI_FOLDERS: Record<ConcreteAIType, string[]> = {
claude: ['.claude'],
cursor: ['.cursor', '.shared'],
windsurf: ['.windsurf', '.shared'],
+3 -3
View File
@@ -1,14 +1,14 @@
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import type { AIType } from '../types/index.js';
import type { AIType, ConcreteAIType } from '../types/index.js';
interface DetectionResult {
detected: AIType[];
detected: ConcreteAIType[];
suggested: AIType | null;
}
export function detectAIType(cwd: string = process.cwd()): DetectionResult {
const detected: AIType[] = [];
const detected: ConcreteAIType[] = [];
if (existsSync(join(cwd, '.claude'))) {
detected.push('claude');
+27 -5
View File
@@ -19,25 +19,45 @@ export class GitHubDownloadError extends Error {
}
}
export function getGitHubTokenGuidance(): string {
return (
'To increase your GitHub API rate limit, set the UI_PRO_MAX_GITHUB_TOKEN environment variable\n' +
'to a GitHub Personal Access Token (no scopes needed for public repos).\n' +
'Create one at: https://github.com/settings/tokens\n' +
'Example: UI_PRO_MAX_GITHUB_TOKEN=ghp_xxx uipro init\n' +
'Or pass it directly: uipro init --token ghp_xxx'
);
}
function checkRateLimit(response: Response): void {
const remaining = response.headers.get('x-ratelimit-remaining');
if (response.status === 403 && remaining === '0') {
const resetTime = response.headers.get('x-ratelimit-reset');
const resetDate = resetTime ? new Date(parseInt(resetTime) * 1000).toLocaleTimeString() : 'unknown';
throw new GitHubRateLimitError(`GitHub API rate limit exceeded. Resets at ${resetDate}`);
throw new GitHubRateLimitError(
`GitHub API rate limit exceeded. Resets at ${resetDate}.\n${getGitHubTokenGuidance()}`
);
}
if (response.status === 429) {
throw new GitHubRateLimitError('GitHub API rate limit exceeded (429 Too Many Requests)');
throw new GitHubRateLimitError(
`GitHub API rate limit exceeded (429 Too Many Requests).\n${getGitHubTokenGuidance()}`
);
}
}
export async function fetchReleases(): Promise<Release[]> {
function getAuthHeaders(token?: string): Record<string, string> {
const resolved = (token || process.env['UI_PRO_MAX_GITHUB_TOKEN'] || process.env['GITHUB_TOKEN'])?.trim();
return resolved ? { 'Authorization': `Bearer ${resolved}` } : {};
}
export async function fetchReleases(token?: string): Promise<Release[]> {
const url = `${API_BASE}/repos/${REPO_OWNER}/${REPO_NAME}/releases`;
const response = await fetch(url, {
headers: {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'uipro-cli',
...getAuthHeaders(token),
},
});
@@ -50,13 +70,14 @@ export async function fetchReleases(): Promise<Release[]> {
return response.json();
}
export async function getLatestRelease(): Promise<Release> {
export async function getLatestRelease(token?: string): Promise<Release> {
const url = `${API_BASE}/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
const response = await fetch(url, {
headers: {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'uipro-cli',
...getAuthHeaders(token),
},
});
@@ -69,11 +90,12 @@ export async function getLatestRelease(): Promise<Release> {
return response.json();
}
export async function downloadRelease(url: string, dest: string): Promise<void> {
export async function downloadRelease(url: string, dest: string, token?: string): Promise<void> {
const response = await fetch(url, {
headers: {
'User-Agent': 'uipro-cli',
'Accept': 'application/octet-stream',
...getAuthHeaders(token),
},
});
+58 -1
View File
@@ -1,4 +1,4 @@
import { readFile, mkdir, writeFile, cp, access, readdir } from 'node:fs/promises';
import { readFile, mkdir, writeFile, cp, access, readdir, lstat, rm } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { homedir } from 'node:os';
import { fileURLToPath } from 'node:url';
@@ -156,6 +156,23 @@ export async function renderSkillFile(config: PlatformConfig, isGlobal = false):
return frontmatter + content;
}
/**
* Replace a pre-existing non-directory at `path` so a real directory can be
* created there. Older CLI installs (and Windows checkouts of the repo's
* symlinked data/scripts) can leave plain "pointer" files at these paths;
* mkdir then throws EEXIST and the install silently leaves stale files.
*/
async function ensureCleanDir(path: string): Promise<void> {
try {
const stat = await lstat(path);
if (!stat.isDirectory()) {
await rm(path, { recursive: true, force: true });
}
} catch {
// Nothing exists at the path yet — mkdir will create it.
}
}
/**
* Copy data and scripts to target directory
*/
@@ -168,17 +185,46 @@ async function copyDataAndScripts(targetSkillDir: string): Promise<void> {
// Copy data
if (await exists(dataSource)) {
await ensureCleanDir(dataTarget);
await mkdir(dataTarget, { recursive: true });
await cp(dataSource, dataTarget, { recursive: true });
}
// Copy scripts
if (await exists(scriptsSource)) {
await ensureCleanDir(scriptsTarget);
await mkdir(scriptsTarget, { recursive: true });
await cp(scriptsSource, scriptsTarget, { recursive: true });
}
}
/**
* List the static sub-skills bundled under assets/skills/ (everything except
* the template-rendered orchestrator). Empty if the package predates bundling.
*/
export async function listBundledSubSkills(): Promise<string[]> {
const skillsSource = join(ASSETS_DIR, 'skills');
if (!(await exists(skillsSource))) return [];
const entries = await readdir(skillsSource, { withFileTypes: true });
return entries.filter(e => e.isDirectory()).map(e => e.name).sort();
}
/**
* Install the bundled sub-skills as siblings of the orchestrator skill, so a
* single `uipro init` delivers all 7 skills instead of only ui-ux-pro-max.
*/
async function copySubSkills(skillsParentDir: string, force: boolean): Promise<void> {
const skillsSource = join(ASSETS_DIR, 'skills');
if (!(await exists(skillsSource))) return;
for (const name of await listBundledSubSkills()) {
const target = join(skillsParentDir, name);
if (await exists(target) && !force) continue;
await mkdir(target, { recursive: true });
await cp(join(skillsSource, name), target, { recursive: true });
}
}
/**
* Generate platform files for a specific AI type
* All platforms use self-contained installation with data and scripts
@@ -222,6 +268,17 @@ export async function generatePlatformFiles(
// Copy data and scripts into the skill directory (self-contained)
await copyDataAndScripts(skillDir);
// Install the sibling sub-skills (banner-design, brand, design, ...) next to
// the orchestrator so all 7 skills are delivered. The skills parent is the
// orchestrator's parent dir (skills/ for most platforms, prompts/ for
// copilot, steering/ for kiro) — derived, not hardcoded.
const skillsParentDir = join(
effectiveDir,
config.folderStructure.root,
dirname(config.folderStructure.skillPath)
);
await copySubSkills(skillsParentDir, force);
return createdFolders;
}