refactor: move to monorepo (#475)

* init

* fix: electron

Signed-off-by: Innei <i@innei.in>

* update

Signed-off-by: Innei <i@innei.in>

* fix: build

Signed-off-by: Innei <i@innei.in>

* fix: lint

Signed-off-by: Innei <i@innei.in>

* fix: test

Signed-off-by: Innei <i@innei.in>

---------

Signed-off-by: Innei <i@innei.in>
This commit is contained in:
Innei 2024-09-18 20:54:48 +08:00 committed by GitHub
parent 0bcbf0bd94
commit 234471dfdf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
552 changed files with 1711 additions and 578 deletions

View File

@ -1 +1 @@
src/hono.ts
packages/shared/src/hono.ts

3
apps/main/export.ts Normal file
View File

@ -0,0 +1,3 @@
export type { RendererHandlers } from "./src/renderer-handlers"
export type { Router } from "./src/tipc"
export { router } from "./src/tipc"

2
apps/main/global.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import "../../types/vite"
import "../../types/authjs"

68
apps/main/package.json Normal file
View File

@ -0,0 +1,68 @@
{
"name": "@follow/electron-main",
"type": "module",
"private": true,
"author": "Follow Team",
"license": "GPL-3.0-only",
"homepage": "https://github.com/RSSNext",
"repository": {
"url": "https://github.com/RSSNext/follow",
"type": "git"
},
"exports": {
".": {
"types": "./dist/export.d.ts",
"import": "./dist/export.js"
}
},
"main": "./src/index.ts",
"scripts": {
"typecheck": "tsup && tsc --noEmit"
},
"dependencies": {
"@egoist/tipc": "0.3.2",
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^3.0.0",
"@follow/shared": "workspace:*",
"@mozilla/readability": "^0.5.0",
"@sentry/electron": "5.4.0",
"@yornaath/batshit": "0.10.1",
"bufferutil": "4.0.8",
"builder-util-runtime": "9.2.5",
"dayjs": "1.11.13",
"dotenv": "16.4.5",
"electron-context-menu": "4.0.4",
"electron-log": "5.2.0",
"electron-squirrel-startup": "1.0.1",
"electron-updater": "^6.3.4",
"font-list": "1.5.1",
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "8.0.0",
"idb-keyval": "6.2.1",
"linkedom": "^0.18.4",
"lodash-es": "4.17.21",
"lowdb": "7.0.1",
"msedge-tts": "1.3.4",
"nanoid": "5.0.7",
"ofetch": "1.3.4",
"posthog-js": "1.161.5",
"posthog-node": "4.2.0",
"semver": "7.6.3",
"tldts": "6.1.46",
"vscode-languagedetection": "npm:@vscode/vscode-languagedetection@^1.0.22",
"zod": "3.23.8"
},
"devDependencies": {
"@babel/generator": "7.25.6",
"@hono/node-server": "1.13.0",
"@types/lodash-es": "4.17.12",
"@types/node": "^22.5.5",
"drizzle-orm": "0.33.0",
"electron": "32.1.0",
"electron-devtools-installer": "3.2.0",
"hono": "4.6.2",
"rimraf": "6.0.1",
"tsx": "4.19.1",
"typescript": "^5.6.2"
}
}

13
apps/main/src/@types/i18next.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import type { defaultNS, ns } from "./constants"
import type { resources } from "./resources"
declare module "i18next" {
interface CustomTypeOptions {
ns: typeof ns
resources: (typeof resources)["en"]
defaultNS: typeof defaultNS
// if you see an error like: "Argument of type 'DefaultTFuncReturn' is not assignable to parameter of type xyz"
// set returnNull to false (and also in the i18next init options)
// returnNull: false;
}
}

View File

@ -1,10 +1,13 @@
import { electronApp, optimizer } from "@electron-toolkit/utils"
import { APP_PROTOCOL, DEEPLINK_SCHEME } from "@shared/constants"
import { extractElectronWindowOptions } from "@shared/electron"
// @ts-ignore
import { APP_PROTOCOL, DEEPLINK_SCHEME } from "@follow/shared/constants"
// @ts-ignore
import { extractElectronWindowOptions } from "@follow/shared/electron"
// @ts-ignore
import { env } from "@follow/shared/env"
import { app, BrowserWindow, session } from "electron"
import squirrelStartup from "electron-squirrel-startup"
import { env } from "../env"
import { isDev, isMacOS } from "./env"
import { initializeApp } from "./init"
import { setAuthSessionToken } from "./lib/user"

View File

@ -1,7 +1,7 @@
import path from "node:path"
import { registerIpcMain } from "@egoist/tipc/main"
import { APP_PROTOCOL } from "@shared/constants"
import { APP_PROTOCOL } from "@follow/shared/constants"
import { app, nativeTheme, shell } from "electron"
import contextMenu from "electron-context-menu"

View File

@ -1,9 +1,10 @@
import { env } from "@env"
import { env } from "@follow/shared/env"
import PKG from "@pkg"
import { hc } from "hono/client"
import { ofetch } from "ofetch"
import type { AppType } from "../../hono"
// @ts-ignore
import type { AppType } from "../../../../packages/shared/src/hono"
import { logger } from "../logger"
import { getAuthSessionToken } from "./user"

View File

@ -1,13 +1,12 @@
import i18next from "i18next"
import { initReactI18next } from "react-i18next"
import { resources } from "../@types/resources"
export const defaultNS = "native"
export const i18n = i18next.createInstance()
export const i18n = i18next.createInstance() as typeof i18next
i18n.use(initReactI18next).init({
i18n.init({
fallbackLng: "en",
defaultNS,
resources,

View File

@ -1,4 +1,4 @@
import { env } from "@env"
import { env } from "@follow/shared/env"
import { PostHog } from "posthog-node"
import { getUser } from "./user"

View File

@ -3,10 +3,14 @@ import { resolve } from "node:path"
import { app } from "electron"
import { JSONFileSyncPreset } from "lowdb/node"
export const db = JSONFileSyncPreset(resolve(app.getPath("userData"), "db.json"), {})
const db = JSONFileSyncPreset(resolve(app.getPath("userData"), "db.json"), {}) as {
data: Record<string, unknown>
write: () => void
read: () => void
}
export const store = {
get: (key: string) => db.data[key],
get: (key: string) => db.data[key] as any,
set: (key: string, value: any) => {
db.data[key] = value
db.write()

View File

@ -1,5 +1,5 @@
import { dispatchEventOnWindow } from "@follow/shared/event"
import { name } from "@pkg"
import { dispatchEventOnWindow } from "@shared/event"
import type { BrowserWindow, MenuItem, MenuItemConstructorOptions } from "electron"
import { Menu } from "electron"

View File

@ -1,12 +1,13 @@
import path from "node:path"
import { getRendererHandlers } from "@egoist/tipc/main"
import { callGlobalContextMethod } from "@shared/bridge"
import { callGlobalContextMethod } from "@follow/shared/bridge"
import type { BrowserWindow } from "electron"
import { app, clipboard, dialog, screen } from "electron"
import { registerMenuAndContextMenu } from "~/init"
import { isWindows11 } from "../env"
import { registerMenuAndContextMenu } from "../init"
import { downloadFile } from "../lib/download"
import { i18n } from "../lib/i18n"
import { cleanAuthSessionToken, cleanUser } from "../lib/user"

View File

@ -15,10 +15,10 @@ export const trackerRoute = {
setUser(user)
posthog?.capture({
event: "login",
distinctId: user.id,
distinctId: user.id ?? "",
properties: {
name: user.name,
handle: user.handle,
handle: user.handle ?? "",
},
})
}),

View File

@ -2,8 +2,8 @@ import path from "node:path"
import { fileURLToPath } from "node:url"
import { is } from "@electron-toolkit/utils"
import { callGlobalContextMethod } from "@shared/bridge"
import { imageRefererMatches } from "@shared/image"
import { callGlobalContextMethod } from "@follow/shared/bridge"
import { imageRefererMatches } from "@follow/shared/image"
import type { BrowserWindowConstructorOptions } from "electron"
import { BrowserWindow, Menu, shell } from "electron"

17
apps/main/tsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
"compilerOptions": {
"outDir": "dist",
"types": ["electron-vite/node"],
"moduleResolution": "Bundler",
"noImplicitReturns": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"baseUrl": ".",
"paths": {
"@pkg": ["../../package.json"],
"@locales/*": ["../../locales/*"],
"~/*": ["./src/*"]
}
}
}

12
apps/main/tsup.config.ts Normal file
View File

@ -0,0 +1,12 @@
import { defineConfig } from "tsup"
export default defineConfig(() => ({
entry: ["./export.ts"],
outDir: "dist",
format: "esm",
clean: true,
dts: true,
splitting: false,
sourcemap: true,
skipNodeModulesBundle: true,
}))

12
apps/renderer/global.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
import "../../types/authjs"
import type { ElectronAPI } from "@electron-toolkit/preload"
declare global {
interface Window {
electron?: ElectronAPI
api?: { canWindowBlur: boolean }
platform: NodeJS.Platform
}
}
export {}

128
apps/renderer/package.json Normal file
View File

@ -0,0 +1,128 @@
{
"name": "@follow/web",
"type": "module",
"private": true,
"main": "./dist/main/index.js",
"scripts": {
"test": "vitest",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@egjs/react-infinitegrid": "4.12.0",
"@egoist/tipc": "0.3.2",
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^3.0.0",
"@follow/electron-main": "workspace:*",
"@follow/shared": "workspace:*",
"@fontsource/sn-pro": "5.1.0",
"@hono/auth-js": "1.0.10",
"@hookform/resolvers": "3.9.0",
"@iconify/tools": "4.0.6",
"@microflash/remark-callout-directives": "4.3.1",
"@mozilla/readability": "^0.5.0",
"@radix-ui/react-alert-dialog": "1.1.1",
"@radix-ui/react-avatar": "1.1.0",
"@radix-ui/react-checkbox": "1.1.1",
"@radix-ui/react-context-menu": "2.2.1",
"@radix-ui/react-dialog": "1.1.1",
"@radix-ui/react-dropdown-menu": "2.1.1",
"@radix-ui/react-hover-card": "1.1.1",
"@radix-ui/react-label": "2.1.0",
"@radix-ui/react-popover": "1.1.1",
"@radix-ui/react-radio-group": "1.2.0",
"@radix-ui/react-scroll-area": "1.1.0",
"@radix-ui/react-select": "2.1.1",
"@radix-ui/react-slider": "^1.2.0",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-switch": "1.1.0",
"@radix-ui/react-tabs": "1.1.0",
"@radix-ui/react-toast": "1.2.1",
"@radix-ui/react-tooltip": "1.1.2",
"@sentry/react": "8.30.0",
"@sentry/vite-plugin": "2.22.4",
"@shikijs/transformers": "1.17.7",
"@t3-oss/env-core": "^0.11.1",
"@tanstack/query-sync-storage-persister": "5.56.2",
"@tanstack/react-query": "5.56.2",
"@tanstack/react-query-devtools": "5.56.2",
"@tanstack/react-query-persist-client": "5.56.2",
"@use-gesture/react": "10.3.1",
"@yornaath/batshit": "0.10.1",
"bufferutil": "4.0.8",
"builder-util-runtime": "9.2.5",
"class-variance-authority": "0.7.0",
"click-to-react-component": "1.1.0",
"clsx": "2.1.1",
"cmdk": "1.0.0",
"dayjs": "1.11.13",
"dexie": "4.0.8",
"dnum": "^2.13.1",
"dotenv": "16.4.5",
"electron-log": "5.2.0",
"foxact": "0.2.38",
"framer-motion": "11.5.4",
"franc-min": "6.2.0",
"fuse.js": "7.0.0",
"hast-util-to-jsx-runtime": "2.3.0",
"hast-util-to-text": "4.0.2",
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "8.0.0",
"idb-keyval": "6.2.1",
"immer": "10.1.1",
"jotai": "2.9.3",
"lethargy": "1.0.9",
"linkedom": "^0.18.4",
"lodash-es": "4.17.21",
"msedge-tts": "1.3.4",
"nanoid": "5.0.7",
"ofetch": "1.3.4",
"path-to-regexp": "8.1.0",
"posthog-js": "1.161.5",
"re-resizable": "6.9.18",
"react-error-boundary": "4.0.13",
"react-fast-marquee": "1.6.5",
"react-hook-form": "7.53.0",
"react-hotkeys-hook": "4.5.1",
"react-i18next": "^15.0.2",
"react-intersection-observer": "9.13.1",
"react-resizable-layout": "npm:@innei/react-resizable-layout@0.7.3-fork.1",
"react-router-dom": "6.26.2",
"react-selecto": "1.26.3",
"react-shadow": "20.5.0",
"react-virtuoso": "4.10.4",
"rehype-infer-description-meta": "2.0.0",
"rehype-parse": "9.0.0",
"rehype-sanitize": "6.0.0",
"rehype-stringify": "10.0.0",
"remark-directive": "3.0.0",
"remark-gfm": "4.0.0",
"remark-gh-alerts": "0.0.3",
"remark-parse": "11.0.0",
"remark-rehype": "11.1.0",
"semver": "7.6.3",
"shiki": "1.17.7",
"sonner": "^1.5.0",
"swiper": "11.1.14",
"tailwind-merge": "2.5.2",
"tldts": "6.1.46",
"unified": "11.0.5",
"use-context-selector": "2.0.0",
"usehooks-ts": "3.1.0",
"utf-8-validate": "6.0.4",
"vfile": "6.0.3",
"zod": "3.23.8",
"zustand": "4.5.5"
},
"devDependencies": {
"@babel/generator": "7.25.6",
"@hono/node-server": "1.13.0",
"@types/lodash-es": "4.17.12",
"@types/node": "^22.5.5",
"@types/react": "^18.3.7",
"@types/react-dom": "^18.3.0",
"fake-indexeddb": "6.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.6.2"
}
}

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,56 @@
import en from "../../../../locales/app/en.json"
import common_ardz from "../../../../locales/common/ar-DZ.json"
import common_arma from "../../../../locales/common/ar-MA.json"
import common_arsa from "../../../../locales/common/ar-SA.json"
import common_en from "../../../../locales/common/en.json"
import common_es from "../../../../locales/common/es.json"
import common_fr from "../../../../locales/common/fr.json"
import common_ja from "../../../../locales/common/ja.json"
import common_pt from "../../../../locales/common/pt.json"
import common_zhCN from "../../../../locales/common/zh-CN.json"
import common_zhTW from "../../../../locales/common/zh-TW.json"
import external_en from "../../../../locales/external/en.json"
import lang_ardz from "../../../../locales/lang/ar-DZ.json"
import lang_arma from "../../../../locales/lang/ar-MA.json"
import lang_arsa from "../../../../locales/lang/ar-SA.json"
import lang_en from "../../../../locales/lang/en.json"
import lang_es from "../../../../locales/lang/es.json"
import lang_fr from "../../../../locales/lang/fr.json"
import lang_ja from "../../../../locales/lang/ja.json"
import lang_pt from "../../../../locales/lang/pt.json"
import lang_zhCN from "../../../../locales/lang/zh-CN.json"
import lang_zhTW from "../../../../locales/lang/zh-TW.json"
import settings_en from "../../../../locales/settings/en.json"
import shortcuts_en from "../../../../locales/shortcuts/en.json"
/**
* This file is the language resource that is loaded in full when the app is initialized.
* When switching languages, the app will automatically download the required language resources,
* we will not load all the language resources to minimize the first screen loading time of the app.
* Generally, we only load english resources synchronously by default.
* In addition, we attach common resources for other languages, and the size of the common resources must be controlled.
*/
export const defaultResources = {
en: {
app: en,
lang: lang_en,
common: common_en,
external: external_en,
settings: settings_en,
shortcuts: shortcuts_en,
},
"zh-CN": {
lang: lang_zhCN,
common: common_zhCN,
},
ja: {
lang: lang_ja,
common: common_ja,
},
"ar-DZ": { lang: lang_ardz, common: common_ardz },
"ar-SA": { lang: lang_arsa, common: common_arsa },
"ar-MA": { lang: lang_arma, common: common_arma },
"zh-TW": { lang: lang_zhTW, common: common_zhTW },
es: { lang: lang_es, common: common_es },
fr: { lang: lang_fr, common: common_fr },
pt: { lang: lang_pt, common: common_pt },
}

View File

@ -1,5 +1,5 @@
import type { defaultNS, ns } from "./constants"
import type { resources } from "./resources"
import type { defaultResources as resources } from "./default-resource"
declare module "i18next" {
interface CustomTypeOptions {

View File

@ -1,8 +1,8 @@
import { env } from "@follow/shared/env"
import { queryClient } from "@renderer/lib/query-client"
import { useEffect, useLayoutEffect } from "react"
import { Outlet } from "react-router-dom"
import { env } from "../../env.js"
import { useAppIsReady } from "./atoms/app"
import { useUISettingKey } from "./atoms/settings/ui"
import { applyAfterReadyCallbacks } from "./initialize/queue.js"

View File

@ -1,5 +1,5 @@
import type { GeneralSettings } from "@follow/shared/interface/settings"
import { jotaiStore } from "@renderer/lib/jotai"
import type { GeneralSettings } from "@shared/interface/settings"
import { createSettingAtom } from "./helper"

View File

@ -1,4 +1,4 @@
import type { IntegrationSettings } from "@shared/interface/settings"
import type { IntegrationSettings } from "@follow/shared/interface/settings"
import { createSettingAtom } from "./helper"

View File

@ -1,4 +1,4 @@
import type { UISettings } from "@shared/interface/settings"
import type { UISettings } from "@follow/shared/interface/settings"
import { createSettingAtom } from "./helper"

Some files were not shown because too many files have changed in this diff Show More