From d1cf4a7f43d2e527e60976f2fd69e54a9efe7322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A5=9E=E4=BB=A3=E7=B6=BA=E5=87=9B?= Date: Thu, 15 Aug 2024 20:33:34 +0800 Subject: [PATCH] Initial commit --- .gitattributes | 3 + .github/workflows/pages-deploy.yml | 47 +++++++ .gitignore | 30 ++++ .prettierrc | 9 ++ .vscode/extensions.json | 3 + LICENSE | 21 +++ README.md | 13 ++ bun.lockb | Bin 0 -> 100691 bytes env.d.ts | 4 + index.html | 13 ++ node/env.d.ts | 3 + node/proxy.ts | 16 +++ package.json | 36 +++++ public/favicon.ico | Bin 0 -> 16958 bytes src/App.vue | 216 +++++++++++++++++++++++++++++ src/assets/main.css | 43 ++++++ src/main.ts | 6 + src/utils/fileSystem.ts | 32 +++++ src/utils/git.ts | 5 + src/workers/git.ts | 211 ++++++++++++++++++++++++++++ tsconfig.app.json | 13 ++ tsconfig.json | 11 ++ tsconfig.node.json | 11 ++ vite.config.ts | 21 +++ 24 files changed, 767 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/pages-deploy.yml create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 .vscode/extensions.json create mode 100644 LICENSE create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 env.d.ts create mode 100644 index.html create mode 100644 node/env.d.ts create mode 100644 node/proxy.ts create mode 100644 package.json create mode 100644 public/favicon.ico create mode 100644 src/App.vue create mode 100644 src/assets/main.css create mode 100644 src/main.ts create mode 100644 src/utils/fileSystem.ts create mode 100644 src/utils/git.ts create mode 100644 src/workers/git.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..efd08cf --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Auto detect text files and perform LF normalization +* text=auto +*.lockb binary diff=lockb diff --git a/.github/workflows/pages-deploy.yml b/.github/workflows/pages-deploy.yml new file mode 100644 index 0000000..0bab191 --- /dev/null +++ b/.github/workflows/pages-deploy.yml @@ -0,0 +1,47 @@ +name: Deploy to Pages + +on: + workflow_dispatch: + push: + branches: + - master + paths: + - 'src/**' + - 'public/**' + - 'index.html' + - 'package.json' + - 'vite.config.ts' + - '.github/workflows/pages-deploy.yml' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + - name: Install Dependencies + run: bun i + - name: Build + run: bun build-only + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: './dist' + deploy: + needs: build + runs-on: ubuntu-latest + concurrency: + group: 'pages' + cancel-in-progress: true + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..9d353cc --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "printWidth": 140, + "singleQuote": true, + "trailingComma": "all", + "arrowParens": "avoid", + "useTabs": false, + "tabWidth": 2, + "semi": true +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..515d60a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 神代綺凛 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b131aaa --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# MAA Resource Updater + +无需依赖任何第三方软件,纯靠浏览器工作的 [MAA 资源文件](https://github.com/arkntools/maa-resource-updater) 更新器 + +仅支持:Chrome ≥ 86, Edge ≥ 86, Opera ≥ 72 + +## 原理 + +[isomorphic-git](https://github.com/isomorphic-git/isomorphic-git) + [File System API](https://developer.mozilla.org/zh-CN/docs/Web/API/File_System_API) + +## 首次使用 Tips + +如果你本地的资源已经最新,那么可以直接点击“仅 clone / pull”,后续有更新时再用增量更新即可 diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..0e291b720143db5ccd72f1521cadf07bddf78c71 GIT binary patch literal 100691 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 +/// +/// +/// diff --git a/index.html b/index.html new file mode 100644 index 0000000..562de1c --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + MAA Resource Updater + + +
+ + + diff --git a/node/env.d.ts b/node/env.d.ts new file mode 100644 index 0000000..752bd78 --- /dev/null +++ b/node/env.d.ts @@ -0,0 +1,3 @@ +declare module '@isomorphic-git/cors-proxy/middleware' { + export default function corsProxy(): any; +} diff --git a/node/proxy.ts b/node/proxy.ts new file mode 100644 index 0000000..7b2fc40 --- /dev/null +++ b/node/proxy.ts @@ -0,0 +1,16 @@ +import { createServer } from 'http'; +import Express from 'express'; +import gitCorsProxy from '@isomorphic-git/cors-proxy/middleware'; + +export const startProxy = () => { + try { + const app = Express(); + app.use(gitCorsProxy()); + app.on('error', console.error); + const server = createServer(app); + server.on('error', () => {}); + server.listen(9999, () => { + console.log('Git CORS proxy started'); + }); + } catch {} +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..a56b56a --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "maa-resource-updater", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build --force" + }, + "dependencies": { + "@isomorphic-git/lightning-fs": "^4.6.0", + "@vueuse/core": "^11.0.0", + "buffer": "^6.0.3", + "isomorphic-git": "^1.27.1", + "sass": "^1.77.8", + "vue": "^3.4.38" + }, + "devDependencies": { + "@isomorphic-git/cors-proxy": "^2.7.1", + "@tsconfig/node18": "^18.2.4", + "@types/express": "^4.17.21", + "@types/node": "^18.19.44", + "@types/wicg-file-system-access": "^2023.10.5", + "@vitejs/plugin-vue": "^5.1.2", + "@vue/tsconfig": "^0.5.1", + "express": "^4.19.2", + "npm-run-all2": "^6.2.2", + "typescript": "^5.5.4", + "vite": "^5.4.1", + "vite-plugin-comlink": "^5.0.1", + "vue-tsc": "^2.0.29" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..31876112b9aabd26c87a206511515c01cf9f2a0b GIT binary patch literal 16958 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 OZUAC|Kd|)J{eJ=WKQnp& literal 0 HcmV?d00001 diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..8d71783 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/src/assets/main.css b/src/assets/main.css new file mode 100644 index 0000000..d21088a --- /dev/null +++ b/src/assets/main.css @@ -0,0 +1,43 @@ +html, +body { + height: 100%; + width: 100%; + margin: 0; +} + +body { + display: flex; + flex-direction: column; + align-items: center; +} + +#app { + display: flex; + flex-direction: column; + width: 100%; + max-width: 632px; + padding: 16px; + box-sizing: border-box; +} + +h1, +p { + margin-top: 0; +} +pre { + white-space: pre-wrap; +} +button { + user-select: none; +} +a, +a:visited { + color: #0969da; +} +a { + text-decoration: none; +} + +.text-center { + text-align: center; +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..b05b45f --- /dev/null +++ b/src/main.ts @@ -0,0 +1,6 @@ +import './assets/main.css'; + +import { createApp } from 'vue'; +import App from './App.vue'; + +createApp(App).mount('#app'); diff --git a/src/utils/fileSystem.ts b/src/utils/fileSystem.ts new file mode 100644 index 0000000..4b2146b --- /dev/null +++ b/src/utils/fileSystem.ts @@ -0,0 +1,32 @@ +import { shallowRef } from 'vue'; + +export const useDirectoryPicker = () => { + const dirHandle = shallowRef(); + + const pickDir = async (checkFunc?: (handle: FileSystemDirectoryHandle) => Promise) => { + const handle = await window.showDirectoryPicker({ + id: 'maa-root', + mode: 'readwrite', + startIn: 'desktop', + }); + if (checkFunc && !(await checkFunc(handle))) return; + dirHandle.value = handle; + }; + + return { dirHandle, pickDir }; +}; + +const checkDirExist = async (rootHandle: FileSystemDirectoryHandle, name: string) => { + try { + await rootHandle.getDirectoryHandle(name, { create: false }); + return true; + } catch { + return false; + } +}; + +export const checkIsMAARoot = async (dirHandle: FileSystemDirectoryHandle) => { + if (!(await checkDirExist(dirHandle, 'cache'))) return false; + if (!(await checkDirExist(dirHandle, 'resource'))) return false; + return true; +}; diff --git a/src/utils/git.ts b/src/utils/git.ts new file mode 100644 index 0000000..dbd6a1d --- /dev/null +++ b/src/utils/git.ts @@ -0,0 +1,5 @@ +import type { Git } from '@/workers/git'; + +const worker = new ComlinkWorker(new URL('../workers/git.js', import.meta.url)); + +export const createGitClient = (...args: ConstructorParameters) => new worker.Git(...args); diff --git a/src/workers/git.ts b/src/workers/git.ts new file mode 100644 index 0000000..ef83d33 --- /dev/null +++ b/src/workers/git.ts @@ -0,0 +1,211 @@ +import LightningFS, { type PromisifiedFS } from '@isomorphic-git/lightning-fs'; +import http from 'isomorphic-git/http/web'; +import git, { TREE, WORKDIR, type WalkerEntry } from 'isomorphic-git'; +import { Buffer } from 'buffer/'; + +(globalThis as any).Buffer = Buffer; + +const ignoreWalkSet = new Set(['.git', '.gitignore', 'LICENSE', 'README.md']); + +const getFilename = (path: string) => path.split('/').pop()!; + +interface WalkResult { + path: string; + name: string; + type: 'blob' | 'tree'; + entry: WalkerEntry; + children?: WalkResult[]; +} + +export interface GitProgress { + value: number; + desc: string; +} + +export interface GitCommit { + message: string; + sha1: string; +} + +export type OnGitProgress = (progress: GitProgress) => void; + +export type OnGitCommitsUpdate = (commits: GitCommit[]) => void; + +export class Git { + private readonly fs: PromisifiedFS; + private readonly commonOptions: Parameters<(typeof git)['clone']>[0] & Parameters<(typeof git)['pull']>[0]; + + constructor( + private readonly url: string, + private readonly onProgress: OnGitProgress, + private readonly onCommitsUpdate: OnGitCommitsUpdate, + ) { + this.fs = new LightningFS(url).promises; + this.commonOptions = { + fs: this.fs, + http, + dir: '/', + corsProxy: import.meta.env.DEV ? 'http://127.0.0.1:9999' : 'https://mashir0-mrugcp.hf.space', + url, + singleBranch: true, + depth: 1, + author: { + name: '', + email: '', + }, + onProgress: ({ phase, loaded, total }) => { + this.onProgress({ + value: total ? loaded / total : 0, + desc: total ? `${phase} (${loaded}/${total})` : `${phase} (${loaded})`, + }); + }, + }; + this.emitUpdateCommits(); + } + + async update() { + let type = ''; + if (await this.getHeadCommit()) { + console.log('git pull'); + await git.pull(this.commonOptions); + type = 'pull'; + } else { + console.log('git clone'); + await git.clone(this.commonOptions); + type = 'clone'; + } + await this.emitUpdateCommits(); + return type; + } + + clear() { + return new Promise(resolve => { + const req = indexedDB.deleteDatabase(this.url); + req.onsuccess = () => { + this.onCommitsUpdate([]); + resolve(true); + }; + req.onerror = () => { + resolve(false); + }; + }); + } + + async copyToFileSystem(root: FileSystemDirectoryHandle) { + this.onProgress({ value: 0, desc: 'Processing files' }); + let total = 0; + const result: WalkResult = await git.walk({ + fs: this.fs, + dir: '/', + trees: [WORKDIR()], + map: async (path, [entry]) => { + if (!entry) return null; + const name = getFilename(path); + if (ignoreWalkSet.has(name)) return null; + const type = await entry.type(); + if (type === 'blob') total++; + return { + path, + name, + type, + entry, + }; + }, + reduce: this.walkReduce, + }); + await this.copyDir(root, result, { cur: 0, total }); + return { total }; + } + + async copyToFileSystemIncremental(root: FileSystemDirectoryHandle, startCommit: string) { + this.onProgress({ value: 0, desc: 'Processing files' }); + let total = 0; + const headCommit = await this.getHeadCommit(); + if (headCommit === startCommit) return { total }; + const paths: string[] = []; + const result: WalkResult = await git.walk({ + fs: this.fs, + dir: '/', + trees: [TREE({ ref: 'HEAD' }), TREE({ ref: startCommit })], + map: async (path, [entry, pastEntry]) => { + if (!entry) return null; + const name = getFilename(path); + if (ignoreWalkSet.has(name)) return null; + const type = await entry.type(); + if (type === 'blob') { + if (pastEntry) { + const oid = await entry.oid(); + const pastOid = await pastEntry.oid(); + if (oid === pastOid) return null; + } + total++; + paths.push(path); + } + return { + path, + name, + type, + entry, + }; + }, + reduce: this.walkReduce, + }); + await this.copyDir(root, result, { cur: 0, total }); + return { total }; + } + + async getHeadCommit() { + try { + const logs = await git.log({ fs: this.fs, dir: '/', depth: 1 }); + return logs[0]?.commit.tree || null; + } catch { + return null; + } + } + + async getCommitList() { + try { + const logs = await git.log({ fs: this.fs, dir: '/' }); + return logs.map(({ commit: { message, tree } }): GitCommit => ({ message, sha1: tree })); + } catch { + return []; + } + } + + private async walkReduce(parent: WalkResult, children: WalkResult[]) { + if (parent.type === 'blob') return parent; + return Object.assign(parent, { children: children.filter(({ type, children }) => !(type === 'tree' && !children?.length)) }); + } + + private async copyDir(parentHandler: FileSystemDirectoryHandle, parentResult: WalkResult, state: { cur: number; total: number }) { + if (!parentResult.children) return; + await Promise.all( + parentResult.children.map(async result => { + try { + if (result.children) { + const dirHandler = await parentHandler.getDirectoryHandle(result.name, { create: true }); + await this.copyDir(dirHandler, result, state); + } else { + const content = await result.entry.content(); + if (!content) return; + const fileHandler = await parentHandler.getFileHandle(result.name, { create: true }); + const writable = await fileHandler.createWritable(); + await writable.write(content); + await writable.close(); + state.cur++; + this.onProgress({ + value: state.total === 0 ? 1 : state.cur / state.total, + desc: `Write (${state.cur}/${state.total}): ${result.path}`, + }); + } + } catch (error) { + console.error(error); + } + }), + ); + } + + private async emitUpdateCommits() { + this.onCommitsUpdate(await this.getCommitList()); + } +} diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..491e093 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "noEmit": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..80cd7c8 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "extends": "@tsconfig/node18/tsconfig.json", + "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "nightwatch.conf.*", "playwright.config.*", "node/**/*"], + "compilerOptions": { + "composite": true, + "noEmit": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..096b0d1 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,21 @@ +import { fileURLToPath, URL } from 'node:url'; +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import { comlink } from 'vite-plugin-comlink'; +import { startProxy } from './node/proxy'; + +// https://vitejs.dev/config/ +export default defineConfig(({ command }) => { + if (command === 'serve') startProxy(); + return { + plugins: [comlink(), vue()], + worker: { + plugins: () => [comlink()], + }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + }; +});