Skip to content

feat: connect DevX — recents history, recency-ordered logins & keep-alive --watch#5

Merged
pascalsaussier-edifice merged 10 commits into
mainfrom
psa/review-and-upgrades
Jun 23, 2026
Merged

feat: connect DevX — recents history, recency-ordered logins & keep-alive --watch#5
pascalsaussier-edifice merged 10 commits into
mainfrom
psa/review-and-upgrades

Conversation

@pascalsaussier-edifice

@pascalsaussier-edifice pascalsaussier-edifice commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Résumé

Améliore l'ergonomie de la commande connect (historiques de connexion, tri des logins, mode --watch keep-alive) et intègre une passe de revue technique du repo (outillage, refacto, robustesse réseau).

Contexte

Retours de développeurs sur l'usage quotidien :

  1. Jongler entre plusieurs sujets avec des comptes/droits différents obligeait à tout refaire à chaque fois.
  2. Souhait de faire remonter les derniers logins utilisés dans la sélection d'identifiant.
  3. Les sessions expirent → 401 « fantômes » dans le front local, sans visibilité.

L'idée initiale d'auto-reconnect a été challengée : la vraie douleur est l'observabilité, pas l'automatisation. Vérifié en réel sur recette — le cookie oneSessionId n'a pas de Max-Age (timeout serveur ~5 min, glissant) et GET /auth/oauth2/userinfo est une sonde fiable. Le --watch a donc été conçu en keep-alive : un ping régulier maintient la session vivante sans toucher au .env (donc sans reload Vite), et ne ré-authentifie qu'en cas de chute réelle. Mécanique validée en run réel.

En amont de ces features, une revue technique du repo a produit quelques correctifs d'outillage et de robustesse, inclus ici.

Changements

Ergonomie de connect

  • Historique des connexions récentes (3 dernières, dédupliquées par env + login + jeu d'apps, horodatées) : le menu interactif les propose en tête pour un rejeu en un clic. Migration transparente de l'ancien lastConnection.
  • Tri des logins par récence (alpha en égalité) dans la sélection d'identifiant.
  • Fraîcheur de session : « connecté il y a Xh » + ⚠️ si probablement périmée, dans le menu et au connect.
  • Mode --watch keep-alive (--watch-interval <min>, défaut 2) : ping /auth/oauth2/userinfo, ré-auth + réinjection .env uniquement si la session est tombée.

Stockage centralisé hors du repo → CLI installable/globale

  • config/app.config.json était versionné alors qu'il porte l'appsRoot propre à chaque dev (édité localement, à ne pas committer, et fuite d'un chemin perso dans git). Les données étaient en plus éparpillées (config dans le repo, credentials et environnements lus depuis le cwd), si bien que la CLI ne marchait que lancée depuis la racine du repo (un bin installé globalement ne trouvait rien).
  • Tout l'état runtime est désormais sous un seul dossier hors du repo : ~/.dev-auth-fetcher/ (surchargeable via DEV_AUTH_FETCHER_HOME) → config.json + credentials/<user>.json + environments/*.json. La CLI est donc indépendante du cwd et fonctionne en install global.
  • La liste partagée d'environnements devient une source unique versionnée dans le code (DEFAULT_ENVIRONMENTS), seedée au premier usage ; les config/environments/*.json redondants sont retirés du repo. (Ajouter un env partagé = éditer ce tableau ; un env perso = déposer un .json dans le dossier ci-dessus.)
  • Migration automatique des anciens emplacements (config/app.config.json, ./.dev-auth-fetcher/, config/environments/) au premier lancement ; config/app.config.json est dé-versionné et .gitignore.

Revue technique (en amont)

  • Suppression du dossier mort old/ (ancienne implémentation Playwright) ; ajout d'un CLAUDE.md.
  • Hygiène outillage : test = vitest run (+ test:watch), script typecheck (src + tests) câblé en CI, suppression de champs morts de config, logger partagé.
  • Refacto de EnvSyncService + injection de IAuthClient (orchestration testable sans réseau) ; dédup de la logique de reconnexion.
  • FetchAuthClient : timeout (AbortController) et erreurs réseau lisibles ; capture de l'expiration cookie ; sonde isSessionAlive.

Comment tester

pnpm install
pnpm run typecheck && pnpm run lint && pnpm test   # 45 tests

Manuellement :

  1. pnpm run dev connect deux fois avec des logins/apps différents → le menu propose ensuite plusieurs reconnexions rapides (avec l'âge), et la liste d'identifiants remonte le dernier login utilisé en tête.
  2. pnpm run dev connect --watch → toutes les 2 min : Session maintenue active. sans reload Vite ; Ctrl+C arrête proprement. Si la session tombe réellement → Session expirée — ré-authentification… puis réinjection.

La boucle --watch (timers/SIGINT) est couverte par vérification manuelle ; le reste est sous tests automatisés.

pascalsaussier-edifice and others added 9 commits June 22, 2026 14:11
- Add CLAUDE.md documenting stack, commands and architecture
- Remove unused old/ (legacy Playwright-based implementation,
  replaced by the fetch-based FetchAuthClient)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ogger)

- test now runs once (vitest run); add test:watch for watch mode
- add typecheck script (tsc) covering src + tests via tsconfig.typecheck.json,
  wired into CI; fixes latent test issues it surfaced (missing .js extensions,
  implicit any)
- remove dead AppConfig fields (profiles, lastUsedProfile)
- expose a shared logger singleton instead of re-creating it per module

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST now aborts after a configurable timeout (default 15s) so the CLI no
longer hangs forever on an unreachable recette server, and network/abort
failures are surfaced as readable AuthError messages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ogic

- inject IAuthClient into EnvSyncService (constructor, defaults to
  FetchAuthClient) so the orchestration is testable without network
- extract the 4-branch credential resolution into ResolveCredentialsStep
- extract environment resolution into a private resolveEnvironment method
- dedupe the "replay last connection" logic into reconnectOptionsFromLast /
  describeLastConnectionApps, shared by the service and reconnect-last

No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ection)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drives runInteractive with a fake IAuthClient and a temp cwd (config,
environments, discoverable app, saved profile) to cover: cookie injection
into the app .env with preservation of existing vars, no-op on auth failure,
and clean abort on unknown environment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eshness

Addresses three developer-experience asks around switching contexts and
session expiry:

- Multiple histories: replace the single lastConnection with a deduplicated
  recents history (cap 3, signed by env+login+app-set, timestamped). The
  interactive connect menu lists each recent combo for one-key replay, so
  juggling several topics/accounts no longer means redoing the whole flow.
  Migrates legacy lastConnection stores transparently.
- Recency-first login ordering: ResolveCredentialsStep surfaces the most
  recently used logins first (alpha tiebreak), derived from the history.
- Session freshness + opt-in --watch: capture the session cookie's
  Max-Age/Expires, show "connecté il y a Xh" / ⚠️ in the recents menu and on
  connect, and add `connect --watch`. The watch flag is propagated through the
  quick-reconnect replay path.

Auth client now exposes expiresAt; EnvSyncService extracts authenticateAndInject
(shared by one-shot and watch). Tests cover dedup/cap/migration/getLoginRecency,
cookie-expiry parsing, freshness helpers, and recents recording.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The recette oneSessionId cookie carries no Max-Age/Expires (pure session
cookie) and the server uses a sliding ~5 min inactivity timeout, so timer-based
re-auth was guessing and forced a Vite reload on every tick. Instead, --watch
now pings GET /auth/oauth2/userinfo (200 = alive) on an interval: while the
session answers, the ping itself keeps the sliding server-side session alive,
so nothing is rewritten and Vite never reloads. Re-auth + .env re-injection
happen only when the probe reports the session is actually dead.

- IAuthClient.isSessionAlive(envUrl, sessionId) + FetchAuthClient impl (shared
  fetch-with-timeout helper); GET with only the oneSessionId cookie.
- runWatch rewritten as a keep-alive loop; --watch-interval <minutes>, default
  2 min (kept below the observed ~5 min idle timeout so a ping refreshes the
  session mid-life rather than at expiry).
- README updated; tests for isSessionAlive (mocked fetch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
app.config.json was versioned but held the per-developer appsRoot, so each dev
had to edit it locally and avoid committing it (and a personal path leaked into
git). User data was also split between config/app.config.json (repo) and
./.dev-auth-fetcher/credentials (cwd).

Centralize all non-versioned per-user data into a single home folder
~/.dev-auth-fetcher/ (override via DEV_AUTH_FETCHER_HOME): config.json
(appsRoot, defaultEnvironment) + credentials/<user>.json. Environment
definitions stay versioned in the repo (config/environments/, shared defaults).
loadAppConfig/loadUserCredentialsStore migrate the legacy cwd locations once.
app.config.json is untracked and gitignored.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@damienromito damienromito left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

plein de choses... j'ai surtout testé l'outils et tout fonctionne bien

…nt CLI)

Config & credentials already moved to the home dir; environments were the last
piece still read from process.cwd()/config/environments, so the CLI only worked
when launched from the repo root (a global `bin` install elsewhere found no
environments — the likely "it doesn't find my config" reports).

Environments now resolve from ~/.dev-auth-fetcher/environments/, seeded on first
use from the code-level DEFAULT_ENVIRONMENTS (single versioned source of truth,
bundled), with a one-time migration of a legacy config/environments/ if present.
The redundant repo config/environments/*.json are removed. The whole runtime
state (config + credentials + environments) now lives under the home dir.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pascalsaussier-edifice
pascalsaussier-edifice merged commit 3682aec into main Jun 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants