diff --git a/.gitignore b/.gitignore index dd10ebc50..559e12e40 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ app-update.yml yarn-error.log /test .vscode/settings.json -src/main/lang/index.ts /data package-lock.json +src/helper-go/rsrc_windows_amd64.syso +src/helper-go/rsrc_windows_386.syso +src/helper-go/go.sum diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..3729de5ac --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,322 @@ +# FlyEnv - AI Agent Development Guide + +## Project Overview + +**FlyEnv** is an All-In-One Full-Stack Environment Management Tool built with Electron and Vue 3. It provides a lightweight, modular development environment manager for Windows, macOS, and Linux, allowing developers to install and manage Apache, PHP, Node.js, Python, databases, and more—running natively without Docker. + +- **Version**: 4.13.2 +- **Electron Version**: 35.7.5 +- **License**: MIT +- **Author**: Pengfei Xu +- **Repository**: https://github.com/xpf0000/FlyEnv + +## Technology Stack + +### Core Technologies +- **Frontend Framework**: Vue 3 (Composition API) +- **Desktop Framework**: Electron 35.7.5 +- **Build Tool**: Vite 6.x + esbuild 0.25.x +- **Language**: TypeScript 5.8.x +- **State Management**: Pinia 3.x +- **UI Component Library**: Element Plus 2.11.x +- **Styling**: Tailwind CSS 3.4.x + SCSS +- **Internationalization**: Vue I18n 11.x + +### Additional Libraries +- **Code Editor**: Monaco Editor +- **Terminal**: node-pty + xterm.js +- **HTTP Client**: Axios +- **Process Management**: child_process, node-pty +- **File Operations**: fs-extra +- **Markdown Processing**: markdown-it + Shiki + +## Project Architecture + +FlyEnv follows Electron's multi-process architecture with three main components: + +### 1. Main Process (`/src/main`) +Acts as a command relay station between the renderer and fork processes. + +**Key Files**: +- `index.ts` - Entry point, initializes Launcher +- `Launcher.ts` - Application bootstrap, single-instance lock, event handling +- `Application.ts` - Main application controller, window management, IPC handling + +**Core Managers**: +- `WindowManager` - Browser window creation and management +- `TrayManager` - System tray functionality +- `MenuManager` - Application menus +- `ConfigManager` - Configuration persistence +- `ForkManager` - Forked process management +- `NodePTY` - Terminal PTY handling + +### 2. Forked Asynchronous Process (`/src/fork`) +Executes all heavy commands asynchronously to prevent main thread blocking. + +**Key Files**: +- `index.ts` - Fork process entry point +- `BaseManager.ts` - Command dispatcher +- `module/Base/index.ts` - Base class for all service modules + +**Module Structure** (`/src/fork/module/`): +Each service has its own module (e.g., `Nginx/`, `Php/`, `Mysql/`). A typical module extends the `Base` class and implements: +- `_startServer(version)` - Start the service +- `_stopService(version)` - Stop the service +- `fetchAllOnlineVersion()` - Fetch available versions +- `installSoft()` - Download and install + +### 3. Renderer Process (`/src/render`) +Vue 3-based UI application. + +**Key Files**: +- `main.ts` - Renderer entry point +- `App.vue` - Root component +- `core/type.ts` - Module type definitions + +**Directory Structure**: +- `components/` - Vue components organized by module +- `store/` - Pinia stores +- `util/` - Utility functions +- `style/` - Global styles (SCSS) + +~~### 4. Helper Process (`/src/helper`) +Background helper process for privileged operations.~~ + +Deprecated. Only the Go version is used. + +### 5. Go Helper (`/src/helper-go/`) +Go-based helper binary for platform-specific operations (Windows admin tasks). + +### 6. Shared Code (`/src/shared`) +Shared utilities between all processes: +- `ForkPromise.ts` - Promise with progress callbacks +- `Process.ts` - Process management utilities +- `child-process.ts` - Child process helpers +- `utils.ts` - Platform detection utilities + +### 7. Internationalization (`/src/lang`) +Supports 25+ languages with JSON-based translation files. + +**Supported Languages**: +Arabic, Azerbaijani, Bengali, Czech, Danish, German, Greek, English, Spanish, Finnish, French, Indonesian, Italian, Japanese, Dutch, Norwegian, Polish, Portuguese, Portuguese (Brazil), Romanian, Russian, Swedish, Turkish, Ukrainian, Vietnamese, Chinese. + +## Build System + +### Build Scripts (package.json) + +```bash +# Development +yarn dev # Start development server with hot reload +yarn clean:dev # Clean dist folder +yarn build-dev-runner # Build dev runner script + +# Production Build +yarn build # Build for production (platform-specific) +yarn clean # Clean node-pty build +yarn postinstall # Install Electron app dependencies +``` + +### Build Process Flow + +1. **Development** (`yarn dev`): + - Cleans dist folder + - Builds dev-runner.ts → `electron/dev-runner.mjs` + - Starts Vite dev server + - Watches main/ and fork/ directories for changes + - Restarts Electron on file changes + +2. **Production** (`yarn build`): + - Cleans dist folder and node-pty build + - Builds main process with esbuild + - Builds fork process with esbuild + - Builds renderer with Vite + - Packages with electron-builder + +### esbuild Configuration (`/configs/esbuild.config.ts`) + +| Target | Entry | Output | Minify | +|--------|-------|--------|--------| +| dev | src/main/index.dev.ts | dist/electron/main.mjs | false | +| dist | src/main/index.ts | dist/electron/main.mjs | true | +| devFork | src/fork/index.ts | dist/electron/fork.mjs | false | +| distFork | src/fork/index.ts | dist/electron/fork.mjs | true | +| devHelper | src/helper/index.ts | dist/helper/helper.js | true | +| distHelper | src/helper/index.ts | dist/helper/helper.js | true | + +### Vite Configuration (`/configs/vite.config.ts`) + +**Entry Points**: +- `main` - Main application window +- `tray` - Tray popup window +- `capturer` - Screen capture window + +**Path Aliases**: +- `@` → `src/render/` +- `@shared` → `src/shared/` +- `@lang` → `src/lang/` + +## Code Style Guidelines + +### ESLint Configuration +- Uses flat config format (`eslint.config.mjs`) +- TypeScript ESLint recommended rules +- Vue 3 recommended rules +- Prettier integration for formatting + +### Key Rules +- `@typescript-eslint/no-explicit-any`: error (disabled in practice) +- `vue/multi-word-component-names`: off +- `vue/block-lang`: requires TypeScript in Vue SFCs +- `prettier/prettier`: error (formatting issues treated as errors) + +### Styling +- **Primary**: Tailwind CSS for utility-first styling +- **Secondary**: SCSS for complex styles +- **Dark Mode**: CSS selector-based (`darkMode: 'selector'`) + +### Code Conventions +- Use TypeScript for all new code +- Vue SFCs must use ` + diff --git a/src/render/components/CloudflareTunnel/Logs.vue b/src/render/components/CloudflareTunnel/Logs.vue new file mode 100644 index 000000000..dbfcccb96 --- /dev/null +++ b/src/render/components/CloudflareTunnel/Logs.vue @@ -0,0 +1,54 @@ + + + + + + + {{ I18nT('base.log') }} + + + + + + + + + + diff --git a/src/render/components/CloudflareTunnel/add.vue b/src/render/components/CloudflareTunnel/add.vue index 17379c94a..61351529e 100644 --- a/src/render/components/CloudflareTunnel/add.vue +++ b/src/render/components/CloudflareTunnel/add.vue @@ -4,6 +4,8 @@ :title="'Cloudflare Tunnel' + ' ' + I18nT('base.add')" class="el-dialog-content-flex-1 h-[75%] dark:bg-[#1d2033]" width="600px" + :close-on-click-modal="false" + :close-on-press-escape="false" @closed="closedFn" > @@ -51,10 +53,14 @@ @@ -73,6 +79,7 @@ import { reactiveBind, uuid } from '@/util/Index' import CloudflareTunnelStore from '@/core/CloudflareTunnel/CloudflareTunnelStore' import { BrewStore } from '@/store/brew' + import { MessageError } from '@/util/Element' const brewStore = BrewStore() @@ -80,6 +87,8 @@ const formRef = ref() + const loading = ref(false) + const zones = ref([]) const form = ref({ @@ -189,7 +198,11 @@ return '' } const domain = `${form.value.subdomain}.${form.value.zoneName}` - const all = CloudflareTunnelStore.items.map((item) => `${item.subdomain}.${item.zoneName}`) + const all = CloudflareTunnelStore.items + .map((item) => { + return item.dns.map((d) => `${d.subdomain}.${d.zoneName}`) + }) + .flat() if (all.includes(domain)) { return I18nT('host.CloudflareTunnel.OnlineDomainExistsTips') } @@ -201,11 +214,39 @@ } const doSubmit = async () => { - const item = reactiveBind(new CloudflareTunnel(form.value)) + if (loading.value) { + return + } + loading.value = true + const obj: any = { + apiToken: form.value.apiToken, + cloudflaredBin: form.value.cloudflaredBin, + accountId: form.value.accountId, + + dns: [ + { + id: uuid(), + subdomain: form.value.subdomain, + localService: form.value.localService, + zoneId: form.value.zoneId, + zoneName: form.value.zoneName + } + ] + } + const item = reactiveBind(new CloudflareTunnel(obj)) item.id = uuid() - CloudflareTunnelStore.items.unshift(item) - CloudflareTunnelStore.save() - onCancel() + item + .fetchTunnel() + .then(() => { + CloudflareTunnelStore.items.unshift(item) + CloudflareTunnelStore.save() + loading.value = false + onCancel() + }) + .catch((error) => { + MessageError(I18nT('host.CloudflareTunnel.TunnelInitFailTips', { error })) + loading.value = false + }) } defineExpose({ diff --git a/src/render/components/CloudflareTunnel/addDNS.vue b/src/render/components/CloudflareTunnel/addDNS.vue new file mode 100644 index 000000000..eedde1a11 --- /dev/null +++ b/src/render/components/CloudflareTunnel/addDNS.vue @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + + + + + + + + + + :// + + + + + + + + + + + + diff --git a/src/render/components/CloudflareTunnel/edit.vue b/src/render/components/CloudflareTunnel/edit.vue index c6c98e449..b6b86de85 100644 --- a/src/render/components/CloudflareTunnel/edit.vue +++ b/src/render/components/CloudflareTunnel/edit.vue @@ -2,12 +2,16 @@ + + + + @@ -15,38 +19,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - . - - - - - - - @@ -61,14 +33,9 @@ diff --git a/src/render/components/CloudflareTunnel/setup.ts b/src/render/components/CloudflareTunnel/setup.ts index 9aa1050da..87f04944d 100644 --- a/src/render/components/CloudflareTunnel/setup.ts +++ b/src/render/components/CloudflareTunnel/setup.ts @@ -3,7 +3,12 @@ import { computed, reactive } from 'vue' import CloudflareTunnelStore from '@/core/CloudflareTunnel/CloudflareTunnelStore' import { AppStore } from '@/store/app' import { AsyncComponentShow } from '@/util/AsyncComponent' -import type { ZoneType } from '@/core/CloudflareTunnel/type' +import { CloudflareTunnelDnsRecord, ZoneType } from '@/core/CloudflareTunnel/type' +import Base from '@/core/Base' +import { I18nT } from '@lang/index' +import { clipboard, shell } from '@/util/NodeFn' +import { MessageError, MessageSuccess } from '@/util/Element' +import { SetupStore } from '@/components/Setup/store' export const ZoneDict: Record = reactive({}) @@ -20,6 +25,10 @@ export const Setup = () => { }) function add() { + if (isLocked.value) { + MessageError(I18nT('host.CloudflareTunnel.licenseTips')) + return + } AsyncComponentShow(AddVM).then() } @@ -30,17 +39,40 @@ export const Setup = () => { function edit(item: CloudflareTunnel) { AsyncComponentShow(EditVM, { - item + item: JSON.parse(JSON.stringify(item)) }).then() } - function info(item: CloudflareTunnel) {} + let LogVM: any + import('./Logs.vue').then((res) => { + LogVM = res.default + }) - function del(item: CloudflareTunnel, index: number) {} + function log(item: CloudflareTunnel) { + AsyncComponentShow(LogVM, { + item: JSON.parse(JSON.stringify(item)) + }).then() + } - const openOutUrl = (item: CloudflareTunnel) => {} + function del(item: CloudflareTunnel, index: number) { + Base._Confirm(I18nT('base.areYouSure'), undefined, { + customClass: 'confirm-del', + type: 'warning' + }).then(() => { + CloudflareTunnelStore.items.splice(index, 1) + CloudflareTunnelStore.save() + }) + } + + const openOutUrl = (item: CloudflareTunnelDnsRecord) => { + const url = `http://${item.subdomain}.${item.zoneName}` + shell.openExternal(url).catch() + } - const openLocalUrl = (item: CloudflareTunnel) => {} + const openLocalUrl = (item: CloudflareTunnelDnsRecord) => { + const url = `${item?.protocol || 'http'}://${item.localService}` + shell.openExternal(url).catch() + } const groupTrunOn = (item: CloudflareTunnel) => { const dict = JSON.parse(JSON.stringify(appStore.phpGroupStart)) @@ -55,14 +87,75 @@ export const Setup = () => { appStore.saveConfig().then().catch() } + const copy = (str: string) => { + clipboard.writeText(str).then(() => { + MessageSuccess(I18nT('base.copySuccess')) + }) + } + + let EditDNSVM: any + import('./editDNS.vue').then((res) => { + EditDNSVM = res.default + }) + + const editDNS = (item: CloudflareTunnel, dns: CloudflareTunnelDnsRecord, index: number) => { + AsyncComponentShow(EditDNSVM, { + item: JSON.parse(JSON.stringify(item)), + dns: JSON.parse(JSON.stringify(dns)), + index + }).then() + } + + const delDNS = (item: CloudflareTunnel, dns: CloudflareTunnelDnsRecord, index: number) => { + Base._Confirm(I18nT('base.areYouSure'), undefined, { + customClass: 'confirm-del', + type: 'warning' + }).then(() => { + const find = CloudflareTunnelStore.items.find((i) => i.id === item.id) + if (find) { + find.dns.splice(index, 1) + } + CloudflareTunnelStore.save() + }) + } + + let AddDNSVM: any + import('./addDNS.vue').then((res) => { + AddDNSVM = res.default + }) + + function addDNS(item: CloudflareTunnel) { + if (isLocked.value && item.dns.length > 0) { + MessageError(I18nT('host.CloudflareTunnel.licenseTips')) + return + } + AsyncComponentShow(AddDNSVM, { + item: JSON.parse(JSON.stringify(item)) + }).then() + } + + const setupStore = SetupStore() + const isLocked = computed(() => { + if (setupStore.isActive) { + return false + } + + return CloudflareTunnelStore.items.length > 0 + }) + return { add, edit, - info, del, list, openOutUrl, openLocalUrl, - groupTrunOn + groupTrunOn, + copy, + editDNS, + delDNS, + addDNS, + log, + isLocked } } diff --git a/src/render/components/GoLang/CreateProject.vue b/src/render/components/GoLang/CreateProject.vue new file mode 100644 index 000000000..09440aa8e --- /dev/null +++ b/src/render/components/GoLang/CreateProject.vue @@ -0,0 +1,9 @@ + + + + + + + diff --git a/src/render/components/GoLang/Index.vue b/src/render/components/GoLang/Index.vue index 4f8b00bcc..0877c496f 100644 --- a/src/render/components/GoLang/Index.vue +++ b/src/render/components/GoLang/Index.vue @@ -19,7 +19,8 @@ url="https://go.dev/dl/" :has-static="true" > - + + @@ -38,7 +39,13 @@ import { I18nT } from '@lang/index' import ProjectIndex from '@/components/LanguageProjects/index.vue' import { Project } from '@/util/Project' + import ProjectCreateVM from './CreateProject.vue' const { tab } = AppModuleSetup('golang') - const tabs = [I18nT('base.service'), I18nT('base.versionManager'), I18nT('host.projectGo')] + const tabs = [ + I18nT('base.service'), + I18nT('base.versionManager'), + I18nT('host.newProject'), + I18nT('host.projectGo') + ] diff --git a/src/render/components/Host/CreateProject/go/create.vue b/src/render/components/Host/CreateProject/go/create.vue new file mode 100644 index 000000000..ea36e0cf7 --- /dev/null +++ b/src/render/components/Host/CreateProject/go/create.vue @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + Go {{ I18nT('base.version') }} + + + + + + + + + + + {{ I18nT('host.frameworkVersion') }} + + + + + + + + + + + + + + + + + diff --git a/src/render/components/Host/CreateProject/go/create.win.vue b/src/render/components/Host/CreateProject/go/create.win.vue new file mode 100644 index 000000000..a4044617d --- /dev/null +++ b/src/render/components/Host/CreateProject/go/create.win.vue @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + + + + + Go {{ I18nT('base.version') }} + + + + + + + + + + + {{ I18nT('host.frameworkVersion') }} + + + + + + + + + + + + + + + + + diff --git a/src/render/components/Host/CreateProject/go/index.vue b/src/render/components/Host/CreateProject/go/index.vue new file mode 100644 index 000000000..5535ca2c5 --- /dev/null +++ b/src/render/components/Host/CreateProject/go/index.vue @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + {{ p.name }} + + + + + + + + + + + + + + diff --git a/src/render/components/Host/CreateProject/go/version.ts b/src/render/components/Host/CreateProject/go/version.ts new file mode 100644 index 000000000..15bfbd3dc --- /dev/null +++ b/src/render/components/Host/CreateProject/go/version.ts @@ -0,0 +1,133 @@ +const goVersion = { + 'Go (Standard)': { + url: 'https://golang.org/', + list: [ + { + name: 'latest', + version: '*', + command: 'go mod init my-module; echo "package main\n\nfunc main() {}" > main.go', + commandWin: + 'go mod init my-module; "package main`n`nfunc main() {}" | Out-File -Encoding ascii main.go' + } + ] + }, + Gin: { + url: 'https://gin-gonic.com/', + list: [ + { + name: 'latest', + version: '*', + command: + 'go mod init myapp; go get github.com/gin-gonic/gin; printf "package main\nimport \"github.com/gin-gonic/gin\"\nfunc main() {\n r := gin.Default()\n r.GET(\"/ping\", func(c *gin.Context) { c.JSON(200, gin.H{\"message\": \"pong\"}) })\n r.Run()\n}" > main.go', + commandWin: + 'go mod init myapp; go get github.com/gin-gonic/gin; "package main`nimport `"github.com/gin-gonic/gin`"`nfunc main() {`n r := gin.Default()`n r.GET(`"/ping`签署, func(c *gin.Context) { c.JSON(200, gin.H{`"message`签署: `"pong`签署}) })`n r.Run()`n}" | Out-File -Encoding ascii main.go' + } + ] + }, + Echo: { + url: 'https://echo.labstack.com/', + list: [ + { + name: 'latest', + version: '*', + command: + 'go mod init myapp; go get github.com/labstack/echo/v4; printf "package main\nimport (\n\t\"github.com/labstack/echo/v4\"\n\t\"net/http\"\n)\nfunc main() {\n\te := echo.New()\n\te.GET(\"/\", func(c echo.Context) error {\n\t\treturn c.String(http.StatusOK, \"Hello, World!\")\n\t})\n\te.Logger.Fatal(e.Start(\":1323\"))\n}" > main.go', + commandWin: + 'go mod init myapp; go get github.com/labstack/echo/v4; "package main`nimport (`n`t`"github.com/labstack/echo/v4`"`n`t`"net/http`"`n)`nfunc main() {`n`te := echo.New()`n`te.GET(`"/`签署, func(c echo.Context) error {`n`t`treturn c.String(http.StatusOK, `"Hello, World!`签署)`n`t})`n`te.Logger.Fatal(e.Start(`":1323`签署))`n}" | Out-File -Encoding ascii main.go' + } + ] + }, + Fiber: { + url: 'https://gofiber.io/', + list: [ + { + name: 'latest', + version: '*', + command: + 'go mod init myapp; go get github.com/gofiber/fiber/v2; printf "package main\nimport \"github.com/gofiber/fiber/v2\"\nfunc main() {\n\tapp := fiber.New()\n\tapp.Get(\"/\", func(c *fiber.Ctx) error {\n\t\treturn c.SendString(\"Hello, World!\")\n\t})\n\tapp.Listen(\":3000\")\n}" > main.go', + commandWin: + 'go mod init myapp; go get github.com/gofiber/fiber/v2; "package main`nimport `"github.com/gofiber/fiber/v2`"`nfunc main() {`n`tapp := fiber.New()`n`tapp.Get(`"/`签署, func(c *fiber.Ctx) error {`n`t`treturn c.SendString(`"Hello, World!`签署)`n`t})`n`tapp.Listen(`":3000`签署)`n}" | Out-File -Encoding ascii main.go' + } + ] + }, + Iris: { + url: 'https://www.iris-go.com/', + list: [ + { + name: 'latest', + version: '*', + command: + 'go mod init myapp; go get github.com/kataras/iris/v12; printf "package main\nimport \"github.com/kataras/iris/v12\"\nfunc main() {\n\tapp := iris.New()\n\tapp.Get(\"/\", func(ctx iris.Context) {\n\t\tctx.WriteString(\"Hello, World!\")\n\t})\n\tapp.Listen(\":8080\")\n}" > main.go', + commandWin: + 'go mod init myapp; go get github.com/kataras/iris/v12; "package main`nimport `"github.com/kataras/iris/v12`"`nfunc main() {`n`tapp := iris.New()`n`tapp.Get(`"/`签署, func(ctx iris.Context) {`n`t`tctx.WriteString(`"Hello, World!`签署)`n`t})`n`tapp.Listen(`":8080`签署)`n}" | Out-File -Encoding ascii main.go' + } + ] + }, + GoFrame: { + url: 'https://goframe.org/', + list: [ + { + name: 'latest', + version: '*', + command: + 'go mod init myapp; go get github.com/gogf/gf/v2; printf "package main\nimport (\n\t\"github.com/gogf/gf/v2/frame/g\"\n\t\"github.com/gogf/gf/v2/net/ghttp\"\n)\nfunc main() {\n\ts := g.Server()\n\ts.BindHandler(\"/\", func(r *ghttp.Request) {\n\t\tr.Response.Write(\"Hello, World!\")\n\t})\n\ts.Run()\n}" > main.go', + commandWin: + 'go mod init myapp; go get github.com/gogf/gf/v2; "package main`nimport (`n`t`"github.com/gogf/gf/v2/frame/g`"`n`t`"github.com/gogf/gf/v2/net/ghttp`"`n)`nfunc main() {`n`ts := g.Server()`n`ts.BindHandler(`"/`签署, func(r *ghttp.Request) {`n`t`tr.Response.Write(`"Hello, World!`签署)`n`t})`n`ts.Run()`n}" | Out-File -Encoding ascii main.go' + } + ] + }, + 'Go-Zero': { + url: 'https://go-zero.dev/', + list: [ + { + name: 'latest', + version: '*', + command: + 'go install github.com/zeromicro/go-zero/tools/goctl@latest; $(go env GOPATH)/bin/goctl api new demo', + commandWin: + '$gopath = go env GOPATH; go install github.com/zeromicro/go-zero/tools/goctl@latest; & "$gopath\\bin\\goctl.exe" api new demo' + } + ] + }, + Buffalo: { + url: 'https://gobuffalo.io/', + list: [ + { + name: 'latest', + version: '*', + command: + 'go install github.com/gobuffalo/cli/cmd/buffalo@latest; $(go env GOPATH)/bin/buffalo new myapp', + commandWin: + '$gopath = go env GOPATH; go install github.com/gobuffalo/cli/cmd/buffalo@latest; & "$gopath\\bin\\buffalo.exe" new myapp' + } + ] + }, + 'Hugo (Static)': { + url: 'https://gohugo.io/', + list: [ + { + name: 'latest', + version: '*', + command: + 'go install github.com/gohugoio/hugo@latest; $(go env GOPATH)/bin/hugo new site my-hugo-site', + commandWin: + '$gopath = go env GOPATH; go install github.com/gohugoio/hugo@latest; & "$gopath\\bin\\hugo.exe" new site my-hugo-site' + } + ] + }, + 'Cobra (CLI)': { + url: 'https://github.com/spf13/cobra', + list: [ + { + name: 'latest', + version: '*', + command: + 'go install github.com/spf13/cobra-cli@latest; go mod init myapp; $(go env GOPATH)/bin/cobra-cli init', + commandWin: + '$gopath = go env GOPATH; go install github.com/spf13/cobra-cli@latest; go mod init myapp; & "$gopath\\bin\\cobra-cli.exe" init' + } + ] + } +} + +export default goVersion diff --git a/src/render/components/Host/CreateProject/new.vue b/src/render/components/Host/CreateProject/new.vue index 69ff9d9b1..0b27179de 100644 --- a/src/render/components/Host/CreateProject/new.vue +++ b/src/render/components/Host/CreateProject/new.vue @@ -15,6 +15,8 @@ + + @@ -22,7 +24,9 @@ - + + + @@ -34,6 +38,8 @@ import { I18nT } from '@lang/index' import { nextTick } from 'vue' import NodeJS from './nodejs.vue' + import PythonVM from './python/index.vue' + import GoVM from './go/index.vue' const { show, onClosed, onSubmit, closedFn, callback } = AsyncComponentSetup() diff --git a/src/render/components/Host/CreateProject/nodejs.vue b/src/render/components/Host/CreateProject/nodejs.vue index fbc4fe904..80552379a 100644 --- a/src/render/components/Host/CreateProject/nodejs.vue +++ b/src/render/components/Host/CreateProject/nodejs.vue @@ -31,7 +31,7 @@ { + const openURL = (e: MouseEvent, url: string) => { + e?.preventDefault?.() + e?.stopPropagation?.() shell.openExternal(url) } diff --git a/src/render/components/Host/CreateProject/nodejsCreate.win.vue b/src/render/components/Host/CreateProject/nodejsCreate.win.vue index 88e3c409f..3dca7809e 100644 --- a/src/render/components/Host/CreateProject/nodejsCreate.win.vue +++ b/src/render/components/Host/CreateProject/nodejsCreate.win.vue @@ -195,9 +195,6 @@ command.push( `$env:npm_config_cache="${join(window.Server.UserHome!, 'AppData/Local/npm-cache')}"` ) - command.push( - `$env:npm_config_cache="${join(window.Server.UserHome!, 'AppData/Local/npm-cache')}"` - ) } command.push(`cd "${form.dir}"`) const arr = item?.commandWin?.split(';') ?? item?.command?.split(';') ?? [] diff --git a/src/render/components/Host/CreateProject/php.vue b/src/render/components/Host/CreateProject/php.vue index e7a833d98..319b39ef2 100644 --- a/src/render/components/Host/CreateProject/php.vue +++ b/src/render/components/Host/CreateProject/php.vue @@ -31,7 +31,7 @@ { + const openURL = (e: MouseEvent, url: string) => { + e?.preventDefault?.() + e?.stopPropagation?.() shell.openExternal(url) } diff --git a/src/render/components/Host/CreateProject/project.ts b/src/render/components/Host/CreateProject/project.ts index 5ca5c6b3e..1473febd2 100644 --- a/src/render/components/Host/CreateProject/project.ts +++ b/src/render/components/Host/CreateProject/project.ts @@ -1,7 +1,7 @@ import { reactive } from 'vue' import XTerm from '@/util/XTerm' -export type ProjectTypes = 'PHP' | 'NodeJS' +export type ProjectTypes = 'PHP' | 'NodeJS' | 'Python' | 'Go' export type ProjectPHPForm = { dir: string @@ -23,6 +23,15 @@ export type ProjectNodeJSForm = { created: boolean } +export type ProjectForm = { + dir: string + bin: string + version: string | undefined + framework: string + running: boolean + created: boolean +} + export const ProjectSetup = reactive<{ tab: ProjectTypes collapse: Partial> @@ -31,6 +40,8 @@ export const ProjectSetup = reactive<{ form: { PHP: ProjectPHPForm NodeJS: ProjectNodeJSForm + Python: ProjectForm + Go: ProjectForm } execing: Partial> phpFormInit: () => void @@ -57,6 +68,22 @@ export const ProjectSetup = reactive<{ framework: '', running: false, created: false + }, + Python: { + dir: '', + bin: '', + version: undefined, + framework: '', + running: false, + created: false + }, + Go: { + dir: '', + bin: '', + version: undefined, + framework: '', + running: false, + created: false } }, phpFormInit() { diff --git a/src/render/components/Host/CreateProject/python/create.vue b/src/render/components/Host/CreateProject/python/create.vue new file mode 100644 index 000000000..8d5701a5c --- /dev/null +++ b/src/render/components/Host/CreateProject/python/create.vue @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + Python {{ I18nT('base.version') }} + + + + + + + + + + + {{ I18nT('host.frameworkVersion') }} + + + + + + + + + + + + + + + + + diff --git a/src/render/components/Host/CreateProject/python/create.win.vue b/src/render/components/Host/CreateProject/python/create.win.vue new file mode 100644 index 000000000..104ead358 --- /dev/null +++ b/src/render/components/Host/CreateProject/python/create.win.vue @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + Python {{ I18nT('base.version') }} + + + + + + + + + + + {{ I18nT('host.frameworkVersion') }} + + + + + + + + + + + + + + + + + diff --git a/src/render/components/Host/CreateProject/python/index.vue b/src/render/components/Host/CreateProject/python/index.vue new file mode 100644 index 000000000..884c8898e --- /dev/null +++ b/src/render/components/Host/CreateProject/python/index.vue @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + {{ p.name }} + + + + + + + + + + + + + + diff --git a/src/render/components/Host/CreateProject/python/version.ts b/src/render/components/Host/CreateProject/python/version.ts new file mode 100644 index 000000000..7bca3d4c5 --- /dev/null +++ b/src/render/components/Host/CreateProject/python/version.ts @@ -0,0 +1,158 @@ +const pythonVersion = { + FastAPI: { + url: 'https://fastapi.tiangolo.com/', + list: [ + { + name: 'latest', + version: '*', + // 推荐使用官方的 fast-api-template + command: + 'pip install fastapi; npx degit fastapi/full-stack-fastapi-template my-fastapi-app', + commandWin: + 'pip install fastapi; npx degit fastapi/full-stack-fastapi-template my-fastapi-app' + } + ] + }, + Django: { + url: 'https://www.djangoproject.com/', + list: [ + { + name: 'latest', + version: '*', + command: 'pip install django; django-admin startproject mysite', + commandWin: 'pip install django; django-admin startproject mysite' + } + ] + }, + Flask: { + url: 'https://flask.palletsprojects.com/', + list: [ + { + name: 'latest', + version: '*', + command: 'mkdir my-flask-app; cd my-flask-app; pip install Flask', + commandWin: 'mkdir my-flask-app; cd my-flask-app; pip install Flask' + } + ] + }, + Streamlit: { + url: 'https://streamlit.io/', + list: [ + { + name: 'latest', + version: '*', + command: + 'pip install streamlit; echo "import streamlit as st\nst.write(\'Hello World\')" > app.py; streamlit hello', + commandWin: + 'pip install streamlit; echo "import streamlit as st`nst.write(\'Hello World\')" > app.py; streamlit hello' + } + ] + }, + Masonite: { + url: 'https://docs.masoniteproject.com/', + list: [ + { + name: 'latest', + version: '*', + command: 'pip install masonite-cli; craft new project', + commandWin: 'pip install masonite-cli; craft new project' + } + ] + }, + 'uv (Modern Manager)': { + url: 'https://docs.astral.sh/uv/', + list: [ + { + name: 'latest', + version: '*', + command: 'curl -LsSf https://astral.sh/uv/install.sh | sh; uv init', + commandWin: + 'powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"; uv init' + } + ] + }, + 'Cookiecutter-Django': { + url: 'https://github.com/cookiecutter/cookiecutter-django', + list: [ + { + name: 'latest', + version: '*', + // 使用 cookiecutter 从主流模板创建,这会触发交互式问答界面 + command: + 'pip install cookiecutter; cookiecutter https://github.com/cookiecutter/cookiecutter-django', + commandWin: + 'pip install cookiecutter; cookiecutter https://github.com/cookiecutter/cookiecutter-django' + } + ] + }, + Wagtail: { + url: 'https://wagtail.org/', + list: [ + { + name: 'latest', + version: '*', + // Wagtail 是基于 Django 的高性能内容管理框架,自带交互式脚手架 + command: 'pip install wagtail; wagtail start mysite', + commandWin: 'pip install wagtail; wagtail start mysite' + } + ] + }, + // --- 以下是新增的框架和工具 --- + Sanic: { + url: 'https://sanicframework.org/', + list: [ + { + name: 'latest', + version: '*', + command: 'pip install sanic; sanic myapp', + commandWin: 'pip install sanic; sanic myapp' + } + ] + }, + 'Pynecone (Reflex)': { + url: 'https://reflex.dev/', + list: [ + { + name: 'latest', + version: '*', + command: 'pip install reflex; reflex init', + commandWin: 'pip install reflex; reflex init' + } + ] + }, + Litestar: { + url: 'https://litestar.dev/', + list: [ + { + name: 'latest', + version: '*', + command: 'pip install litestar; litestar create my-litestar-app', + commandWin: 'pip install litestar; litestar create my-litestar-app' + } + ] + }, + Mezzanine: { + url: 'https://github.com/stephenmcd/mezzanine', + list: [ + { + name: 'latest', + version: '*', + command: 'pip install mezzanine; mezzanine-project my-mezzanine-site', + commandWin: 'pip install mezzanine; mezzanine-project my-mezzanine-site' + } + ] + }, + PDM: { + url: 'https://pdm.fming.dev/', + list: [ + { + name: 'latest', + version: '*', + command: 'pip install pdm; pdm init', + commandWin: 'pip install pdm; pdm init' + } + ] + } +} + +export default pythonVersion diff --git a/src/render/components/Host/CreateProject/version_nodejs.ts b/src/render/components/Host/CreateProject/version_nodejs.ts index f1a88452b..4ec8c03e6 100644 --- a/src/render/components/Host/CreateProject/version_nodejs.ts +++ b/src/render/components/Host/CreateProject/version_nodejs.ts @@ -69,6 +69,66 @@ const version = { commandWin: 'npm i -g @nestjs/cli;Start-Sleep -Seconds 1.5;nest new' } ] + }, + Nuxt: { + url: 'https://nuxt.com/', + list: [ + { + name: 'latest', + version: '*', + command: 'npx nuxi@latest init' + } + ] + }, + Svelte: { + url: 'https://svelte.dev/', + list: [ + { + name: 'latest', + version: '*', + command: 'npm create sv@latest' + } + ] + }, + Astro: { + url: 'https://astro.build/', + list: [ + { + name: 'latest', + version: '*', + command: 'npm create astro@latest' + } + ] + }, + Hono: { + url: 'https://hono.dev/', + list: [ + { + name: 'latest', + version: '*', + command: 'npm create hono@latest' + } + ] + }, + Solid: { + url: 'https://www.solidjs.com/', + list: [ + { + name: 'latest', + version: '*', + command: 'npm create solid@latest' + } + ] + }, + Fastify: { + url: 'https://fastify.dev/', + list: [ + { + name: 'latest', + version: '*', + command: 'npm init fastify' + } + ] } } export default version diff --git a/src/render/components/Python/CreateProject.vue b/src/render/components/Python/CreateProject.vue new file mode 100644 index 000000000..c023fc865 --- /dev/null +++ b/src/render/components/Python/CreateProject.vue @@ -0,0 +1,9 @@ + + + + + + + diff --git a/src/render/components/Python/Index.vue b/src/render/components/Python/Index.vue index c478b1197..a7fda70a4 100644 --- a/src/render/components/Python/Index.vue +++ b/src/render/components/Python/Index.vue @@ -13,8 +13,9 @@ :fetch-data-when-create="true" > + @@ -36,7 +37,13 @@ import { I18nT } from '@lang/index' import ProjectIndex from '@/components/LanguageProjects/index.vue' import { Project } from '@/util/Project' + import ProjectCreateVM from './CreateProject.vue' const { tab } = AppModuleSetup('python') - const tabs = [I18nT('base.service'), I18nT('base.versionManager'), I18nT('host.projectPython')] + const tabs = [ + I18nT('base.service'), + I18nT('base.versionManager'), + I18nT('host.newProject'), + I18nT('host.projectPython') + ] diff --git a/src/render/components/Ruby/Index.vue b/src/render/components/Ruby/Index.vue index e6a9463d4..fa07a209e 100644 --- a/src/render/components/Ruby/Index.vue +++ b/src/render/components/Ruby/Index.vue @@ -19,11 +19,7 @@ title="Ruby" url="https://github.com/oneclick/rubyinstaller2/releases" > - + @@ -44,9 +40,5 @@ import { Project } from '@/util/Project' const { tab } = AppModuleSetup('ruby') - const tabs = [ - I18nT('base.service'), - I18nT('base.versionManager'), - `Ruby ${I18nT('base.projects')}` - ] + const tabs = [I18nT('base.service'), I18nT('base.versionManager'), I18nT('host.projectRuby')] diff --git a/src/render/components/Rust/Index.vue b/src/render/components/Rust/Index.vue index 413fa0829..7b4a843e1 100644 --- a/src/render/components/Rust/Index.vue +++ b/src/render/components/Rust/Index.vue @@ -21,15 +21,11 @@ url="https://forge.rust-lang.org/infra/other-installation-methods.html#standalone-installers" > - + - {{ I18nT('nodejs.openIN') }} RustMine + {{ I18nT('nodejs.openIN') }} RustRover @@ -51,6 +47,6 @@ I18nT('base.service'), I18nT('base.versionManager'), 'Rustup', - `Rust ${I18nT('base.projects')}` + I18nT('host.projectRust') ] diff --git a/src/render/components/Rust/rustup.vue b/src/render/components/Rust/rustup.vue index 52bae63f9..f8bb7cae2 100644 --- a/src/render/components/Rust/rustup.vue +++ b/src/render/components/Rust/rustup.vue @@ -5,8 +5,16 @@ Rustup - - + + @@ -98,10 +106,8 @@ :loading="RustupSetup.installing" :disabled="RustupSetup.installing" @click="doVersionAction(scope.row)" - >{{ - scope.row.isInstalled ? I18nT('base.uninstall') : I18nT('base.install') - }} + >{{ scope.row.isInstalled ? I18nT('base.uninstall') : I18nT('base.install') }} + @@ -111,7 +117,9 @@ - target + {{ + I18nT('base.targetPlatforms') + }} {{ - scope.row.installed ? I18nT('base.uninstall') : I18nT('base.install') - }} + >{{ scope.row.installed ? I18nT('base.uninstall') : I18nT('base.install') }} + diff --git a/src/render/components/Tools/GitCheatsheet/git-memo.content.md b/src/render/components/Tools/GitCheatsheet/git-memo.content.md deleted file mode 100644 index 6783ad864..000000000 --- a/src/render/components/Tools/GitCheatsheet/git-memo.content.md +++ /dev/null @@ -1,77 +0,0 @@ -## Configuration - -Set the global config - -```shell -git config --global user.name "[name]" -git config --global user.email "[email]" -``` - -## Get started - -Create a git repository - -```shell -git init -``` - -Clone an existing git repository - -```shell -git clone [url] -``` - -## Commit - -Commit all tracked changes - -```shell -git commit -am "[commit message]" -``` - -Add new modifications to the last commit - -```shell -git commit --amend --no-edit -``` - -## I’ve made a mistake - -Change last commit message - -```shell -git commit --amend -``` - -Undo most recent commit and keep changes - -```shell -git reset HEAD~1 -``` - -Undo the `N` most recent commit and keep changes - -```shell -git reset HEAD~N -``` - -Undo most recent commit and get rid of changes - -```shell -git reset HEAD~1 --hard -``` - -Reset branch to remote state - -```shell -git fetch origin -git reset --hard origin/[branch-name] -``` - -## Miscellaneous - -Renaming the local master branch to main - -```shell -git branch -m master main -``` diff --git a/src/render/components/Tools/GitCheatsheet/index.vue b/src/render/components/Tools/GitCheatsheet/index.vue index bd3a654da..362dac252 100644 --- a/src/render/components/Tools/GitCheatsheet/index.vue +++ b/src/render/components/Tools/GitCheatsheet/index.vue @@ -1,10 +1,3 @@ - @@ -16,8 +9,246 @@ - + + + + diff --git a/src/render/components/Tools/GitCheatsheet/lang/git-memo.en.md b/src/render/components/Tools/GitCheatsheet/lang/git-memo.en.md new file mode 100644 index 000000000..f81de16c8 --- /dev/null +++ b/src/render/components/Tools/GitCheatsheet/lang/git-memo.en.md @@ -0,0 +1,495 @@ +## Configuration + +Set the global config + +```shell +git config --global user.name "[name]" +git config --global user.email "[email]" +``` + +## Get started + +Create a git repository + +```shell +git init +``` + +Clone an existing git repository + +```shell +git clone [url] +``` + +## Branching + +Manage development lines. + +- **List branches**: + ```shell + git branch + ``` +- **Create new branch**: + ```shell + git branch [branch-name] + ``` +- **Switch branch**: + ```shell + git checkout [branch-name] + # Or with 'switch' (Git 2.23+): + git switch [branch-name] + ``` +- **Create and switch to new branch**: + ```shell + git checkout -b [branch-name] + # Or with 'switch': + git switch -c [branch-name] + ``` +- **Merge branch** (to current branch): + ```shell + git merge [branch-name] + ``` +- **Delete branch**: + ```shell + git branch -d [branch-name] + ``` + +## Inspect & Compare + +Check status and history. + +- **Check status**: + ```shell + git status + ``` +- **View commit history**: + ```shell + git log --oneline --graph --decorate --all + ``` +- **Show changes**: + ```shell + git diff + ``` + +## Stash + +Temporarily store uncommitted changes. + +- **Save changes to stash**: + ```shell + git stash push -m "message" + ``` +- **List stashes**: + ```shell + git stash list + ``` +- **Apply stash and keep it**: + ```shell + git stash apply stash@{n} + ``` +- **Apply stash and delete it**: + ```shell + git stash pop + ``` +- **Delete specific stash**: + ```shell + git stash drop stash@{n} + ``` + +## Commit + +Commit all tracked changes + +```shell +git commit -am "[commit message]" +``` + +Add new modifications to the last commit + +```shell +git commit --amend --no-edit +``` + +## I’ve made a mistake + +Change last commit message + +```shell +git commit --amend +``` + +Undo most recent commit and keep changes + +```shell +git reset HEAD~1 +``` + +Undo the `N` most recent commit and keep changes + +```shell +git reset HEAD~N +``` + +Undo most recent commit and get rid of changes + +```shell +git reset HEAD~1 --hard +``` + +Reset branch to remote state + +```shell +git fetch origin +git reset --hard origin/[branch-name] +``` + +## Miscellaneous + +Renaming the local master branch to main + +```shell +git branch -m master main +``` + +## Git Flow + +Git Flow is a branching model designed to organize software development, release management, and emergency bug fixing. + +### Core Branches + +- **`master` (or `main`)**: Stores the official release history. It must always be stable and production-ready. +- **`develop`**: The primary integration branch. It contains the latest development changes for the next release. + +### Supporting Branches + +- **`feature/*`** (from `develop`): Used for developing new features. Once finished, they are merged back into `develop`. +- **`release/*`** (from `develop`): Used to prepare for a new production release. It allows for minor bug fixes and metadata preparation. Merged into both `master` and `develop` when finished. +- **`hotfix/*`** (from `master`): Used for critical production bug fixes. Merged into both `master` and `develop` immediately after the fix. + +### Key Commands + +- **New Feature**: `git checkout -b feature/feature-name develop` +- **New Release**: `git checkout -b release/v1.0.0 develop` +- **New Hotfix**: `git checkout -b hotfix/fix-name master` +- **Commit Message Format**: `: ` (e.g., `feat: add login`, `fix: header overlap`). + - _Types_: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`. + +### Collaboration & Best Practices + +- **Git Fetch vs. Pull**: Use `git fetch` to see remote changes without affecting your local code. Use `git pull` to fetch and merge into your current branch. +- **Integration Workflow**: Before pushing a feature, merge `develop` into your feature branch to resolve conflicts locally: + +```shell +git checkout feature/your-feature +git merge develop +``` + +- **Safe Force Pushing**: Avoid `git push --force`. Use `git push --force-with-lease` to ensure you don't accidentally overwrite others' work. + +### Line Endings (CRLF/LF) + +Handle cross-platform line ending issues between Windows (CRLF) and Linux/macOS (LF). + +#### 1. Global Configuration + +- **Windows users**: + ```shell + git config --global core.autocrlf true + ``` +- **macOS/Linux users**: + ```shell + git config --global core.autocrlf input + ``` + +#### 2. Using `.gitattributes` (Best Practice) + +Create a `.gitattributes` file in the project root to enforce consistent line endings for everyone: + +```text +# Handle line endings automatically for text files +* text=auto + +# Explicitly set to LF for specific files +*.sh text eol=lf +*.js text eol=lf +``` + +#### 3. Refreshing/Renormalizing line endings + +If you already have files with wrong line endings in your repo: + +```shell +# 1. Save your work! (commit or stash) +# 2. Remove every file from Git's index +git rm --cached -r . +# 3. Rewrite the index to pick up all the new line ending configurations +git reset --hard +``` + +Alternative for Git 2.16+: + +```shell +git add --renormalize . +``` + +### Git Submodules + +Submodules allow you to keep another Git repository as a subdirectory of your repository. + +- **Add a submodule**: + ```shell + git submodule add [url] [path] + ``` +- **Initialize submodules** (after cloning parent): + ```shell + git submodule init + git submodule update + ``` +- **Clone with all submodules**: + ```shell + git clone --recursive [url] + ``` +- **Update all submodules to latest**: + ```shell + git submodule update --remote --merge + ``` + +## Remote Management + +Manage connections to other repositories. + +- **Add a remote**: + ```shell + git remote add [name] [url] + ``` +- **List remotes**: + ```shell + git remote -v + ``` +- **Change remote URL**: + ```shell + git remote set-url [name] [url] + ``` +- **Fetch changes from remote** (no merge): + ```shell + git fetch [remote] + ``` +- **Pull changes and rebase local commits**: + ```shell + git pull --rebase [remote] [branch] + ``` +- **Delete remote branch**: + ```shell + git push [remote] --delete [branch] + ``` + +## Advanced Stash + +Precise control over temporary storage. + +- **Stash specific parts of changes (interactive)**: + ```shell + git stash -p + ``` +- **Stash including untracked files**: + ```shell + git stash -u + ``` +- **Stash tracked files but keep them in index**: + ```shell + git stash --keep-index + ``` +- **Create a branch from a stash**: + ```shell + git stash branch [branch-name] stash@{n} + ``` + +## Advanced Rebase + +Rewrite history for a clean linear log. + +- **Interactive rebase (last N commits)**: + ```shell + git rebase -i HEAD~N + ``` + _Actions: `pick` (keep), `reword` (edit msg), `edit` (edit code), `squash` (merge up), `fixup` (merge up & discard msg)._ +- **Auto-squash commits marked as fixups**: + ```shell + git rebase -i --autosquash [base-branch] + ``` +- **Continue rebase after conflict resolution**: + ```shell + git rebase --continue + ``` +- **Abort rebase**: + ```shell + git rebase --abort + ``` + +## Advanced Merging + +Handle complex integrations and conflicts. + +- **Merge with specific strategy**: + ```shell + git merge -s [strategy] [branch] + # Strategies: recursive (default), resolve, octopus, ours, subtree + ``` +- **Merge but don't commit (examine results)**: + ```shell + git merge --no-commit [branch] + ``` +- **Find merge base (common ancestor)**: + ```shell + git merge-base [branch1] [branch2] + ``` +- **Conflict Resolution**: + 1. Open files with markers `<<<<<<<`, `=======`, `>>>>>>>`. + 2. Edit to keep desired code. + 3. `git add [file]` and `git commit`. + +## Tags + +Mark specific points in history (usually releases). + +- **List tags**: + ```shell + git tag + ``` +- **Lightweight tag**: + ```shell + git tag [tag-name] + ``` +- **Annotated tag (recommended)**: + ```shell + git tag -a [tag-name] -m "[message]" + ``` +- **Verify a signed tag**: + ```shell + git tag -v [tag-name] + ``` +- **Push tags to remote**: + ```shell + git push origin --tags + ``` + +## Debugging & Inspection + +Find bugs and examine objects. + +- **Show commit/object details**: + ```shell + git show [commit-hash] + ``` +- **See who changed each line (blame)**: + ```shell + git blame [file] + ``` +- **Search text in tracked files**: + ```shell + git grep "[text]" + ``` +- **Binary search for bug (bisect)**: + ```shell + git bisect start + git bisect bad # Current version is broken + git bisect good [commit-hash] # This old version was working + # Git will checkout midpoints. Test and tell Git 'good' or 'bad'. + git bisect reset # Finish debugging + ``` +- **Verify objects in a pack file**: + ```shell + git verify-pack -v .git/objects/pack/pack-*.idx + ``` + +## Internal Tools & Workflows + +Advanced repository management. + +- **View reference history (reflog)**: + ```shell + git reflog + # Useful for recovering 'lost' commits after a reset. + ``` +- **Manage multiple working trees**: + ```shell + git worktree add [path] [branch] + # Allows working on two branches at once in different folders. + ``` +- **Run command on all submodules**: + ```shell + git submodule foreach '[command]' + ``` +- **Synchronize submodule URLs**: + ```shell + git submodule sync + ``` + +## Maintenance & Cleanup + +Keep the repository healthy and small. + +- **Garbage collection & optimization**: + ```shell + git gc --prune=now --aggressive + ``` +- **Check for corrupted objects**: + ```shell + git fsck + ``` +- **Remove unreachable objects**: + ```shell + git prune -v + ``` +- **Repack objects into efficient packfiles**: + ```shell + git repack -a -d + ``` + +## Security & Patch Workflow + +- **GPG Signing (Sign commit)**: + ```shell + git commit -S -m "[message]" + ``` +- **Format commits as email patches**: + ```shell + git format-patch [branch] + ``` +- **Apply mail patches**: + ```shell + git am < [patch-file] + ``` +- **Credential Manager (Recommended)**: + ```shell + # Windows, macOS, Linux (with GCM installed) + git config --global credential.helper manager + # Alternative OS-specific: osxkeychain (macOS), libsecret (Linux) + ``` +- **Credential caching (Temporary)**: + ```shell + git config --global credential.helper 'cache --timeout=3600' + ``` + +## Useful Shortcuts & Configs + +- **Enable Reuse Recorded Resolution (rerere)**: + ```shell + git config --global rerere.enabled true + ``` +- **Help Autocorrect (0.1s delay)**: + ```shell + git config --global help.autocorrect 1 + ``` +- **Useful Alias**: + ```shell + git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" + ``` + +## Best Practices in Teams + +- **Atomic Commits**: Each commit should represent one logical change. +- **Pull before Push**: Always sync with remote to avoid unnecessary conflicts. +- **Write descriptive messages**: Use the imperative mood (e.g., "Fix" not "Fixed"). +- **Never rebase public history**: Only rebase branches that haven't been pushed yet. +- **Use meaningful branch names**: `feat/description` or `fix/bug-id`. diff --git a/src/render/components/Tools/GitCheatsheet/lang/git-memo.vi.md b/src/render/components/Tools/GitCheatsheet/lang/git-memo.vi.md new file mode 100644 index 000000000..e8d9f5193 --- /dev/null +++ b/src/render/components/Tools/GitCheatsheet/lang/git-memo.vi.md @@ -0,0 +1,495 @@ +## Cấu hình + +Thiết lập cấu hình toàn cục + +```shell +git config --global user.name "[tên]" +git config --global user.email "[email]" +``` + +## Bắt đầu + +Tạo một kho lưu trữ git mới + +```shell +git init +``` + +Sao chép một kho lưu trữ git hiện có + +```shell +git clone [url] +``` + +## Nhánh (Branching) + +Quản lý các luồng phát triển. + +- **Liệt kê danh sách nhánh**: + ```shell + git branch + ``` +- **Tạo nhánh mới**: + ```shell + git branch [tên-nhánh] + ``` +- **Chuyển nhánh**: + ```shell + git checkout [tên-nhánh] + # Hoặc dùng 'switch' (Git 2.23+): + git switch [tên-nhánh] + ``` +- **Tạo và chuyển sang nhánh mới ngay lập tức**: + ```shell + git checkout -b [tên-nhánh] + # Hoặc dùng 'switch': + git switch -c [tên-nhánh] + ``` +- **Gộp nhánh** (vào nhánh hiện tại): + ```shell + git merge [tên-nhánh] + ``` +- **Xóa nhánh**: + ```shell + git branch -d [tên-nhánh] + ``` + +## Kiểm tra & So sánh + +Xem trạng thái và lịch sử. + +- **Kiểm tra trạng thái**: + ```shell + git status + ``` +- **Xem lịch sử commit**: + ```shell + git log --oneline --graph --decorate --all + ``` +- **Xem các thay đổi chưa commit**: + ```shell + git diff + ``` + +## Tạm lưu (Stash) + +Lưu trữ tạm thời các thay đổi chưa commit. + +- **Lưu các thay đổi vào stash**: + ```shell + git stash push -m "tin nhắn" + ``` +- **Liệt kê danh sách stash**: + ```shell + git stash list + ``` +- **Áp dụng stash và giữ lại trong danh sách**: + ```shell + git stash apply stash@{n} + ``` +- **Áp dụng stash và xóa khỏi danh sách**: + ```shell + git stash pop + ``` +- **Xóa một stash cụ thể**: + ```shell + git stash drop stash@{n} + ``` + +## Commit + +Commit tất cả các thay đổi đã theo dõi + +```shell +git commit -am "[tin nhắn commit]" +``` + +Thêm các sửa đổi mới vào commit cuối cùng + +```shell +git commit --amend --no-edit +``` + +## Tôi đã mắc lỗi + +Thay đổi tin nhắn của commit cuối cùng + +```shell +git commit --amend +``` + +Hoàn tác commit gần nhất và giữ lại các thay đổi + +```shell +git reset HEAD~1 +``` + +Hoàn tác `N` commit gần nhất và giữ lại các thay đổi + +```shell +git reset HEAD~N +``` + +Hoàn tác commit gần nhất và loại bỏ hoàn toàn các thay đổi + +```shell +git reset HEAD~1 --hard +``` + +Đặt lại nhánh về trạng thái của remote + +```shell +git fetch origin +git reset --hard origin/[tên-nhánh] +``` + +## Các lệnh khác + +Đổi tên nhánh master cục bộ thành main + +```shell +git branch -m master main +``` + +## Git Flow + +Git Flow là mô hình phân nhánh giúp tổ chức quy trình phát triển phần mềm, quản lý các bản phát hành và sửa lỗi khẩn cấp. + +### Các nhánh chính + +- **`master` (hoặc `main`)**: Lưu trữ lịch sử các bản phát hành chính thức. Nhánh này luôn phải ở trạng thái ổn định để sẵn sàng triển khai. +- **`develop`**: Nhánh tích hợp chính. Chứa các mã nguồn mới nhất chuẩn bị cho bản phát hành tiếp theo. + +### Các nhánh hỗ trợ + +- **`feature/*`** (tách từ `develop`): Dùng để phát triển tính năng mới. Sau khi hoàn thành sẽ được gộp lại vào `develop`. +- **`release/*`** (tách từ `develop`): Chuẩn bị cho việc phát hành bản production mới. Dùng để sửa các lỗi nhỏ và cập nhật tài liệu trước khi gộp vào `master` và `develop`. +- **`hotfix/*`** (tách từ `master`): Dùng để sửa các lỗi nghiêm trọng phát sinh trên môi trường production. Sau đó được gộp vào cả `master` và `develop`. + +### Các lệnh quan trọng + +- **Tạo tính năng mới**: `git checkout -b feature/ten-tinh-nang develop` +- **Tạo bản release**: `git checkout -b release/v1.0.0 develop` +- **Sửa lỗi gấp (Hotfix)**: `git checkout -b hotfix/ten-fix master` +- **Chuẩn commit message**: `: ` (VD: `feat: thêm chức năng đăng nhập`). + - _Các loại phổ biến_: `feat` (tính năng), `fix` (sửa lỗi), `docs` (tài liệu), `refactor` (tối ưu mã), `chore` (cấu hình hệ thống). + +### Hợp tác & Quy tắc tốt nhất + +- **Git Fetch và Pull**: Dùng `git fetch` để xem các thay đổi từ remote mà không ảnh hưởng đến code local. Dùng `git pull` để lấy về và gộp ngay vào nhánh hiện tại. +- **Quy trình Tích hợp**: Trước khi đẩy (push) tính năng, hãy gộp `develop` vào nhánh feature để xử lý xung đột tại máy cá nhân: + +```shell +git checkout feature/ten-tinh-nang +git merge develop +``` + +- **Đẩy code (Push) an toàn**: Tránh dùng `git push --force`. Hãy dùng `git push --force-with-lease` để đảm bảo bạn không vô tình ghi đè lên công việc của đồng nghiệp khác. + +### Cấu hình xuống dòng (CRLF/LF) + +Xử lý vấn đề tương thích ký tự xuống dòng giữa Windows (CRLF) và Linux/macOS (LF). + +#### 1. Cấu hình toàn cục (Global) + +- **Người dùng Windows**: + ```shell + git config --global core.autocrlf true + ``` +- **Người dùng macOS/Linux**: + ```shell + git config --global core.autocrlf input + ``` + +#### 2. Sử dụng `.gitattributes` (Tốt nhất) + +Tạo file `.gitattributes` ở thư mục gốc của dự án để ép buộc quy tắc xuống dòng cho tất cả mọi người: + +```text +# Tự động xử lý cho tất cả các file text +* text=auto + +# Ép buộc dùng LF cho các loại file cụ thể +*.sh text eol=lf +*.js text eol=lf +``` + +#### 3. Làm mới/Chuẩn hóa lại (Renormalize) + +Nếu dự án hiện tại đã bị lẫn lộn CRLF/LF, hãy chạy các lệnh sau để chuẩn hóa: + +```shell +# 1. Lưu lại công việc (commit hoặc stash) +# 2. Xóa cache của git +git rm --cached -r . +# 3. Khôi phục lại các file theo cấu hình mới +git reset --hard +``` + +Hoặc dùng lệnh sau (Git 2.16+): + +```shell +git add --renormalize . +``` + +### Git Submodules + +Submodule cho phép bạn chứa một kho lưu trữ Git khác như một thư mục con trong kho lưu trữ của mình. + +- **Thêm một submodule**: + ```shell + git submodule add [url] [đường-dẫn] + ``` +- **Khởi tạo submodule** (sau khi clone project cha): + ```shell + git submodule init + git submodule update + ``` +- **Clone kèm theo tất cả submodule**: + ```shell + git clone --recursive [url] + ``` +- **Cập nhật tất cả submodule lên bản mới nhất**: + ```shell + git submodule update --remote --merge + ``` + +## Quản lý Remote + +Quản lý kết nối với các kho lưu trữ khác. + +- **Thêm một remote**: + ```shell + git remote add [tên] [url] + ``` +- **Liệt kê các remote**: + ```shell + git remote -v + ``` +- **Thay đổi URL của remote**: + ```shell + git remote set-url [tên] [url] + ``` +- **Lấy thay đổi từ remote** (không gộp): + ```shell + git fetch [remote] + ``` +- **Lấy thay đổi và rebase các commit cục bộ**: + ```shell + git pull --rebase [remote] [nhánh] + ``` +- **Xóa nhánh trên remote**: + ```shell + git push [remote] --delete [nhánh] + ``` + +## Tạm lưu nâng cao (Advanced Stash) + +Kiểm soát chính xác việc lưu trữ tạm thời. + +- **Tạm lưu từng phần thay đổi (tương tác)**: + ```shell + git stash -p + ``` +- **Tạm lưu bao gồm cả các file chưa theo dõi (untracked)**: + ```shell + git stash -u + ``` +- **Tạm lưu các file đã theo dõi nhưng giữ lại trong index**: + ```shell + git stash --keep-index + ``` +- **Tạo một nhánh mới từ một stash**: + ```shell + git stash branch [tên-nhánh] stash@{n} + ``` + +## Rebase nâng cao + +Viết lại lịch sử để có một log tuyến tính sạch sẽ. + +- **Rebase tương tác (N commit cuối)**: + ```shell + git rebase -i HEAD~N + ``` + _Hành động: `pick` (giữ), `reword` (sửa tin nhắn), `edit` (sửa code), `squash` (gộp lên), `fixup` (gộp lên và bỏ tin nhắn)._ +- **Tự động gộp các commit được đánh dấu là fixup**: + ```shell + git rebase -i --autosquash [nhánh-gốc] + ``` +- **Tiếp tục rebase sau khi xử lý xung đột**: + ```shell + git rebase --continue + ``` +- **Hủy bỏ rebase**: + ```shell + git rebase --abort + ``` + +## Gộp nhánh nâng cao (Advanced Merging) + +Xử lý các tình huống tích hợp và xung đột phức tạp. + +- **Gộp nhánh với chiến lược cụ thể**: + ```shell + git merge -s [chiến-lược] [nhánh] + # Chiến lược: recursive (mặc định), resolve, octopus, ours, subtree + ``` +- **Gộp nhánh nhưng không commit (để kiểm tra kết quả)**: + ```shell + git merge --no-commit [nhánh] + ``` +- **Tìm điểm gốc chung (merge base)**: + ```shell + git merge-base [nhánh1] [nhánh2] + ``` +- **Xử lý xung đột (Conflict Resolution)**: + 1. Mở các file có dấu hiệu `<<<<<<<`, `=======`, `>>>>>>>`. + 2. Chỉnh sửa để giữ lại code mong muốn. + 3. `git add [file]` và `git commit`. + +## Thẻ (Tags) + +Đánh dấu các điểm cụ thể trong lịch sử (thường là các bản phát hành). + +- **Liệt kê danh sách tag**: + ```shell + git tag + ``` +- **Tag nhẹ (Lightweight)**: + ```shell + git tag [tên-tag] + ``` +- **Tag có chú thích (Annotated - Khuyên dùng)**: + ```shell + git tag -a [tên-tag] -m "[tin nhắn]" + ``` +- **Xác minh một tag đã ký số**: + ```shell + git tag -v [tên-tag] + ``` +- **Đẩy các tag lên remote**: + ```shell + git push origin --tags + ``` + +## Gỡ lỗi & Kiểm tra (Debugging) + +Tìm lỗi và kiểm tra các đối tượng. + +- **Xem chi tiết commit/đối tượng**: + ```shell + git show [mã-hash] + ``` +- **Xem ai đã thay đổi từng dòng code (blame)**: + ```shell + git blame [file] + ``` +- **Tìm kiếm văn bản trong các file đã theo dõi**: + ```shell + git grep "[văn-bản]" + ``` +- **Tìm kiếm lỗi bằng thuật toán chia đôi (bisect)**: + ```shell + git bisect start + git bisect bad # Phiên bản hiện tại bị lỗi + git bisect good [mã-hash] # Phiên bản cũ này vẫn hoạt động tốt + # Git sẽ tự động checkout các điểm ở giữa. Bạn kiểm tra và báo cho Git 'good' hoặc 'bad'. + git bisect reset # Kết thúc quá trình gỡ lỗi + ``` +- **Kiểm tra tính toàn vẹn của file pack**: + ```shell + git verify-pack -v .git/objects/pack/pack-*.idx + ``` + +## Công cụ nội bộ & Quy trình nâng cao + +Quản lý kho lưu trữ nâng cao. + +- **Xem lịch sử tham chiếu (reflog)**: + ```shell + git reflog + # Rất hữu ích để khôi phục các commit 'bị mất' sau khi reset. + ``` +- **Quản lý nhiều cây làm việc (worktree)**: + ```shell + git worktree add [đường-dẫn] [nhánh] + # Cho phép làm việc trên hai nhánh cùng lúc ở các thư mục khác nhau. + ``` +- **Chạy lệnh trên tất cả các submodule**: + ```shell + git submodule foreach '[lệnh]' + ``` +- **Đồng bộ hóa URL của submodule**: + ```shell + git submodule sync + ``` + +## Bảo trì & Làm sạch + +Giữ cho kho lưu trữ khỏe mạnh và gọn nhẹ. + +- **Thu gom rác & tối ưu hóa**: + ```shell + git gc --prune=now --aggressive + ``` +- **Kiểm tra các đối tượng bị lỗi**: + ```shell + git fsck + ``` +- **Xóa các đối tượng không thể truy cập**: + ```shell + git prune -v + ``` +- **Đóng gói lại các đối tượng vào packfile**: + ```shell + git repack -a -d + ``` + +## Bảo mật & Quy trình Patch + +- **Ký số GPG (Ký commit)**: + ```shell + git commit -S -m "[tin nhắn]" + ``` +- **Xuất commit thành file patch email**: + ```shell + git format-patch [nhánh] + ``` +- **Áp dụng các file patch**: + ```shell + git am < [file-patch] + ``` +- **Trình quản lý thông tin đăng nhập (Khuyên dùng)**: + ```shell + # Windows, macOS, Linux (khi đã cài GCM) + git config --global credential.helper manager + # Các trình hỗ trợ khác: osxkeychain (macOS), libsecret (Linux) + ``` +- **Lưu tạm thông tin đăng nhập (Credential cache)**: + ```shell + git config --global credential.helper 'cache --timeout=3600' + ``` + +## Phím tắt & Cấu hình hữu ích + +- **Bật tính năng Reuse Recorded Resolution (rerere)**: + ```shell + git config --global rerere.enabled true + ``` +- **Tự động sửa lỗi gõ lệnh (độ trễ 0.1s)**: + ```shell + git config --global help.autocorrect 1 + ``` +- **Alias hữu ích (lg)**: + ```shell + git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" + ``` + +## Quy tắc tốt nhất trong nhóm (Best Practices) + +- **Commit nguyên tử (Atomic)**: Mỗi commit chỉ nên đại diện cho một thay đổi logic duy nhất. +- **Pull trước khi Push**: Luôn đồng bộ với remote để tránh các xung đột không đáng có. +- **Viết tin nhắn commit rõ ràng**: Sử dụng thể mệnh lệnh (VD: "Fix" thay vì "Fixed"). +- **Không bao giờ rebase lịch sử đã công khai**: Chỉ rebase các nhánh chưa được push lên server. +- **Sử dụng tên nhánh có ý nghĩa**: `feat/description` hoặc `fix/bug-id`. diff --git a/src/render/components/Zig/Index.vue b/src/render/components/Zig/Index.vue index bf75019a0..99dcc77e4 100644 --- a/src/render/components/Zig/Index.vue +++ b/src/render/components/Zig/Index.vue @@ -33,7 +33,7 @@ import ProjectIndex from '@/components/LanguageProjects/index.vue' const projectTitle = computed(() => { - return `Zig ${I18nT('base.projects')}` + return I18nT('host.projectZig') }) const { tab } = AppModuleSetup('zig') diff --git a/src/render/core/CloudflareTunnel/CloudflareTunnel.ts b/src/render/core/CloudflareTunnel/CloudflareTunnel.ts index 88d05f5b4..833ef20c7 100644 --- a/src/render/core/CloudflareTunnel/CloudflareTunnel.ts +++ b/src/render/core/CloudflareTunnel/CloudflareTunnel.ts @@ -1,11 +1,13 @@ import IPC from '@/util/IPC' -import { CloudflareTunnelDnsRecord, ZoneType } from '@/core/CloudflareTunnel/type' +import { CloudflareTunnelDnsRecord } from '@/core/CloudflareTunnel/type' import { I18nT } from '@lang/index' import { MessageError } from '@/util/Element' +import { md5 } from '@/util/Index' export class CloudflareTunnel { id: string = '' apiToken: string = '' + tunnelName: string = '' tunnelId: string = '' tunnelToken: string = '' cloudflaredBin: string = '' @@ -22,17 +24,25 @@ export class CloudflareTunnel { this.pid = '' this.run = false this.running = false + if (this.apiToken && !this.tunnelName) { + this.tunnelName = `FlyEnv-Tunnel-${md5(this.apiToken).substring(0, 12)}` + } } - /** - * 获取所有的Zone - */ - fetchAllZone(): Promise { - return new Promise((resolve) => { - IPC.send('app-fork:cloudflare-tunnel', 'fetchAllZone').then((key: string, res: any) => { - IPC.off(key) - resolve(res?.data ?? []) - }) + fetchTunnel(): Promise { + return new Promise((resolve, reject) => { + IPC.send('app-fork:cloudflare-tunnel', 'fetchTunnel', JSON.parse(JSON.stringify(this))).then( + (key: string, res: any) => { + IPC.off(key) + if (res?.data?.tunnelId && res?.data?.tunnelToken) { + this.tunnelId = res?.data?.tunnelToken + this.tunnelToken = res?.data?.tunnelToken + this.tunnelName = res?.data?.tunnelName + resolve(true) + } + reject(new Error(res?.msg ?? I18nT('base.fail'))) + } + ) }) } diff --git a/src/render/core/CloudflareTunnel/CloudflareTunnelStore.ts b/src/render/core/CloudflareTunnel/CloudflareTunnelStore.ts index 3c1bf83d5..5419e3962 100644 --- a/src/render/core/CloudflareTunnel/CloudflareTunnelStore.ts +++ b/src/render/core/CloudflareTunnel/CloudflareTunnelStore.ts @@ -14,6 +14,7 @@ class CloudflareTunnelStore { StorageGetAsync(storeKey) .then((res: CloudflareTunnel[]) => { if (res) { + console.log('CloudflareTunnelStore init res:', res) for (const item of res) { const obj = reactiveBind(new CloudflareTunnel(item)) this.items.push(obj) diff --git a/src/render/core/CloudflareTunnel/type.ts b/src/render/core/CloudflareTunnel/type.ts index c89077b3d..36f0f5f12 100644 --- a/src/render/core/CloudflareTunnel/type.ts +++ b/src/render/core/CloudflareTunnel/type.ts @@ -8,8 +8,10 @@ export type ZoneType = { } export type CloudflareTunnelDnsRecord = { + id: string subdomain: string localService: string zoneId: string zoneName: string + protocol: 'http' | 'https' }