release: git-arboretum 3.3.0 (« Démarrer le projet » : lancement multi-terminaux), vscode 0.4.0, desktop 0.1.3
All checks were successful
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
All checks were successful
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).
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## 0.4.0
|
||||
|
||||
Reflects the daemon's **"Start the project"** milestone in supervision (no editor duplicated here).
|
||||
|
||||
- **Start Project.** A new command on repos and worktrees launches the project's configured start
|
||||
commands, one native terminal per command. The commands themselves are defined in the web IDE; when
|
||||
none are set yet, the action offers to open it.
|
||||
- Sessions started by a launch are badged `launch` in the tree, and a **Stop Launch** action stops the
|
||||
whole set (all terminals sharing the launch run) at once.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
Aligns with the daemon's **multi-project IDE** milestone: the web UI is now a single IDE that holds all
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "git-arboretum",
|
||||
"displayName": "Arboretum",
|
||||
"description": "Pilot your git worktrees and Claude Code sessions from VS Code: native terminals, live tree, waiting alerts.",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"private": true,
|
||||
"publisher": "johanleroy",
|
||||
"license": "MIT",
|
||||
@@ -203,6 +203,18 @@
|
||||
"category": "Arboretum",
|
||||
"icon": "$(play)"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.startProject",
|
||||
"title": "Start Project",
|
||||
"category": "Arboretum",
|
||||
"icon": "$(rocket)"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.stopLaunch",
|
||||
"title": "Stop Launch",
|
||||
"category": "Arboretum",
|
||||
"icon": "$(stop-circle)"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.startSessionHere",
|
||||
"title": "Start Session in Current Folder",
|
||||
@@ -253,6 +265,11 @@
|
||||
"when": "viewItem == arboretum:repo",
|
||||
"group": "1_session"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.startProject",
|
||||
"when": "viewItem == arboretum:repo",
|
||||
"group": "1_session"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.openWorktreeIde",
|
||||
"when": "viewItem == arboretum:worktree",
|
||||
@@ -293,6 +310,11 @@
|
||||
"when": "viewItem == arboretum:worktree",
|
||||
"group": "3_session"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.startProject",
|
||||
"when": "viewItem == arboretum:worktree",
|
||||
"group": "3_session"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.attachSession",
|
||||
"when": "viewItem =~ /arboretum:session:live/",
|
||||
@@ -313,6 +335,11 @@
|
||||
"when": "viewItem =~ /arboretum:session:live/",
|
||||
"group": "9_danger"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.stopLaunch",
|
||||
"when": "viewItem =~ /arboretum:session:live.*:launch/",
|
||||
"group": "9_danger"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.resumeSession",
|
||||
"when": "viewItem =~ /arboretum:session:dead/",
|
||||
@@ -407,6 +434,14 @@
|
||||
"command": "arboretum.startGroupSession",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.startProject",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.stopLaunch",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "arboretum.revealWorktree",
|
||||
"when": "false"
|
||||
|
||||
@@ -20,6 +20,8 @@ import type {
|
||||
ReposListResponse,
|
||||
SessionResponse,
|
||||
SessionsListResponse,
|
||||
StartLaunchRequest,
|
||||
StartLaunchResponse,
|
||||
StartRepoSessionRequest,
|
||||
WorktreeResponse,
|
||||
WorktreesListResponse,
|
||||
@@ -111,6 +113,11 @@ export class RestClient {
|
||||
return this.request<SessionResponse>('POST', `/api/v1/repos/${encodeURIComponent(repoId)}/session`, body);
|
||||
}
|
||||
|
||||
/** « Démarrer le projet » : lance un terminal par commande de démarrage activée du repo. */
|
||||
startLaunch(repoId: string, body: StartLaunchRequest): Promise<StartLaunchResponse> {
|
||||
return this.request<StartLaunchResponse>('POST', `/api/v1/repos/${encodeURIComponent(repoId)}/launch`, body);
|
||||
}
|
||||
|
||||
// ---- sessions ----
|
||||
createSession(body: CreateSessionRequest): Promise<SessionResponse> {
|
||||
return this.request<SessionResponse>('POST', '/api/v1/sessions', body);
|
||||
|
||||
@@ -236,6 +236,21 @@ export function registerCommands(context: vscode.ExtensionContext, deps: Command
|
||||
if (group) void startGroupSessionFlow(deps, group);
|
||||
});
|
||||
|
||||
// ---- « Démarrer le projet » (lancement multi-terminaux) ----
|
||||
cmd('arboretum.startProject', (node) => {
|
||||
const wt = asWorktree(node as AnyNode);
|
||||
if (wt) {
|
||||
void startProjectFlow(deps, wt.repoId, { worktreePath: wt.worktree.path });
|
||||
return;
|
||||
}
|
||||
const repo = asRepo(node as AnyNode);
|
||||
if (repo) void startProjectFlow(deps, repo.id, {});
|
||||
});
|
||||
cmd('arboretum.stopLaunch', (node) => {
|
||||
const s = asSession(node as AnyNode);
|
||||
if (s?.launchRunId) void stopLaunchFlow(deps, s.launchRunId);
|
||||
});
|
||||
|
||||
// ---- conscience du workspace (Phase D) ----
|
||||
cmd('arboretum.startSessionHere', () => {
|
||||
const repoId = workspace.currentRepoId();
|
||||
@@ -331,3 +346,47 @@ async function startGroupSessionFlow(deps: CommandDeps, group: GroupSummary): Pr
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lance les commandes de démarrage du repo (un terminal natif par commande activée). L'édition des
|
||||
* commandes reste dans l'IDE web : si aucune n'est configurée, on propose de l'ouvrir.
|
||||
*/
|
||||
async function startProjectFlow(
|
||||
deps: CommandDeps,
|
||||
repoId: string,
|
||||
body: { worktreePath?: string },
|
||||
): Promise<void> {
|
||||
await run('start project', async () => {
|
||||
const res = await deps.rest.startLaunch(repoId, body);
|
||||
for (const s of res.sessions) deps.terminals.open(s, 'interactive');
|
||||
if (res.sessions.length === 0) {
|
||||
const OPEN = 'Open web IDE';
|
||||
const pick = await vscode.window.showInformationMessage(
|
||||
'Arboretum: no launch commands configured for this project. Define them in the web IDE.',
|
||||
OPEN,
|
||||
);
|
||||
if (pick === OPEN) void vscode.env.openExternal(vscode.Uri.parse(deps.rest.url));
|
||||
return;
|
||||
}
|
||||
if (res.skipped.length > 0) {
|
||||
void vscode.window.showWarningMessage(
|
||||
`Arboretum: ${res.skipped.length} command(s) skipped: ${res.skipped.map((s) => s.reason).join('; ')}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Arrête d'un coup tous les terminaux vivants d'un même lancement (partageant le launchRunId). */
|
||||
async function stopLaunchFlow(deps: CommandDeps, launchRunId: string): Promise<void> {
|
||||
const live = deps.store.allSessions().filter((s) => s.live && s.launchRunId === launchRunId);
|
||||
if (live.length === 0) return;
|
||||
const ok = await vscode.window.showWarningMessage(
|
||||
`Stop all ${live.length} launch terminal(s)?`,
|
||||
{ modal: true },
|
||||
'Stop all',
|
||||
);
|
||||
if (ok !== 'Stop all') return;
|
||||
await run('stop launch', async () => {
|
||||
await Promise.all(live.map((s) => deps.rest.killSession(s.id)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,6 +58,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -94,6 +97,7 @@ function sessionDescription(s: SessionSummary): string {
|
||||
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(' ');
|
||||
}
|
||||
|
||||
@@ -62,6 +62,18 @@ describe('RestClient', () => {
|
||||
expect(JSON.parse(String(captured[1]?.init.body))).toEqual({ path: '/w', mode: 'rebase' });
|
||||
});
|
||||
|
||||
it('startLaunch POST /repos/:id/launch avec le corps (commandIds / worktreePath)', async () => {
|
||||
const captured: Captured[] = [];
|
||||
const rest = new RestClient(
|
||||
{ baseUrl: 'http://h:1', token: 't' },
|
||||
fakeFetch({ ok: true, status: 200, body: { sessions: [], skipped: [] } }, captured),
|
||||
);
|
||||
await rest.startLaunch('repo id', { worktreePath: '/w', commandIds: ['a', 'b'] });
|
||||
expect(captured[0]?.url).toBe('http://h:1/api/v1/repos/repo%20id/launch');
|
||||
expect(captured[0]?.init.method).toBe('POST');
|
||||
expect(JSON.parse(String(captured[0]?.init.body))).toEqual({ worktreePath: '/w', commandIds: ['a', 'b'] });
|
||||
});
|
||||
|
||||
it('listSessions construit la query includeHidden/includeArchived', async () => {
|
||||
const captured: Captured[] = [];
|
||||
const rest = new RestClient(
|
||||
|
||||
@@ -9,6 +9,7 @@ function repo(id: string): RepoSummary {
|
||||
label: id,
|
||||
defaultBranch: 'main',
|
||||
postCreateHooks: [],
|
||||
launchCommands: [],
|
||||
preTrust: false,
|
||||
createdAt: '2026-01-01T00:00:00Z',
|
||||
valid: true,
|
||||
|
||||
Reference in New Issue
Block a user