• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

agentic-dev-library / thumbcode / 21961208242

12 Feb 2026 07:29PM UTC coverage: 51.569% (-13.4%) from 64.947%
21961208242

push

github

web-flow
feat: complete Expo → Capacitor/Vite migration (#130)

## Summary

Full framework migration from Expo/React Native to Vite + Capacitor + React Router:

- **Framework**: Expo SDK 52 → Vite 6 + Capacitor 7
- **Routing**: expo-router → react-router-dom v7
- **Styling**: NativeWind → Tailwind CSS v4
- **Testing**: Jest → Vitest + @testing-library/react
- **Components**: React Native primitives → HTML/JSX (View→div, Text→span, etc.)
- **Build**: Metro bundler → Vite with HMR

### Key changes
- Removed all React Native and Expo dependencies
- Migrated 50+ components from RN to web HTML/JSX
- Rewrote 70 test files (844 tests passing)
- Replaced deploy-gh-pages.yml: Astro docs → Vite static site deployment
- Added android-release.yml: per-architecture debug APKs on GitHub releases
- Added ABI splits to build.gradle (armeabi-v7a, arm64-v8a, x86_64, universal)
- Fixed all biome lint, TypeScript, and E2E test issues
- Deleted dead `app/` directory (old Expo Router screens)

### CI Status
All critical checks passing: Lint & Type Check, Run Tests (844 passing), Build Web, Build Web + Capacitor Sync, E2E Tests (Web), Security Scan, CodeQL, Validate PR.

1329 of 2944 branches covered (45.14%)

Branch coverage included in aggregate %.

138 of 866 new or added lines in 82 files covered. (15.94%)

120 existing lines in 23 files now uncovered.

2188 of 3876 relevant lines covered (56.45%)

9.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.45
/packages/core/src/git/git-fs.ts
1
/**
2
 * Git File System Adapter
3
 *
4
 * Bridges @capacitor/filesystem with isomorphic-git for Capacitor compatibility.
5
 * Shared across all Git service modules.
6
 */
7

8
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
9
import type { HttpClient } from 'isomorphic-git';
10
import { gitHttpClient } from './GitHttpClient';
11

12
// HTTP client adapter - bridges our implementation to isomorphic-git's HttpClient type
13
// Our implementation uses AsyncIterableIterator which works with isomorphic-git at runtime
14
// Using type assertion as the body types differ but are compatible at runtime
15
export const http = gitHttpClient as unknown as HttpClient;
2✔
16

17
/**
18
 * File system adapter for isomorphic-git
19
 * Uses @capacitor/filesystem for Capacitor compatibility
20
 */
21
export const fs = {
2✔
22
  promises: {
23
    readFile: async (filepath: string, options?: { encoding?: string }) => {
24
      const result = await Filesystem.readFile({
2,246✔
25
        path: filepath,
26
        directory: Directory.Documents,
27
        encoding:
28
          options?.encoding === 'utf8'
2,246✔
29
            ? Encoding.UTF8
30
            : undefined, // undefined = base64 in Capacitor
31
      });
32
      if (options?.encoding === 'utf8') {
1,217✔
33
        return result.data as string;
211✔
34
      }
35
      // Return as Buffer-like for binary files (data is base64 string when no encoding)
36
      return Buffer.from(result.data as string, 'base64');
1,006✔
37
    },
38

39
    writeFile: async (
40
      filepath: string,
41
      data: string | Uint8Array,
42
      _options?: { mode?: number }
43
    ) => {
44
      const isString = typeof data === 'string';
408✔
45
      await Filesystem.writeFile({
408✔
46
        path: filepath,
47
        data: isString ? data : Buffer.from(data).toString('base64'),
408✔
48
        directory: Directory.Documents,
49
        encoding: isString ? Encoding.UTF8 : undefined,
408✔
50
        recursive: true,
51
      });
52
    },
53

54
    unlink: async (filepath: string) => {
NEW
55
      try {
×
NEW
56
        await Filesystem.deleteFile({
×
57
          path: filepath,
58
          directory: Directory.Documents,
59
        });
60
      } catch {
61
        // Idempotent delete - ignore if not found
62
      }
63
    },
64

65
    readdir: async (dirpath: string) => {
NEW
66
      const result = await Filesystem.readdir({
×
67
        path: dirpath,
68
        directory: Directory.Documents,
69
      });
NEW
70
      return result.files.map((f) => f.name);
×
71
    },
72

73
    mkdir: async (dirpath: string, options?: { recursive?: boolean }) => {
74
      await Filesystem.mkdir({
7✔
75
        path: dirpath,
76
        directory: Directory.Documents,
77
        recursive: options?.recursive ?? true,
13✔
78
      });
79
    },
80

81
    rmdir: async (dirpath: string) => {
NEW
82
      try {
×
NEW
83
        await Filesystem.rmdir({
×
84
          path: dirpath,
85
          directory: Directory.Documents,
86
          recursive: true,
87
        });
88
      } catch {
89
        // Idempotent delete - ignore if not found
90
      }
91
    },
92

93
    stat: async (filepath: string) => {
94
      try {
1,416✔
95
        const info = await Filesystem.stat({
1,416✔
96
          path: filepath,
97
          directory: Directory.Documents,
98
        });
99
        return {
1,009✔
NEW
100
          isFile: () => info.type === 'file',
×
101
          isDirectory: () => info.type === 'directory',
608✔
102
          isSymbolicLink: () => false,
200✔
103
          size: info.size || 0,
1,009!
104
          mode: 0o644,
105
          mtimeMs: info.mtime ? new Date(info.mtime).getTime() : 0,
1,009!
106
          ctimeMs: info.ctime ? new Date(info.ctime).getTime() : 0,
1,009!
107
          uid: 0,
108
          gid: 0,
109
          dev: 0,
110
          ino: 0,
111
        };
112
      } catch {
113
        const error = new Error(`ENOENT: no such file or directory, stat '${filepath}'`);
407✔
114
        (error as any).code = 'ENOENT';
407✔
115
        throw error;
407✔
116
      }
117
    },
118

119
    lstat: async (filepath: string) => {
120
      // For Capacitor, lstat behaves same as stat
121
      return fs.promises.stat(filepath);
602✔
122
    },
123

124
    readlink: async (_filepath: string): Promise<string> => {
125
      // Symlinks not fully supported in Capacitor mobile
126
      throw new Error('Symlinks not supported');
×
127
    },
128

129
    symlink: async (_target: string, _filepath: string): Promise<void> => {
130
      // Symlinks not fully supported in Capacitor mobile
131
      throw new Error('Symlinks not supported');
×
132
    },
133

134
    chmod: async (_filepath: string, _mode: number): Promise<void> => {
135
      // chmod not applicable in Capacitor mobile
136
      return;
×
137
    },
138
  },
139
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc