electron app
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
{"_e":"root:init","_t":1787490823136,"format":"v0-jsonl","version":"57.0.12"}
|
||||||
|
{"_e":"env:mode","_t":1787490823267,"nodeEnv":"production","babelEnv":"production","mode":"production"}
|
||||||
|
{"_e":"env:load","_t":1787490823269,"mode":"production","files":[],"env":{}}
|
||||||
|
{"_e":"devserver:start","_t":1787490823283,"mode":"production","web":true,"baseUrl":"","asyncRoutes":false,"routerRoot":"../../../app","serverComponents":false,"serverActions":false,"serverRendering":false,"apiRoutes":false,"exporting":true}
|
||||||
|
{"_e":"metro:config","_t":1787490823513,"serverRoot":"../../..","projectRoot":"../../..","exporting":true,"flags":{"autolinkingModuleResolution":false,"serverActions":false,"serverComponents":false,"reactCompiler":false,"optimizeGraph":false,"treeshaking":false,"logbox":false}}
|
||||||
|
{"_e":"metro:instantiate","_t":1787490823624,"atlas":false,"workers":10,"host":null,"port":null}
|
||||||
|
{"_e":"metro:bundling:started","_t":1787490824135,"id":"1","platform":"web","environment":null,"entry":"node_modules/expo-router/entry.js"}
|
||||||
|
{"_e":"root:init","_t":1787490834893,"format":"v0-jsonl","version":"57.0.12"}
|
||||||
|
{"_e":"env:mode","_t":1787490834983,"nodeEnv":"production","babelEnv":"production","mode":"production"}
|
||||||
|
{"_e":"env:load","_t":1787490834985,"mode":"production","files":[],"env":{}}
|
||||||
|
{"_e":"devserver:start","_t":1787490834998,"mode":"production","web":true,"baseUrl":"","asyncRoutes":false,"routerRoot":"../../../app","serverComponents":false,"serverActions":false,"serverRendering":false,"apiRoutes":false,"exporting":true}
|
||||||
|
{"_e":"metro:config","_t":1787490835165,"serverRoot":"../../..","projectRoot":"../../..","exporting":true,"flags":{"autolinkingModuleResolution":false,"serverActions":false,"serverComponents":false,"reactCompiler":false,"optimizeGraph":false,"treeshaking":false,"logbox":false}}
|
||||||
|
{"_e":"metro:instantiate","_t":1787490835236,"atlas":false,"workers":10,"host":null,"port":null}
|
||||||
|
{"_e":"metro:bundling:started","_t":1787490835782,"id":"1","platform":"web","environment":null,"entry":"node_modules/expo-router/entry.js"}
|
||||||
|
{"_e":"root:init","_t":1787490897618,"format":"v0-jsonl","version":"57.0.12"}
|
||||||
|
{"_e":"env:mode","_t":1787490897716,"nodeEnv":"production","babelEnv":"production","mode":"production"}
|
||||||
|
{"_e":"env:load","_t":1787490897718,"mode":"production","files":[],"env":{}}
|
||||||
|
{"_e":"devserver:start","_t":1787490897732,"mode":"production","web":true,"baseUrl":"","asyncRoutes":false,"routerRoot":"../../../app","serverComponents":false,"serverActions":false,"serverRendering":false,"apiRoutes":false,"exporting":true}
|
||||||
|
{"_e":"metro:config","_t":1787490897904,"serverRoot":"../../..","projectRoot":"../../..","exporting":true,"flags":{"autolinkingModuleResolution":false,"serverActions":false,"serverComponents":false,"reactCompiler":false,"optimizeGraph":false,"treeshaking":false,"logbox":false}}
|
||||||
|
{"_e":"metro:instantiate","_t":1787490897978,"atlas":false,"workers":10,"host":null,"port":null}
|
||||||
|
{"_e":"metro:bundling:started","_t":1787490898645,"id":"1","platform":"web","environment":null,"entry":"node_modules/expo-router/entry.js"}
|
||||||
|
{"_e":"metro:bundling:done","_t":1787490904477,"id":"1","total":743,"ms":5831.675237}
|
||||||
|
{"_e":"root:init","_t":1787490907952,"format":"v0-jsonl","version":"57.0.12"}
|
||||||
|
{"_e":"env:mode","_t":1787490908055,"nodeEnv":"production","babelEnv":"production","mode":"production"}
|
||||||
|
{"_e":"env:load","_t":1787490908056,"mode":"production","files":[],"env":{}}
|
||||||
|
{"_e":"devserver:start","_t":1787490908069,"mode":"production","web":true,"baseUrl":"","asyncRoutes":false,"routerRoot":"../../../app","serverComponents":false,"serverActions":false,"serverRendering":false,"apiRoutes":false,"exporting":true}
|
||||||
|
{"_e":"metro:config","_t":1787490908244,"serverRoot":"../../..","projectRoot":"../../..","exporting":true,"flags":{"autolinkingModuleResolution":false,"serverActions":false,"serverComponents":false,"reactCompiler":false,"optimizeGraph":false,"treeshaking":false,"logbox":false}}
|
||||||
|
{"_e":"metro:instantiate","_t":1787490908323,"atlas":false,"workers":10,"host":null,"port":null}
|
||||||
|
{"_e":"metro:bundling:started","_t":1787490908955,"id":"1","platform":"web","environment":null,"entry":"node_modules/expo-router/entry.js"}
|
||||||
|
{"_e":"metro:bundling:done","_t":1787490909421,"id":"1","total":731,"ms":465.745099}
|
||||||
|
{"_e":"root:init","_t":1787490971753,"format":"v0-jsonl","version":"57.0.12"}
|
||||||
|
{"_e":"env:mode","_t":1787490972145,"nodeEnv":"production","babelEnv":"production","mode":"production"}
|
||||||
|
{"_e":"env:load","_t":1787490972146,"mode":"production","files":[],"env":{}}
|
||||||
|
{"_e":"devserver:start","_t":1787490972158,"mode":"production","web":true,"baseUrl":"","asyncRoutes":false,"routerRoot":"../../../app","serverComponents":false,"serverActions":false,"serverRendering":false,"apiRoutes":false,"exporting":true}
|
||||||
|
{"_e":"metro:config","_t":1787490972351,"serverRoot":"../../..","projectRoot":"../../..","exporting":true,"flags":{"autolinkingModuleResolution":false,"serverActions":false,"serverComponents":false,"reactCompiler":false,"optimizeGraph":false,"treeshaking":false,"logbox":false}}
|
||||||
|
{"_e":"metro:instantiate","_t":1787490972454,"atlas":false,"workers":10,"host":null,"port":null}
|
||||||
|
{"_e":"metro:bundling:started","_t":1787490973074,"id":"1","platform":"web","environment":null,"entry":"node_modules/expo-router/entry.js"}
|
||||||
|
{"_e":"metro:bundling:done","_t":1787490973517,"id":"1","total":743,"ms":441.956684}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: Build Desktop (deb)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
|
- name: Install JS dependencies
|
||||||
|
working-directory: carry-your-live
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Export web bundle
|
||||||
|
working-directory: carry-your-live
|
||||||
|
run: npx expo export --platform web
|
||||||
|
|
||||||
|
- name: Build deb package
|
||||||
|
working-directory: carry-your-live
|
||||||
|
run: npx electron-builder --linux deb --config electron-builder.config.js
|
||||||
|
|
||||||
|
- name: Upload deb
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: carry-your-live-desktop-deb
|
||||||
|
path: carry-your-live/desktop-build/*.deb
|
||||||
@@ -127,6 +127,7 @@ Client-side only; no backend changes required.
|
|||||||
- Custom app icon / theme presets beyond accent color
|
- Custom app icon / theme presets beyond accent color
|
||||||
- Onboarding flow + sample data seeding
|
- Onboarding flow + sample data seeding
|
||||||
- Android home-screen widget / week agenda widget
|
- Android home-screen widget / week agenda widget
|
||||||
|
- import calendar from other apps
|
||||||
|
|
||||||
**Theme:** "Make it yours, make it fast."
|
**Theme:** "Make it yours, make it fast."
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ node_modules/
|
|||||||
.expo/
|
.expo/
|
||||||
dist/
|
dist/
|
||||||
web-build/
|
web-build/
|
||||||
|
|
||||||
|
# Electron
|
||||||
|
desktop-build/
|
||||||
expo-env.d.ts
|
expo-env.d.ts
|
||||||
|
|
||||||
# Native
|
# Native
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 384 KiB |
@@ -0,0 +1,46 @@
|
|||||||
|
module.exports = {
|
||||||
|
appId: 'com.carryyourlive.desktop',
|
||||||
|
productName: 'Carry Your Live',
|
||||||
|
copyright: 'Copyright © 2026',
|
||||||
|
|
||||||
|
directories: {
|
||||||
|
output: 'desktop-build',
|
||||||
|
buildResources: 'build',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Override the entry point in the packaged app (repo keeps expo-router/entry
|
||||||
|
// as `main` so `expo start` keeps working).
|
||||||
|
extraMetadata: {
|
||||||
|
main: 'electron/main.js',
|
||||||
|
},
|
||||||
|
|
||||||
|
// The Electron shell needs zero runtime dependencies: main/preload use Node
|
||||||
|
// built-ins only, and the renderer is fully bundled by Metro into dist/.
|
||||||
|
// Returning false tells electron-builder that node_modules are handled
|
||||||
|
// externally — i.e. don't bundle production deps (expo, react-native, ...)
|
||||||
|
// into the package. This keeps the deb ~100x smaller.
|
||||||
|
// NOTE: do not set npmRebuild:false here — installAppDependencies()
|
||||||
|
// short-circuits on it *before* evaluating beforeBuild, which would
|
||||||
|
// re-enable bundling of node_modules.
|
||||||
|
beforeBuild: async () => false,
|
||||||
|
// Sanity check validates the ORIGINAL package.json `main` (expo-router/entry)
|
||||||
|
// against the asar, which no longer exists once deps are excluded.
|
||||||
|
// The real entry point is set via extraMetadata.main above.
|
||||||
|
disableSanityCheckAsar: true,
|
||||||
|
|
||||||
|
files: ['dist/**/*', 'electron/**/*', 'assets/icon.png', 'package.json'],
|
||||||
|
|
||||||
|
// Icons are generated from build/icon.png (1024x1024) for each platform.
|
||||||
|
mac: {
|
||||||
|
category: 'public.app-category.productivity',
|
||||||
|
target: ['dmg', 'zip'],
|
||||||
|
},
|
||||||
|
win: {
|
||||||
|
target: ['nsis'],
|
||||||
|
},
|
||||||
|
linux: {
|
||||||
|
category: 'Productivity',
|
||||||
|
maintainer: 'carry-your-live',
|
||||||
|
target: ['deb', 'AppImage'],
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
/* global __dirname */
|
/* global __dirname */
|
||||||
const { app, BrowserWindow } = require('electron')
|
const { app, BrowserWindow, ipcMain } = require('electron')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
|
const DEV_SERVER_URL = process.env.ELECTRON_START_URL
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
width: 1200,
|
width: 1200,
|
||||||
@@ -11,24 +13,39 @@ function createWindow() {
|
|||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
|
sandbox: true,
|
||||||
webSecurity: true,
|
webSecurity: true,
|
||||||
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
},
|
},
|
||||||
icon: path.join(__dirname, '../assets/icon.png'),
|
icon: path.join(__dirname, '../assets/icon.png'),
|
||||||
titleBarStyle: 'default',
|
titleBarStyle: 'default',
|
||||||
show: false,
|
show: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (DEV_SERVER_URL) {
|
||||||
|
win.loadURL(DEV_SERVER_URL)
|
||||||
|
} else {
|
||||||
win.loadFile(path.join(__dirname, '../dist/index.html'))
|
win.loadFile(path.join(__dirname, '../dist/index.html'))
|
||||||
|
}
|
||||||
|
|
||||||
win.once('ready-to-show', () => {
|
win.once('ready-to-show', () => {
|
||||||
win.show()
|
win.show()
|
||||||
})
|
})
|
||||||
|
|
||||||
win.on('closed', () => {
|
// In dev, reload instead of quitting when the renderer crashes / navigates away
|
||||||
app.quit()
|
win.webContents.on('didFailLoad', (_event, code, desc) => {
|
||||||
|
console.error(`[electron] failed to load: ${code} ${desc}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return win
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcMain.handle('app:get-info', () => ({
|
||||||
|
version: app.getVersion(),
|
||||||
|
name: app.getName(),
|
||||||
|
platform: process.platform,
|
||||||
|
}))
|
||||||
|
|
||||||
app.whenReady().then(createWindow)
|
app.whenReady().then(createWindow)
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const { contextBridge, ipcRenderer } = require('electron')
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('electron', {
|
||||||
|
isElectron: true,
|
||||||
|
getAppInfo: () => ipcRenderer.invoke('app:get-info'),
|
||||||
|
})
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 8081;
|
|
||||||
server_name localhost;
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /api {
|
|
||||||
proxy_pass http://backend:3000;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
+2639
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,8 @@
|
|||||||
"name": "carry-your-live",
|
"name": "carry-your-live",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "expo-router/entry",
|
"main": "expo-router/entry",
|
||||||
|
"homepage": "http://localhost:8081",
|
||||||
|
"homepage": "https://gitea.techmos.org/tech08mag/carry-your-live",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
"@nozbe/watermelondb": "^0.28.1-0",
|
"@nozbe/watermelondb": "^0.28.1-0",
|
||||||
@@ -35,6 +37,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.0",
|
"@babel/core": "^7.20.0",
|
||||||
"@types/react": "~19.2.2",
|
"@types/react": "~19.2.2",
|
||||||
|
"electron": "^41.7.1",
|
||||||
|
"electron-builder": "^26.15.3",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-expo": "~57.0.1",
|
"eslint-config-expo": "~57.0.1",
|
||||||
"patch-package": "^8.0.1",
|
"patch-package": "^8.0.1",
|
||||||
@@ -49,6 +53,11 @@
|
|||||||
"lint": "expo lint",
|
"lint": "expo lint",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"format": "prettier --write \"**/*.{ts,tsx,json,md}\"",
|
"format": "prettier --write \"**/*.{ts,tsx,json,md}\"",
|
||||||
|
"export:web": "expo export --platform web",
|
||||||
|
"desktop": "electron electron/main.js",
|
||||||
|
"desktop:dev": "ELECTRON_START_URL=http://localhost:8081 electron electron/main.js",
|
||||||
|
"desktop:dist": "npm run export:web && electron-builder --config electron-builder.config.js",
|
||||||
|
"desktop:dir": "npm run export:web && electron-builder --dir --config electron-builder.config.js",
|
||||||
"build:ios": "eas build --platform ios",
|
"build:ios": "eas build --platform ios",
|
||||||
"build:android": "eas build --platform android",
|
"build:android": "eas build --platform android",
|
||||||
"build:web": "eas build --platform web",
|
"build:web": "eas build --platform web",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import RepeatProfile from '@/models/RepeatProfile';
|
|||||||
import Friendship from '@/models/Friendship';
|
import Friendship from '@/models/Friendship';
|
||||||
import { TombstoneEntity } from '@/models/Tombstone';
|
import { TombstoneEntity } from '@/models/Tombstone';
|
||||||
import { fetchPendingTombstones, removeTombstonesInBatch } from './tombstones';
|
import { fetchPendingTombstones, removeTombstonesInBatch } from './tombstones';
|
||||||
|
import { useOnline } from '@/hooks/useOnline';
|
||||||
|
|
||||||
const LAST_PULLED_AT_KEY = 'sync:lastPulledAt';
|
const LAST_PULLED_AT_KEY = 'sync:lastPulledAt';
|
||||||
const LAST_PUSHED_AT_KEY = 'sync:lastPushedAt';
|
const LAST_PUSHED_AT_KEY = 'sync:lastPushedAt';
|
||||||
@@ -94,6 +95,21 @@ export async function runSync(): Promise<SyncResult> {
|
|||||||
throw new Error('NOT_SIGNED_IN');
|
throw new Error('NOT_SIGNED_IN');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If offline, mark timestamps and return early - changes will sync on next foreground launch
|
||||||
|
const isCurrentlyOnline = await useOnline();
|
||||||
|
if (!isCurrentlyOnline) {
|
||||||
|
const now = Date.now();
|
||||||
|
await setLastPulledAt(now);
|
||||||
|
await setLastPushedAt(now);
|
||||||
|
const result: SyncResult = {
|
||||||
|
pushed: { categories: 0, tasks: 0, subtasks: 0, repeatProfiles: 0, friendships: 0 },
|
||||||
|
pulled: { categories: 0, tasks: 0, subtasks: 0, repeatProfiles: 0, friendships: 0 },
|
||||||
|
conflicts: 0,
|
||||||
|
timestamp: now,
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
const result: SyncResult = {
|
const result: SyncResult = {
|
||||||
pushed: { categories: 0, tasks: 0, subtasks: 0, repeatProfiles: 0, friendships: 0 },
|
pushed: { categories: 0, tasks: 0, subtasks: 0, repeatProfiles: 0, friendships: 0 },
|
||||||
pulled: { categories: 0, tasks: 0, subtasks: 0, repeatProfiles: 0, friendships: 0 },
|
pulled: { categories: 0, tasks: 0, subtasks: 0, repeatProfiles: 0, friendships: 0 },
|
||||||
@@ -113,8 +129,18 @@ export async function runSync(): Promise<SyncResult> {
|
|||||||
|
|
||||||
// Use current time as pull cutoff to avoid re-pulling what we just pushed
|
// Use current time as pull cutoff to avoid re-pulling what we just pushed
|
||||||
const pullSince = Date.now();
|
const pullSince = Date.now();
|
||||||
|
let data: any;
|
||||||
|
try {
|
||||||
const response = await apiFetch(`/sync?since=${pullSince}`);
|
const response = await apiFetch(`/sync?since=${pullSince}`);
|
||||||
const data = await response.json();
|
data = await response.json();
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.offline) {
|
||||||
|
result.timestamp = Date.now();
|
||||||
|
await setLastPulledAt(Date.now());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
const pulledCounts = await applyServerChanges(data);
|
const pulledCounts = await applyServerChanges(data);
|
||||||
result.pulled = pulledCounts;
|
result.pulled = pulledCounts;
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export function useOnline(): boolean {
|
||||||
|
const [isOnline, setIsOnline] = useState(navigator.onLine);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = () => setIsOnline(navigator.onLine);
|
||||||
|
window.addEventListener('online', handler);
|
||||||
|
window.addEventListener('offline', handler);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('online', handler);
|
||||||
|
window.removeEventListener('offline', handler);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return isOnline;
|
||||||
|
}
|
||||||
@@ -96,10 +96,19 @@ export async function apiFetch(path: string, options: RequestInit = {}): Promise
|
|||||||
}
|
}
|
||||||
const response = await fetch(`${apiBaseUrl}${path}`, { ...options, headers });
|
const response = await fetch(`${apiBaseUrl}${path}`, { ...options, headers });
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
const text = await response.text();
|
||||||
|
// If it's a 401 or network error, try to get a fresh token and retry once
|
||||||
|
if (response.status === 404 || response.status === 0) {
|
||||||
|
// Not found or network error - don't throw, return the response for caller to handle
|
||||||
|
const error = new Error(text || `Request failed (${response.status})`);
|
||||||
|
(error as any).status = response.status;
|
||||||
|
(error as any).offline = true;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
let message = `Request failed (${response.status})`;
|
let message = `Request failed (${response.status})`;
|
||||||
let details: any = null;
|
let details: any = null;
|
||||||
try {
|
try {
|
||||||
const body = await response.json();
|
const body = JSON.parse(text);
|
||||||
if (body && typeof body.message === 'string') {
|
if (body && typeof body.message === 'string') {
|
||||||
message = body.message;
|
message = body.message;
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+3764
-84
File diff suppressed because it is too large
Load Diff
+32
-1
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"name": "carry-your-live",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Carry Your Live - Desktop App",
|
||||||
|
"main": "expo-router/entry",
|
||||||
|
"homepage": "http://localhost:8081",
|
||||||
|
"homepage": "https://gitea.techmos.org/tech08mag/carry-your-live",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.7.1",
|
"@hookform/resolvers": "^5.7.1",
|
||||||
"@nozbe/watermelondb": "^0.28.0",
|
"@nozbe/watermelondb": "^0.28.0",
|
||||||
@@ -14,9 +20,34 @@
|
|||||||
"react-native-reanimated": "^4.5.3",
|
"react-native-reanimated": "^4.5.3",
|
||||||
"react-native-safe-area-context": "^5.8.1",
|
"react-native-safe-area-context": "^5.8.1",
|
||||||
"react-native-screens": "^4.26.2",
|
"react-native-screens": "^4.26.2",
|
||||||
|
"react-native-web": "^0.21.2",
|
||||||
"zod": "^4.4.3"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"electron": "^41.7.1",
|
||||||
|
"electron-builder": "^26.15.3",
|
||||||
|
"patch-package": "^8.0.1",
|
||||||
"typescript": "~6.0.3"
|
"typescript": "~6.0.3"
|
||||||
}
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "expo start",
|
||||||
|
"android": "expo run:android",
|
||||||
|
"ios": "expo run:ios",
|
||||||
|
"web": "expo start --web",
|
||||||
|
"lint": "expo lint",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"format": "prettier --write \"**/*.{ts,tsx,json,md}\"",
|
||||||
|
"export:web": "expo export --platform web",
|
||||||
|
"desktop": "electron electron/main.js",
|
||||||
|
"desktop:dev": "ELECTRON_START_URL=http://localhost:8081 electron electron/main.js",
|
||||||
|
"desktop:dist": "npm run export:web && electron-builder",
|
||||||
|
"desktop:dir": "npm run export:web && electron-builder --dir",
|
||||||
|
"build:ios": "eas build --platform ios",
|
||||||
|
"build:android": "eas build --platform android",
|
||||||
|
"build:web": "eas build --platform web",
|
||||||
|
"submit:ios": "eas submit --platform ios",
|
||||||
|
"submit:android": "eas submit --platform android",
|
||||||
|
"postinstall": "patch-package"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user