CI / Build & test (Node 22) (push) Successful in 10m17s
CI / Build & test (Node 24) (push) Successful in 10m13s
CI / No em/en dashes (push) Successful in 4s
Deploy site (production) / build-and-deploy (push) Successful in 24s
Release / Publish to Gitea npm registry (push) Successful in 10m17s
VSCode Release / Package VSIX (push) Successful in 9m39s
Desktop Release / Build Linux (AppImage + deb) (push) Successful in 15m41s
CI / Pack & boot smoke (Node 22) (push) Successful in 10m3s
« Démarrer le projet » : un repo définit une fois ses commandes de démarrage (serveur de dev, API, base de données), un clic ouvre un terminal PTY par commande dans le dock IDE. Serveur (additif, PROTOCOL_VERSION inchangé) : - LaunchCommand[] persistées sur repos.launch_commands (migration 13) ; champ additif SessionSummary.launchRunId. - POST /repos/:id/launch : résolution du worktree côté serveur, cwd de commande borné (anti-traversal), commandIds outrepasse enabled. - GET /repos/:id/launch/detect : détection package.json / Procfile / docker-compose. - Shell de login interactif ($SHELL -l -i, charge le PATH nvm/asdf) + auto-type de la commande ; le shell survit à la commande (échec visible). Web : LaunchProjectModal + actions (ProjectTreeNode, SessionsPanel, CommandPalette), stores sessions/worktrees, i18n EN/FR. Alignement du reste du projet : - Extension VS Code 0.4.0 : commande Start Project (repo/worktree), Stop Launch, badge « launch » dans l'arbre, méthode REST startLaunch. - Site vitrine : 16e feature card (Rocket) + section showcase « Start the project » (mockup fidèle au modal), i18n EN/FR. - Documentation : README (EN + FR), help-content (EN + FR), CHANGELOGs server + vscode. Vérifié : 430 tests, typecheck, build (web + site + vscode), acceptance-p13 ALL GREEN, VSIX packagé, garde anti-tirets, vérif visuelle du site (thèmes clair et sombre).
121 lines
5.5 KiB
TypeScript
121 lines
5.5 KiB
TypeScript
// Helpers de construction des TreeItem (icônes d'état, labels, contextValue) partagés par les deux
|
|
// arbres (Repositories & Groups). Les `contextValue` pilotent les menus contextuels (cf. package.json) ;
|
|
// les regex y matchent des sous-chaînes → format stable `arboretum:session:<live|dead>[:discovered][:waiting]`.
|
|
import * as vscode from 'vscode';
|
|
import type { GroupSummary, RepoSummary, SessionSummary, WorktreeSummary } from '@arboretum/shared';
|
|
|
|
export function repoTreeItem(repo: RepoSummary): vscode.TreeItem {
|
|
const item = new vscode.TreeItem(repo.label, vscode.TreeItemCollapsibleState.Expanded);
|
|
item.id = `repo:${repo.id}`;
|
|
item.contextValue = 'arboretum:repo';
|
|
item.iconPath = new vscode.ThemeIcon('repo');
|
|
item.description = repo.defaultBranch ?? '';
|
|
item.tooltip = repo.path;
|
|
return item;
|
|
}
|
|
|
|
export function worktreeTreeItem(worktree: WorktreeSummary): vscode.TreeItem {
|
|
const label = worktree.branch ?? (worktree.detached ? '(detached)' : worktree.head.slice(0, 8));
|
|
const item = new vscode.TreeItem(label, vscode.TreeItemCollapsibleState.Expanded);
|
|
item.id = `wt:${worktree.path}`;
|
|
item.contextValue = 'arboretum:worktree';
|
|
item.iconPath = new vscode.ThemeIcon(worktree.isMain ? 'star-full' : 'git-branch');
|
|
item.description = worktreeStatus(worktree);
|
|
item.tooltip = worktreeTooltip(worktree);
|
|
item.resourceUri = vscode.Uri.file(worktree.path);
|
|
return item;
|
|
}
|
|
|
|
function worktreeStatus(w: WorktreeSummary): string {
|
|
const parts: string[] = [];
|
|
if (w.isMain) parts.push('main');
|
|
if (w.git.ahead > 0) parts.push(`↑${w.git.ahead}`);
|
|
if (w.git.behind > 0) parts.push(`↓${w.git.behind}`);
|
|
if (w.git.dirtyCount > 0) parts.push(`●${w.git.dirtyCount}`);
|
|
// Compteurs fins (P7, additifs) : staged/conflits surfacés directement dans l'arbre.
|
|
if (w.git.stagedCount && w.git.stagedCount > 0) parts.push(`+${w.git.stagedCount}`);
|
|
if (w.git.conflictCount && w.git.conflictCount > 0) parts.push(`!${w.git.conflictCount}`);
|
|
return parts.join(' ');
|
|
}
|
|
|
|
/** Tooltip détaillé : chemin + statut git fin (P7) + dernier commit. */
|
|
function worktreeTooltip(w: WorktreeSummary): vscode.MarkdownString {
|
|
const md = new vscode.MarkdownString();
|
|
md.appendMarkdown(`\`${w.path}\`\n\n`);
|
|
const g = w.git;
|
|
const status: string[] = [];
|
|
if (g.stagedCount && g.stagedCount > 0) status.push(`${g.stagedCount} staged`);
|
|
if (g.unstagedCount && g.unstagedCount > 0) status.push(`${g.unstagedCount} unstaged`);
|
|
if (g.conflictCount && g.conflictCount > 0) status.push(`${g.conflictCount} conflicts`);
|
|
if (status.length === 0 && g.dirtyCount > 0) status.push(`${g.dirtyCount} changed`);
|
|
md.appendMarkdown(status.length > 0 ? status.join(' · ') : 'clean');
|
|
if (g.ahead > 0 || g.behind > 0) md.appendMarkdown(` (↑${g.ahead} ↓${g.behind})`);
|
|
if (g.lastCommitSubject) md.appendMarkdown(`\n\n_${g.lastCommitSubject}_`);
|
|
return md;
|
|
}
|
|
|
|
export function sessionContextValue(s: SessionSummary): string {
|
|
let v = `arboretum:session:${s.live ? 'live' : 'dead'}`;
|
|
if (s.source === 'discovered') v += ':discovered';
|
|
if (s.live && s.activity === 'waiting') v += ':waiting';
|
|
// Session issue de « Démarrer le projet » : suffixe additif (les regex de menu existantes,
|
|
// ancrées sur le préfixe, restent valides) qui gate l'action « Stop launch ».
|
|
if (s.launchRunId) v += ':launch';
|
|
return v;
|
|
}
|
|
|
|
export function sessionTreeItem(s: SessionSummary): vscode.TreeItem {
|
|
const label = s.title?.trim() || s.command;
|
|
const item = new vscode.TreeItem(label, vscode.TreeItemCollapsibleState.None);
|
|
item.id = `session:${s.id}`;
|
|
item.contextValue = sessionContextValue(s);
|
|
item.iconPath = sessionIcon(s);
|
|
item.description = sessionDescription(s);
|
|
item.tooltip = sessionTooltip(s);
|
|
return item;
|
|
}
|
|
|
|
function sessionIcon(s: SessionSummary): vscode.ThemeIcon {
|
|
if (!s.live) return new vscode.ThemeIcon('circle-slash');
|
|
switch (s.activity) {
|
|
case 'waiting':
|
|
return new vscode.ThemeIcon('bell-dot', new vscode.ThemeColor('charts.yellow'));
|
|
case 'busy':
|
|
return new vscode.ThemeIcon('loading~spin');
|
|
case 'idle':
|
|
return new vscode.ThemeIcon('pass', new vscode.ThemeColor('charts.green'));
|
|
default:
|
|
return new vscode.ThemeIcon('terminal');
|
|
}
|
|
}
|
|
|
|
function sessionDescription(s: SessionSummary): string {
|
|
const parts: string[] = [];
|
|
// Affiche 'available' pour idle (cohérent avec le libellé web) ; l'enum reste 'idle' côté protocole.
|
|
if (s.live && s.activity) parts.push(s.activity === 'idle' ? 'available' : s.activity);
|
|
else if (!s.live) parts.push('exited');
|
|
if (s.archived) parts.push('archived');
|
|
if (s.source === 'discovered') parts.push('external');
|
|
if (s.groupId) parts.push('group');
|
|
if (s.launchRunId) parts.push('launch');
|
|
if (s.waitingFor) parts.push(`· ${s.waitingFor}`);
|
|
return parts.join(' ');
|
|
}
|
|
|
|
function sessionTooltip(s: SessionSummary): string {
|
|
const lines = [`${s.command} · ${s.cwd}`, `status: ${s.status}${s.live ? ' (live)' : ''}`];
|
|
if (s.activity) lines.push(`activity: ${s.activity}`);
|
|
if (s.addedDirs && s.addedDirs.length > 0) lines.push(`added dirs: ${s.addedDirs.length}`);
|
|
return lines.join('\n');
|
|
}
|
|
|
|
export function groupTreeItem(group: GroupSummary): vscode.TreeItem {
|
|
const item = new vscode.TreeItem(group.label, vscode.TreeItemCollapsibleState.Collapsed);
|
|
item.id = `group:${group.id}`;
|
|
item.contextValue = 'arboretum:group';
|
|
item.iconPath = new vscode.ThemeIcon('folder-library');
|
|
item.description = `${group.repoIds.length} repos`;
|
|
if (group.description) item.tooltip = group.description;
|
|
return item;
|
|
}
|