feat(dees-actionbar): add action bar component and improve workspace package update handling

This commit is contained in:
2026-01-01 10:20:22 +00:00
parent dce557d85b
commit 22156d71dc
8 changed files with 626 additions and 16 deletions

View File

@@ -12,7 +12,7 @@ import { themeDefaultStyles } from '../../00theme.js';
import type { IExecutionEnvironment } from '../../00group-runtime/index.js';
import '../../dees-icon/dees-icon.js';
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
import type { IRunProcessEventDetail } from '../dees-workspace-terminal/interfaces.js';
import type { IRunProcessEventDetail, ITerminalProcessCompleteEventDetail } from '../dees-workspace-terminal/interfaces.js';
declare global {
interface HTMLElementTagNameMap {
@@ -48,6 +48,19 @@ export class DeesWorkspaceBottombar extends DeesElement {
@state()
accessor isCheckingPackages: boolean = false;
// Track if we have a pending package update that should trigger refresh
private pendingPackageUpdate: boolean = false;
// Bound handler for process-complete events
private handleProcessComplete = (e: CustomEvent<ITerminalProcessCompleteEventDetail>) => {
// If we have a pending package update and a process completed, refresh
if (this.pendingPackageUpdate) {
this.pendingPackageUpdate = false;
// Small delay to let pnpm-lock.yaml update
setTimeout(() => this.checkPackages(), 500);
}
};
public static styles = [
themeDefaultStyles,
cssManager.defaultStyles,
@@ -167,6 +180,17 @@ export class DeesWorkspaceBottombar extends DeesElement {
`;
}
public async connectedCallback() {
await super.connectedCallback();
// Listen for process-complete events to refresh after package updates
window.addEventListener('process-complete', this.handleProcessComplete as EventListener);
}
public async disconnectedCallback() {
await super.disconnectedCallback();
window.removeEventListener('process-complete', this.handleProcessComplete as EventListener);
}
async firstUpdated() {
await this.loadScripts();
await this.checkPackages();
@@ -256,19 +280,46 @@ export class DeesWorkspaceBottombar extends DeesElement {
this.packageStatus = 'checking';
this.isCheckingPackages = true;
// Run pnpm outdated --json
// Run pnpm outdated --json with timeout
const process = await this.executionEnvironment.spawn('pnpm', ['outdated', '--json']);
let output = '';
await process.output.pipeTo(
new WritableStream({
write: (chunk) => {
output += chunk;
},
})
);
const exitCode = await process.exit;
// Collect output asynchronously - don't await, stream may not close if no output
const outputReader = process.output.getReader();
const readOutput = async () => {
try {
while (true) {
const { done, value } = await outputReader.read();
if (done) break;
output += value;
}
} catch {
// Ignore stream errors
}
};
// Start reading but don't await - we'll use whatever we have when process exits
readOutput();
// Wait for process exit with timeout (10 seconds)
const exitCode = await Promise.race([
process.exit,
new Promise<number>((resolve) => setTimeout(() => resolve(-1), 10000)),
]);
// Cancel reader when done
try {
await outputReader.cancel();
} catch {
// Ignore cancel errors
}
// Handle timeout
if (exitCode === -1) {
console.warn('Package check timed out');
this.packageStatus = 'error';
return;
}
// pnpm outdated returns exit code 1 if there are outdated packages
if (exitCode === 0) {
@@ -318,15 +369,15 @@ export class DeesWorkspaceBottombar extends DeesElement {
private async handlePackageClick(e: MouseEvent): Promise<void> {
e.stopPropagation();
if (this.isCheckingPackages) return;
const menuItems: Parameters<typeof DeesContextmenu.openContextMenuWithOptions>[1] = [];
// Refresh option - show output in terminal
menuItems.push({
name: 'Check for updates',
name: this.isCheckingPackages ? 'Checking...' : 'Check for updates',
iconName: 'lucide:refreshCw',
action: async () => {
if (this.isCheckingPackages) return;
// Create terminal tab to show pnpm outdated output
const detail: IRunProcessEventDetail = {
type: 'package-update',
@@ -387,12 +438,15 @@ export class DeesWorkspaceBottombar extends DeesElement {
private async updatePackage(packageName: string): Promise<void> {
if (!this.executionEnvironment) return;
// Mark that we have a pending update - will trigger refresh when complete
this.pendingPackageUpdate = true;
// Emit run-process event for the workspace to create a terminal tab
const detail: IRunProcessEventDetail = {
type: 'package-update',
label: `update ${packageName}`,
command: 'pnpm',
args: ['update', packageName],
args: ['update', '--latest', packageName],
metadata: { packageName },
};
@@ -406,12 +460,15 @@ export class DeesWorkspaceBottombar extends DeesElement {
private async updateAllPackages(): Promise<void> {
if (!this.executionEnvironment) return;
// Mark that we have a pending update - will trigger refresh when complete
this.pendingPackageUpdate = true;
// Emit run-process event for the workspace to create a terminal tab
const detail: IRunProcessEventDetail = {
type: 'package-update',
label: 'update all',
command: 'pnpm',
args: ['update'],
args: ['update', '--latest'],
};
this.dispatchEvent(new CustomEvent('run-process', {

View File

@@ -65,6 +65,7 @@ export class DeesWorkspace extends DeesElement {
'@push.rocks/smartpromise': '^4.2.3',
},
devDependencies: {
'@types/node': '^22.0.0',
typescript: '^5.0.0',
},
},