diff --git a/apps/mobile/src/mobileGitRemote.test.ts b/apps/mobile/src/mobileGitRemote.test.ts new file mode 100644 index 00000000..217e037e --- /dev/null +++ b/apps/mobile/src/mobileGitRemote.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest' +import { parseMobileGitRemote } from './mobileGitRemote' + +describe('mobile git remote parsing', () => { + it('uses GitHub OAuth for GitHub HTTPS remotes', () => { + expect(parseMobileGitRemote('https://github.com/refactoringhq/tolaria.git')).toEqual({ + url: 'https://github.com/refactoringhq/tolaria.git', + host: 'github.com', + owner: 'refactoringhq', + repository: 'tolaria', + authStrategy: 'githubOAuth', + }) + }) + + it('uses GitHub OAuth for GitHub SSH shorthand remotes', () => { + expect(parseMobileGitRemote('git@github.com:refactoringhq/tolaria.git')).toMatchObject({ + host: 'github.com', + owner: 'refactoringhq', + repository: 'tolaria', + authStrategy: 'githubOAuth', + }) + }) + + it('uses SSH keys for non-GitHub SSH remotes', () => { + expect(parseMobileGitRemote('ssh://git@git.example.com/acme/notes.git')).toMatchObject({ + host: 'git.example.com', + owner: 'acme', + repository: 'notes', + authStrategy: 'sshKey', + }) + }) + + it('uses SSH keys for non-GitHub HTTPS remotes', () => { + expect(parseMobileGitRemote('https://gitlab.com/acme/notes.git')).toMatchObject({ + host: 'gitlab.com', + owner: 'acme', + repository: 'notes', + authStrategy: 'sshKey', + }) + }) + + it('rejects non-remote input', () => { + expect(parseMobileGitRemote('/Users/luca/Laputa')).toBeNull() + expect(parseMobileGitRemote('')).toBeNull() + }) +}) diff --git a/apps/mobile/src/mobileGitRemote.ts b/apps/mobile/src/mobileGitRemote.ts new file mode 100644 index 00000000..e27f96c9 --- /dev/null +++ b/apps/mobile/src/mobileGitRemote.ts @@ -0,0 +1,71 @@ +export type MobileGitAuthStrategy = 'githubOAuth' | 'sshKey' + +export type MobileGitRemote = { + url: string + host: string + owner: string + repository: string + authStrategy: MobileGitAuthStrategy +} + +export function parseMobileGitRemote(url: string): MobileGitRemote | null { + const normalizedUrl = url.trim() + return parseHttpRemote(normalizedUrl) ?? parseScpRemote(normalizedUrl) ?? parseSshRemote(normalizedUrl) +} + +function parseHttpRemote(url: string): MobileGitRemote | null { + const parsedUrl = parseUrl(url) + if (!parsedUrl || !['http:', 'https:'].includes(parsedUrl.protocol)) { + return null + } + + return createRemote(url, parsedUrl.hostname, pathParts(parsedUrl.pathname)) +} + +function parseSshRemote(url: string): MobileGitRemote | null { + const parsedUrl = parseUrl(url) + if (!parsedUrl || parsedUrl.protocol !== 'ssh:') { + return null + } + + return createRemote(url, parsedUrl.hostname, pathParts(parsedUrl.pathname)) +} + +function parseScpRemote(url: string): MobileGitRemote | null { + const match = /^git@([^:]+):([^/]+)\/(.+)$/.exec(url) + if (!match) { + return null + } + + return createRemote(url, match[1], [match[2], stripGitSuffix(match[3])]) +} + +function createRemote(url: string, host: string, parts: string[]): MobileGitRemote | null { + if (parts.length < 2) { + return null + } + + return { + url, + host, + owner: parts[0], + repository: stripGitSuffix(parts[1]), + authStrategy: host === 'github.com' ? 'githubOAuth' : 'sshKey', + } +} + +function parseUrl(url: string) { + try { + return new URL(url) + } catch { + return null + } +} + +function pathParts(pathname: string) { + return pathname.split('/').filter(Boolean) +} + +function stripGitSuffix(value: string) { + return value.endsWith('.git') ? value.slice(0, -4) : value +} diff --git a/docs/MOBILE_PROGRESS.md b/docs/MOBILE_PROGRESS.md index 4d826f43..89f0b6f7 100644 --- a/docs/MOBILE_PROGRESS.md +++ b/docs/MOBILE_PROGRESS.md @@ -8,7 +8,7 @@ This file is the resumable working log for Tolaria mobile. The strategy and road - Branch: `codex/mobile` - Active phase: Phase 2 - Mobile Shell -- Active slice: Add compact swipe gesture support +- Active slice: Add mobile Git remote auth strategy parsing - Push policy: commit locally; do not push unless explicitly requested - Validation target: iPad/iOS simulator first @@ -43,13 +43,14 @@ This file is the resumable working log for Tolaria mobile. The strategy and road - Wrapped the mobile root in `GestureHandlerRootView`. - Added a tested compact gesture mapper and `SwipeSurface` wrapper so phone panels can transition through swipe gestures while keeping panel behavior in the reducer. - Re-tested the iPad simulator path; the app now launches in Expo Go on `iPad Pro 13-inch (M4)`. +- Added a pure mobile Git remote parser that codifies the auth choice: GitHub remotes use the GitHub OAuth App path, arbitrary Git remotes use the SSH-key path. ## Next Action Continue Phase 2 with the next mobile shell slice: 1. Dismiss or suppress Expo Go's first-run tools modal during simulator QA so screenshots capture the app without the overlay. -2. Add the storage/auth abstraction skeleton behind the mobile vault repository. +2. Connect parsed Git remotes to a mobile vault configuration model. 3. Begin the TenTap editor spike behind a `MobileEditorAdapter` once the shell/storage boundary is stable. ## Verification Log @@ -114,6 +115,10 @@ Continue Phase 2 with the next mobile shell slice: - CodeScene after gesture support: `apps/mobile/src/compactGestures.ts`, `apps/mobile/src/compactGestures.test.ts`, `apps/mobile/src/SwipeSurface.tsx`, touched app/root files, and touched style module scored `10`; `apps/mobile/index.ts` returned no scorable code. - `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export` passed after adding gesture support. - Simulator screenshot after gesture support captured at `/tmp/tolaria-mobile-gestures-ipad.png`; no red runtime error overlay appeared. +- `pnpm --filter @tolaria/mobile test` passed after Git remote auth parsing: 6 files / 20 tests. +- `pnpm --filter @tolaria/mobile typecheck` passed after Git remote auth parsing. +- CodeScene after Git remote auth parsing: `apps/mobile/src/mobileGitRemote.ts` and `apps/mobile/src/mobileGitRemote.test.ts` scored `10`. +- `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export` passed after Git remote auth parsing. ## Risks / Watch Items