feat: add workflow management commands and utilities

- Implemented workflow installation, listing, and syncing commands in `workflow.ts`.
- Created utility functions for project root detection and package version retrieval in `project-root.ts`.
- Added update checker functionality to notify users of new package versions in `update-checker.ts`.
- Developed unit tests for project root utilities and update checker to ensure functionality and version comparison accuracy.
This commit is contained in:
catlog22
2026-01-21 12:35:33 +08:00
parent ffe9898fd3
commit 9d6bc92837
13 changed files with 1832 additions and 5 deletions

View File

@@ -6,21 +6,100 @@
// State
let versionCheckData = null;
let versionBannerDismissed = false;
let autoUpdateEnabled = true; // Default to enabled
/**
* Initialize version check on page load
*/
async function initVersionCheck() {
// Load auto-update setting from localStorage
const stored = localStorage.getItem('ccw.autoUpdate');
autoUpdateEnabled = stored !== null ? stored === 'true' : true;
// Update toggle checkbox state
updateAutoUpdateToggleUI();
// Check version after a short delay to not block initial render
setTimeout(async () => {
await checkForUpdates();
if (autoUpdateEnabled) {
await checkForUpdates();
}
}, 2000);
}
/**
* Toggle auto-update setting (called from checkbox change event)
*/
function toggleAutoUpdate() {
const checkbox = document.getElementById('autoUpdateToggle');
if (!checkbox) return;
autoUpdateEnabled = checkbox.checked;
localStorage.setItem('ccw.autoUpdate', autoUpdateEnabled.toString());
// Show notification
if (autoUpdateEnabled) {
addGlobalNotification('success', 'Auto-update enabled', 'Version check will run automatically', 'version-check');
// Run check immediately if just enabled
checkForUpdates();
} else {
addGlobalNotification('info', 'Auto-update disabled', 'Version check is turned off', 'version-check');
// Dismiss banner if visible
dismissUpdateBanner();
}
}
/**
* Check for updates immediately (called from "Check Now" button)
*/
async function checkForUpdatesNow() {
const btn = document.getElementById('checkUpdateNow');
if (btn) {
// Add loading animation
btn.classList.add('animate-spin');
btn.disabled = true;
}
// Force check regardless of toggle state
const originalState = autoUpdateEnabled;
autoUpdateEnabled = true;
try {
await checkForUpdates();
addGlobalNotification('success', 'Update check complete', 'Checked for latest version', 'version-check');
} catch (err) {
addGlobalNotification('error', 'Update check failed', err.message, 'version-check');
} finally {
// Restore original state
autoUpdateEnabled = originalState;
if (btn) {
btn.classList.remove('animate-spin');
btn.disabled = false;
}
}
}
/**
* Update auto-update toggle checkbox state
*/
function updateAutoUpdateToggleUI() {
const checkbox = document.getElementById('autoUpdateToggle');
if (!checkbox) return;
checkbox.checked = autoUpdateEnabled;
}
/**
* Check for package updates
*/
async function checkForUpdates() {
// Respect the toggle setting
if (!autoUpdateEnabled) {
console.log('Version check skipped: auto-update is disabled');
return;
}
try {
const res = await fetch('/api/version-check');
if (!res.ok) return;
@@ -165,3 +244,10 @@ npm install -g ' + versionCheckData.packageName + '@latest\n\
function getVersionInfo() {
return versionCheckData;
}
/**
* Check if auto-update is enabled
*/
function isAutoUpdateEnabled() {
return autoUpdateEnabled;
}

View File

@@ -42,6 +42,8 @@ const i18n = {
'header.recentProjects': 'Recent Projects',
'header.browse': 'Browse...',
'header.refreshWorkspace': 'Refresh workspace',
'header.checkUpdateNow': 'Check for updates now',
'header.autoUpdate': 'Auto-update check',
'header.toggleTheme': 'Toggle theme',
'header.language': 'Language',
'header.cliStream': 'CLI Stream Viewer',
@@ -2391,6 +2393,8 @@ const i18n = {
'header.recentProjects': '最近项目',
'header.browse': '浏览...',
'header.refreshWorkspace': '刷新工作区',
'header.checkUpdateNow': '立即检查更新',
'header.autoUpdate': '自动更新检查',
'header.toggleTheme': '切换主题',
'header.language': '语言',
'header.cliStream': 'CLI 流式输出',

View File

@@ -234,6 +234,52 @@
animation: spin 1s linear infinite;
}
/* Auto-Update Toggle Switch */
.auto-update-switch {
position: relative;
display: inline-block;
width: 32px;
height: 18px;
cursor: pointer;
}
.auto-update-switch input {
opacity: 0;
width: 0;
height: 0;
}
.auto-update-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: hsl(var(--muted));
transition: 0.3s;
border-radius: 18px;
}
.auto-update-slider:before {
position: absolute;
content: "";
height: 12px;
width: 12px;
left: 3px;
bottom: 3px;
background-color: hsl(var(--muted-foreground));
transition: 0.3s;
border-radius: 50%;
}
.auto-update-switch input:checked + .auto-update-slider {
background-color: hsl(var(--success));
}
.auto-update-switch input:checked + .auto-update-slider:before {
transform: translateX(14px);
background-color: white;
}
.auto-update-switch:hover .auto-update-slider {
opacity: 0.9;
}
/* Injected from dashboard-css/*.css modules */
{{CSS_CONTENT}}
</style>
@@ -296,6 +342,22 @@
<path d="M16 21h5v-5"/>
</svg>
</button>
<!-- Auto-Update Controls -->
<div class="flex items-center gap-2 border-l border-border pl-2">
<!-- Check Now Button -->
<button class="p-1.5 text-muted-foreground hover:text-foreground hover:bg-hover rounded" id="checkUpdateNow" data-i18n-title="header.checkUpdateNow" title="Check for updates now" onclick="checkForUpdatesNow()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>
</button>
<!-- Auto-Update Toggle Switch -->
<label class="auto-update-switch" data-i18n-title="header.autoUpdate" title="Auto-update check">
<input type="checkbox" id="autoUpdateToggle" onchange="toggleAutoUpdate()" checked>
<span class="auto-update-slider"></span>
</label>
</div>
</div>
<!-- Language Toggle -->