diff --git a/oafAdmin/.gitignore b/oafAdmin/.gitignore new file mode 100644 index 0000000..f1e61f3 --- /dev/null +++ b/oafAdmin/.gitignore @@ -0,0 +1,23 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/oafAdmin/.prettierrc.js b/oafAdmin/.prettierrc.js new file mode 100644 index 0000000..5df303c --- /dev/null +++ b/oafAdmin/.prettierrc.js @@ -0,0 +1,28 @@ +/* + * .prettierrc.js + * Copyright (C) 2022 veypi + * + * Distributed under terms of the Apache license. + */ + +module.exports = { + printWidth: 100, //单行长度 + tabWidth: 2, //缩进长度 + useTabs: false, //使用空格代替tab缩进 + semi: false, //句末使用分号 + singleQuote: true, //使用单引号 + quoteProps: 'as-needed', //仅在必需时为对象的key添加引号 + jsxSingleQuote: true, // jsx中使用单引号 + trailingComma: 'all', //多行时尽可能打印尾随逗号 + bracketSpacing: true, //在对象前后添加空格-eg: { foo: bar } + jsxBracketSameLine: true, //多属性html标签的‘>’折行放置 + arrowParens: 'always', //单参数箭头函数参数周围使用圆括号-eg: (x) => x + requirePragma: false, //无需顶部注释即可格式化 + insertPragma: false, //在已被preitter格式化的文件顶部加上标注 + proseWrap: 'preserve', //不知道怎么翻译 + htmlWhitespaceSensitivity: 'ignore', //对HTML全局空白不敏感 + vueIndentScriptAndStyle: false, //不对vue中的script及style标签缩进 + endOfLine: 'lf', //结束行形式 + embeddedLanguageFormatting: 'auto', //对引用代码进行格式化 +} + diff --git a/oafAdmin/README.md b/oafAdmin/README.md new file mode 100644 index 0000000..c9b59b9 --- /dev/null +++ b/oafAdmin/README.md @@ -0,0 +1,11 @@ +# Vite starter + +### Elements + +- vue3 +- typescript +- vite +- tailwindcss +- pinia +- animate.css +- diff --git a/oafAdmin/index.html b/oafAdmin/index.html new file mode 100644 index 0000000..dfabbea --- /dev/null +++ b/oafAdmin/index.html @@ -0,0 +1,115 @@ + + + + + + + + OA + + + +
+
+
+
+
+
+
+
Loading... +
+
+ + + + + diff --git a/oafAdmin/package.json b/oafAdmin/package.json new file mode 100644 index 0000000..8188c81 --- /dev/null +++ b/oafAdmin/package.json @@ -0,0 +1,28 @@ +{ + "name": "vite-starter", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@veypi/one-icon": "2", + "animate.css": "^4.1.1", + "mitt": "^3.0.0", + "pinia": "^2.0.13", + "vue": "^3.2.25", + "vue-router": "4" + }, + "devDependencies": { + "@types/node": "^17.0.24", + "@vitejs/plugin-vue": "^2.3.1", + "autoprefixer": "^10.4.4", + "postcss": "^8.4.12", + "tailwindcss": "^3.0.24", + "typescript": "^4.6.2", + "vite": "^2.9.2", + "vue-tsc": "^0.34.6" + } +} diff --git a/oafAdmin/postcss.config.js b/oafAdmin/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/oafAdmin/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/oafAdmin/public/favicon.ico b/oafAdmin/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/oafAdmin/public/favicon.ico differ diff --git a/oafAdmin/src/App.vue b/oafAdmin/src/App.vue new file mode 100644 index 0000000..7516a1f --- /dev/null +++ b/oafAdmin/src/App.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/oafAdmin/src/assets/icon.js b/oafAdmin/src/assets/icon.js new file mode 100644 index 0000000..e22ce1a --- /dev/null +++ b/oafAdmin/src/assets/icon.js @@ -0,0 +1 @@ +!function(c){var l,t,a,h,o,e='',i=(i=document.getElementsByTagName("script"))[i.length-1].getAttribute("data-injectcss"),d=function(c,l){l.parentNode.insertBefore(c,l)};if(i&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}function s(){o||(o=!0,a())}function n(){try{h.documentElement.doScroll("left")}catch(c){return void setTimeout(n,50)}s()}l=function(){var c,l;(l=document.createElement("div")).innerHTML=e,e=null,(c=l.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",l=c,(c=document.body).firstChild?d(l,c.firstChild):c.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),l()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(a=l,h=c.document,o=!1,n(),h.onreadystatechange=function(){"complete"==h.readyState&&(h.onreadystatechange=null,s())})}(window); \ No newline at end of file diff --git a/oafAdmin/src/assets/logo.png b/oafAdmin/src/assets/logo.png new file mode 100644 index 0000000..f3d2503 Binary files /dev/null and b/oafAdmin/src/assets/logo.png differ diff --git a/oafAdmin/src/bus/index.ts b/oafAdmin/src/bus/index.ts new file mode 100644 index 0000000..3af82f0 --- /dev/null +++ b/oafAdmin/src/bus/index.ts @@ -0,0 +1,10 @@ +/* +* @name: index +* @author: veypi +* @date: 2022-04-16 16:37 +* @description:index +*/ + +import mitt from 'mitt' + +export default mitt() diff --git a/oafAdmin/src/components/frame.vue b/oafAdmin/src/components/frame.vue new file mode 100644 index 0000000..5cb8533 --- /dev/null +++ b/oafAdmin/src/components/frame.vue @@ -0,0 +1,105 @@ + + + + + + diff --git a/oafAdmin/src/components/fullscreen/fullscreen.vue b/oafAdmin/src/components/fullscreen/fullscreen.vue new file mode 100644 index 0000000..fb348d1 --- /dev/null +++ b/oafAdmin/src/components/fullscreen/fullscreen.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/oafAdmin/src/components/fullscreen/index.ts b/oafAdmin/src/components/fullscreen/index.ts new file mode 100644 index 0000000..5c4a3c6 --- /dev/null +++ b/oafAdmin/src/components/fullscreen/index.ts @@ -0,0 +1,2 @@ +import fullscreen from './fullscreen.vue' +export default fullscreen diff --git a/oafAdmin/src/env.d.ts b/oafAdmin/src/env.d.ts new file mode 100644 index 0000000..aafef95 --- /dev/null +++ b/oafAdmin/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/oafAdmin/src/index.css b/oafAdmin/src/index.css new file mode 100644 index 0000000..5b58993 --- /dev/null +++ b/oafAdmin/src/index.css @@ -0,0 +1,14 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + transition: all 0.2s linear; + --base-color: #000; + --base-bg: #fff; +} + +:root[theme=dark] { + --base-color: #fff; + --base-bg: #000; +} diff --git a/oafAdmin/src/libs/index.ts b/oafAdmin/src/libs/index.ts new file mode 100644 index 0000000..964ae75 --- /dev/null +++ b/oafAdmin/src/libs/index.ts @@ -0,0 +1,10 @@ +/* +* @name: index +* @author: veypi +* @date: 2021-11-17 22:22 +* @description:index +* @update: 2021-11-17 22:22 +*/ +import u from './util' + +export const util = u diff --git a/oafAdmin/src/libs/util.ts b/oafAdmin/src/libs/util.ts new file mode 100644 index 0000000..2272fe7 --- /dev/null +++ b/oafAdmin/src/libs/util.ts @@ -0,0 +1,75 @@ +import axios from 'axios' + +function padLeftZero(str: string): string { + return ('00' + str).substr(str.length) +} + +const util = { + goto(url: string) { + window.open(url, '_blank') + }, + title: function (title: string) { + window.document.title = title ? title + ' - oa' : 'veypi project' + }, + getCookie(name: string) { + const reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)') + const arr = document.cookie.match(reg) + if (arr) { + return unescape(arr[2]) + } else return null + }, + delCookie(name: string) { + const exp = new Date() + exp.setTime(exp.getTime() - 1) + const cval = this.getCookie(name) + if (cval !== null) { + document.cookie = name + '=' + cval + ';expires=' + exp.toLocaleString() + } + }, + setCookie(name: string, value: string, time: number) { + const exp = new Date() + exp.setTime(exp.getTime() + time) + document.cookie = + name + '=' + escape(value) + ';expires=' + exp.toLocaleString() + }, + getToken() { + return localStorage.auth_token + }, + addTokenOf(url: string) { + return url + '?auth_token=' + encodeURIComponent(this.getToken()) + }, + checkLogin() { + // return parseInt(this.getCookie('stat')) === 1 + return Boolean(localStorage.auth_token) + }, + + formatDate(date: Date, fmt: string) { + if (/(y+)/.test(fmt)) { + fmt = fmt.replace( + RegExp.$1, + (date.getFullYear() + '').substr(4 - RegExp.$1.length), + ) + } + const o = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'h+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds(), + } + for (const k in o) { + if (new RegExp(`(${k})`).test(fmt)) { + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore + const str = o[k] + '' + fmt = fmt.replace( + RegExp.$1, + RegExp.$1.length === 1 ? str : padLeftZero(str), + ) + } + } + return fmt + }, +} + +export default util diff --git a/oafAdmin/src/libs/webdav/auth/basic.ts b/oafAdmin/src/libs/webdav/auth/basic.ts new file mode 100644 index 0000000..98ea430 --- /dev/null +++ b/oafAdmin/src/libs/webdav/auth/basic.ts @@ -0,0 +1,7 @@ +import { toBase64 } from "../tools/encode"; +import { AuthHeader } from "../types"; + +export function generateBasicAuthHeader(username: string, password: string): AuthHeader { + const encoded = toBase64(`${username}:${password}`); + return `Basic ${encoded}`; +} diff --git a/oafAdmin/src/libs/webdav/auth/digest.ts b/oafAdmin/src/libs/webdav/auth/digest.ts new file mode 100644 index 0000000..63abdaa --- /dev/null +++ b/oafAdmin/src/libs/webdav/auth/digest.ts @@ -0,0 +1,82 @@ +import md5 from "md5"; +import { ha1Compute } from "../tools/crypto"; +import { DigestContext, Response } from "../types"; + +const NONCE_CHARS = "abcdef0123456789"; +const NONCE_SIZE = 32; + +export function createDigestContext(username: string, password: string): DigestContext { + return { username, password, nc: 0, algorithm: "md5", hasDigestAuth: false }; +} + +export function generateDigestAuthHeader(options, digest: DigestContext): string { + const url = options.url.replace("//", ""); + const uri = url.indexOf("/") == -1 ? "/" : url.slice(url.indexOf("/")); + const method = options.method ? options.method.toUpperCase() : "GET"; + const qop = /(^|,)\s*auth\s*($|,)/.test(digest.qop) ? "auth" : false; + const ncString = `00000000${digest.nc}`.slice(-8); + const ha1 = ha1Compute( + digest.algorithm, + digest.username, + digest.realm, + digest.password, + digest.nonce, + digest.cnonce + ); + const ha2 = md5(`${method}:${uri}`); + const digestResponse = qop + ? md5(`${ha1}:${digest.nonce}:${ncString}:${digest.cnonce}:${qop}:${ha2}`) + : md5(`${ha1}:${digest.nonce}:${ha2}`); + + const authValues = { + username: digest.username, + realm: digest.realm, + nonce: digest.nonce, + uri, + qop, + response: digestResponse, + nc: ncString, + cnonce: digest.cnonce, + algorithm: digest.algorithm, + opaque: digest.opaque + }; + + const authHeader = []; + for (const k in authValues) { + if (authValues[k]) { + if (k === "qop" || k === "nc" || k === "algorithm") { + authHeader.push(`${k}=${authValues[k]}`); + } else { + authHeader.push(`${k}="${authValues[k]}"`); + } + } + } + + return `Digest ${authHeader.join(", ")}`; +} + +function makeNonce(): string { + let uid = ""; + for (let i = 0; i < NONCE_SIZE; ++i) { + uid = `${uid}${NONCE_CHARS[Math.floor(Math.random() * NONCE_CHARS.length)]}`; + } + return uid; +} + +export function parseDigestAuth(response: Response, _digest: DigestContext): boolean { + const authHeader = response.headers["www-authenticate"] || ""; + if (authHeader.split(/\s/)[0].toLowerCase() !== "digest") { + return false; + } + const re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi; + for (;;) { + const match = re.exec(authHeader); + if (!match) { + break; + } + _digest[match[1]] = match[2] || match[3]; + } + _digest.nc += 1; + _digest.cnonce = makeNonce(); + return true; +} diff --git a/oafAdmin/src/libs/webdav/auth/index.ts b/oafAdmin/src/libs/webdav/auth/index.ts new file mode 100644 index 0000000..3758b29 --- /dev/null +++ b/oafAdmin/src/libs/webdav/auth/index.ts @@ -0,0 +1,36 @@ +import { Layerr } from "layerr"; +import { createDigestContext } from "./digest"; +import { generateBasicAuthHeader } from "./basic"; +import { generateTokenAuthHeader } from "./oauth"; +import { AuthType, ErrorCode, OAuthToken, WebDAVClientContext } from "../types"; + +export function setupAuth( + context: WebDAVClientContext, + username: string, + password: string, + oauthToken: OAuthToken +): void { + switch (context.authType) { + case AuthType.Digest: + context.digest = createDigestContext(username, password); + break; + case AuthType.None: + // Do nothing + break; + case AuthType.Password: + context.headers.Authorization = generateBasicAuthHeader(username, password); + break; + case AuthType.Token: + context.headers.Authorization = generateTokenAuthHeader(oauthToken); + break; + default: + throw new Layerr( + { + info: { + code: ErrorCode.InvalidAuthType + } + }, + `Invalid auth type: ${context.authType}` + ); + } +} diff --git a/oafAdmin/src/libs/webdav/auth/oauth.ts b/oafAdmin/src/libs/webdav/auth/oauth.ts new file mode 100644 index 0000000..7261c91 --- /dev/null +++ b/oafAdmin/src/libs/webdav/auth/oauth.ts @@ -0,0 +1,5 @@ +import { AuthHeader, OAuthToken } from "../types"; + +export function generateTokenAuthHeader(token: OAuthToken): AuthHeader { + return `${token.token_type} ${token.access_token}`; +} diff --git a/oafAdmin/src/libs/webdav/compat/arrayBuffer.ts b/oafAdmin/src/libs/webdav/compat/arrayBuffer.ts new file mode 100644 index 0000000..750dcfc --- /dev/null +++ b/oafAdmin/src/libs/webdav/compat/arrayBuffer.ts @@ -0,0 +1,10 @@ +const hasArrayBuffer = typeof ArrayBuffer === "function"; +const { toString: objToString } = Object.prototype; + +// Taken from: https://github.com/fengyuanchen/is-array-buffer/blob/master/src/index.js +export function isArrayBuffer(value: any): boolean { + return ( + hasArrayBuffer && + (value instanceof ArrayBuffer || objToString.call(value) === "[object ArrayBuffer]") + ); +} diff --git a/oafAdmin/src/libs/webdav/compat/buffer.ts b/oafAdmin/src/libs/webdav/compat/buffer.ts new file mode 100644 index 0000000..5133dd9 --- /dev/null +++ b/oafAdmin/src/libs/webdav/compat/buffer.ts @@ -0,0 +1,8 @@ +export function isBuffer(value: any): boolean { + return ( + value != null && + value.constructor != null && + typeof value.constructor.isBuffer === "function" && + value.constructor.isBuffer(value) + ); +} diff --git a/oafAdmin/src/libs/webdav/compat/patcher.ts b/oafAdmin/src/libs/webdav/compat/patcher.ts new file mode 100644 index 0000000..adfb722 --- /dev/null +++ b/oafAdmin/src/libs/webdav/compat/patcher.ts @@ -0,0 +1,10 @@ +import HotPatcher from "hot-patcher"; + +let __patcher: HotPatcher = null; + +export function getPatcher(): HotPatcher { + if (!__patcher) { + __patcher = new HotPatcher(); + } + return __patcher; +} diff --git a/oafAdmin/src/libs/webdav/factory.ts b/oafAdmin/src/libs/webdav/factory.ts new file mode 100644 index 0000000..6a4ee6e --- /dev/null +++ b/oafAdmin/src/libs/webdav/factory.ts @@ -0,0 +1,114 @@ +import Stream from "stream"; +import { extractURLPath } from "./tools/url"; +import { setupAuth } from "./auth/index"; +import { copyFile } from "./operations/copyFile"; +import { createDirectory } from "./operations/createDirectory"; +import { createReadStream, createWriteStream } from "./operations/createStream"; +import { customRequest } from "./operations/customRequest"; +import { deleteFile } from "./operations/deleteFile"; +import { exists } from "./operations/exists"; +import { getDirectoryContents } from "./operations/directoryContents"; +import { getFileContents, getFileDownloadLink } from "./operations/getFileContents"; +import { lock, unlock } from "./operations/lock"; +import { getQuota } from "./operations/getQuota"; +import { getStat } from "./operations/stat"; +import { moveFile } from "./operations/moveFile"; +import { getFileUploadLink, putFileContents } from "./operations/putFileContents"; +import { + AuthType, + BufferLike, + CreateReadStreamOptions, + CreateWriteStreamCallback, + CreateWriteStreamOptions, + GetDirectoryContentsOptions, + GetFileContentsOptions, + GetQuotaOptions, + Headers, + LockOptions, + PutFileContentsOptions, + RequestOptionsCustom, + StatOptions, + WebDAVClient, + WebDAVClientContext, + WebDAVClientOptions, + WebDAVMethodOptions +} from "./types"; + +const DEFAULT_CONTACT_HREF = + "https://github.com/perry-mitchell/webdav-client/blob/master/LOCK_CONTACT.md"; + +export function createClient(remoteURL: string, options: WebDAVClientOptions = {}): WebDAVClient { + const { + authType: authTypeRaw = null, + contactHref = DEFAULT_CONTACT_HREF, + headers = {}, + httpAgent, + httpsAgent, + maxBodyLength, + maxContentLength, + password, + token, + username, + withCredentials + } = options; + let authType = authTypeRaw; + if (!authType) { + authType = username || password ? AuthType.Password : AuthType.None; + } + const context: WebDAVClientContext = { + authType, + contactHref, + headers: Object.assign({}, headers), + httpAgent, + httpsAgent, + maxBodyLength, + maxContentLength, + remotePath: extractURLPath(remoteURL), + remoteURL, + password, + token, + username, + withCredentials + }; + setupAuth(context, username, password, token); + return { + copyFile: (filename: string, destination: string, options?: WebDAVMethodOptions) => + copyFile(context, filename, destination, options), + createDirectory: (path: string, options?: WebDAVMethodOptions) => + createDirectory(context, path, options), + createReadStream: (filename: string, options?: CreateReadStreamOptions) => + createReadStream(context, filename, options), + createWriteStream: ( + filename: string, + options?: CreateWriteStreamOptions, + callback?: CreateWriteStreamCallback + ) => createWriteStream(context, filename, options, callback), + customRequest: (path: string, requestOptions: RequestOptionsCustom) => + customRequest(context, path, requestOptions), + deleteFile: (filename: string, options?: WebDAVMethodOptions) => + deleteFile(context, filename, options), + exists: (path: string, options?: WebDAVMethodOptions) => exists(context, path, options), + getDirectoryContents: (path: string, options?: GetDirectoryContentsOptions) => + getDirectoryContents(context, path, options), + getFileContents: (filename: string, options?: GetFileContentsOptions) => + getFileContents(context, filename, options), + getFileDownloadLink: (filename: string) => getFileDownloadLink(context, filename), + getFileUploadLink: (filename: string) => getFileUploadLink(context, filename), + getHeaders: () => Object.assign({}, context.headers), + getQuota: (options?: GetQuotaOptions) => getQuota(context, options), + lock: (path: string, options?: LockOptions) => lock(context, path, options), + moveFile: (filename: string, destinationFilename: string, options?: WebDAVMethodOptions) => + moveFile(context, filename, destinationFilename, options), + putFileContents: ( + filename: string, + data: string | BufferLike | Stream.Readable, + options?: PutFileContentsOptions + ) => putFileContents(context, filename, data, options), + setHeaders: (headers: Headers) => { + context.headers = Object.assign({}, headers); + }, + stat: (path: string, options?: StatOptions) => getStat(context, path, options), + unlock: (path: string, token: string, options?: WebDAVMethodOptions) => + unlock(context, path, token, options) + }; +} diff --git a/oafAdmin/src/libs/webdav/index.ts b/oafAdmin/src/libs/webdav/index.ts new file mode 100644 index 0000000..defd0f6 --- /dev/null +++ b/oafAdmin/src/libs/webdav/index.ts @@ -0,0 +1,5 @@ +export { createClient } from "./factory"; +export { getPatcher } from "./compat/patcher"; +export * from "./types"; + +export { parseStat, parseXML } from "./tools/dav"; diff --git a/oafAdmin/src/libs/webdav/operations/copyFile.ts b/oafAdmin/src/libs/webdav/operations/copyFile.ts new file mode 100644 index 0000000..cd12627 --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/copyFile.ts @@ -0,0 +1,26 @@ +import { joinURL } from "../tools/url"; +import { encodePath } from "../tools/path"; +import { request, prepareRequestOptions } from "../request"; +import { handleResponseCode } from "../response"; +import { WebDAVClientContext, WebDAVMethodOptions } from "../types"; + +export async function copyFile( + context: WebDAVClientContext, + filename: string, + destination: string, + options: WebDAVMethodOptions = {} +): Promise { + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(filename)), + method: "COPY", + headers: { + Destination: joinURL(context.remoteURL, encodePath(destination)) + } + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); +} diff --git a/oafAdmin/src/libs/webdav/operations/createDirectory.ts b/oafAdmin/src/libs/webdav/operations/createDirectory.ts new file mode 100644 index 0000000..f75b06b --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/createDirectory.ts @@ -0,0 +1,81 @@ +import { joinURL } from "../tools/url"; +import { encodePath, getAllDirectories, normalisePath } from "../tools/path"; +import { request, prepareRequestOptions } from "../request"; +import { handleResponseCode } from "../response"; +import { getStat } from "./stat"; +import { CreateDirectoryOptions, FileStat, WebDAVClientContext, WebDAVClientError } from "../types"; + +export async function createDirectory( + context: WebDAVClientContext, + dirPath: string, + options: CreateDirectoryOptions = {} +): Promise { + if (options.recursive === true) return createDirectoryRecursively(context, dirPath, options); + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, ensureCollectionPath(encodePath(dirPath))), + method: "MKCOL" + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); +} + +/** + * Ensure the path is a proper "collection" path by ensuring it has a trailing "/". + * The proper format of collection according to the specification does contain the trailing slash. + * http://www.webdav.org/specs/rfc4918.html#rfc.section.5.2 + * @param path Path of the collection + * @return string Path of the collection with appended trailing "/" in case the `path` does not have it. + */ +function ensureCollectionPath(path: string): string { + if (!path.endsWith("/")) { + return path + "/"; + } + return path; +} + +async function createDirectoryRecursively( + context: WebDAVClientContext, + dirPath: string, + options: CreateDirectoryOptions = {} +): Promise { + const paths = getAllDirectories(normalisePath(dirPath)); + paths.sort((a, b) => { + if (a.length > b.length) { + return 1; + } else if (b.length > a.length) { + return -1; + } + return 0; + }); + let creating: boolean = false; + for (const testPath of paths) { + if (creating) { + await createDirectory(context, testPath, { + ...options, + recursive: false + }); + continue; + } + try { + const testStat = (await getStat(context, testPath)) as FileStat; + if (testStat.type !== "directory") { + throw new Error(`Path includes a file: ${dirPath}`); + } + } catch (err) { + const error = err as WebDAVClientError; + if (error.status === 404) { + creating = true; + await createDirectory(context, testPath, { + ...options, + recursive: false + }); + } else { + throw err; + } + } + } +} diff --git a/oafAdmin/src/libs/webdav/operations/createStream.ts b/oafAdmin/src/libs/webdav/operations/createStream.ts new file mode 100644 index 0000000..eea29a8 --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/createStream.ts @@ -0,0 +1,109 @@ +import Stream from "stream"; +import { joinURL } from "../tools/url"; +import { encodePath } from "../tools/path"; +import { request, prepareRequestOptions } from "../request"; +import { handleResponseCode } from "../response"; +import { + CreateReadStreamOptions, + CreateWriteStreamCallback, + CreateWriteStreamOptions, + Headers, + WebDAVClientContext, + WebDAVClientError +} from "../types"; + +const NOOP = () => {}; + +export function createReadStream( + context: WebDAVClientContext, + filePath: string, + options: CreateReadStreamOptions = {} +): Stream.Readable { + const PassThroughStream = Stream.PassThrough; + const outStream = new PassThroughStream(); + getFileStream(context, filePath, options) + .then(stream => { + stream.pipe(outStream); + }) + .catch(err => { + outStream.emit("error", err); + }); + return outStream; +} + +export function createWriteStream( + context: WebDAVClientContext, + filePath: string, + options: CreateWriteStreamOptions = {}, + callback: CreateWriteStreamCallback = NOOP +): Stream.Writable { + const PassThroughStream = Stream.PassThrough; + const writeStream = new PassThroughStream(); + const headers = {}; + if (options.overwrite === false) { + headers["If-None-Match"] = "*"; + } + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(filePath)), + method: "PUT", + headers, + data: writeStream, + maxRedirects: 0 + }, + context, + options + ); + request(requestOptions) + .then(response => handleResponseCode(context, response)) + .then(response => { + // Fire callback asynchronously to avoid errors + setTimeout(() => { + callback(response); + }, 0); + }) + .catch(err => { + writeStream.emit("error", err); + }); + return writeStream; +} + +async function getFileStream( + context: WebDAVClientContext, + filePath: string, + options: CreateReadStreamOptions = {} +): Promise { + const headers: Headers = {}; + if (typeof options.range === "object" && typeof options.range.start === "number") { + let rangeHeader = `bytes=${options.range.start}-`; + if (typeof options.range.end === "number") { + rangeHeader = `${rangeHeader}${options.range.end}`; + } + headers.Range = rangeHeader; + } + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(filePath)), + method: "GET", + headers, + responseType: "stream" + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); + if (headers.Range && response.status !== 206) { + const responseError: WebDAVClientError = new Error( + `Invalid response code for partial request: ${response.status}` + ); + responseError.status = response.status; + throw responseError; + } + if (options.callback) { + setTimeout(() => { + options.callback(response); + }, 0); + } + return response.data as Stream.Readable; +} diff --git a/oafAdmin/src/libs/webdav/operations/customRequest.ts b/oafAdmin/src/libs/webdav/operations/customRequest.ts new file mode 100644 index 0000000..cc31dba --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/customRequest.ts @@ -0,0 +1,19 @@ +import { joinURL } from "../tools/url"; +import { encodePath } from "../tools/path"; +import { request, prepareRequestOptions } from "../request"; +import { handleResponseCode } from "../response"; +import { RequestOptionsCustom, Response, WebDAVClientContext } from "../types"; + +export async function customRequest( + context: WebDAVClientContext, + remotePath: string, + requestOptions: RequestOptionsCustom +): Promise { + if (!requestOptions.url) { + requestOptions.url = joinURL(context.remoteURL, encodePath(remotePath)); + } + const finalOptions = prepareRequestOptions(requestOptions, context, {}); + const response = await request(finalOptions); + handleResponseCode(context, response); + return response; +} diff --git a/oafAdmin/src/libs/webdav/operations/deleteFile.ts b/oafAdmin/src/libs/webdav/operations/deleteFile.ts new file mode 100644 index 0000000..fd3f509 --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/deleteFile.ts @@ -0,0 +1,22 @@ +import { joinURL } from "../tools/url"; +import { encodePath } from "../tools/path"; +import { request, prepareRequestOptions } from "../request"; +import { handleResponseCode } from "../response"; +import { WebDAVClientContext, WebDAVMethodOptions } from "../types"; + +export async function deleteFile( + context: WebDAVClientContext, + filename: string, + options: WebDAVMethodOptions = {} +): Promise { + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(filename)), + method: "DELETE" + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); +} diff --git a/oafAdmin/src/libs/webdav/operations/directoryContents.ts b/oafAdmin/src/libs/webdav/operations/directoryContents.ts new file mode 100644 index 0000000..8988e16 --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/directoryContents.ts @@ -0,0 +1,78 @@ +import pathPosix from "path-posix"; +import { joinURL, normaliseHREF } from "../tools/url"; +import { encodePath, normalisePath } from "../tools/path"; +import { parseXML, prepareFileFromProps } from "../tools/dav"; +import { request, prepareRequestOptions } from "../request"; +import { handleResponseCode, processGlobFilter, processResponsePayload } from "../response"; +import { + DAVResult, + FileStat, + GetDirectoryContentsOptions, + ResponseDataDetailed, + WebDAVClientContext +} from "../types"; + +export async function getDirectoryContents( + context: WebDAVClientContext, + remotePath: string, + options: GetDirectoryContentsOptions = {} +): Promise | ResponseDataDetailed>> { + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(remotePath), "/"), + method: "PROPFIND", + headers: { + Accept: "text/plain", + Depth: options.deep ? "infinity" : "1" + }, + responseType: "text" + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); + const davResp = await parseXML(response.data as string); + let files = getDirectoryFiles(davResp, context.remotePath, remotePath, options.details); + if (options.glob) { + files = processGlobFilter(files, options.glob); + } + return processResponsePayload(response, files, options.details); +} + +function getDirectoryFiles( + result: DAVResult, + serverBasePath: string, + requestPath: string, + isDetailed: boolean = false +): Array { + const serverBase = pathPosix.join(serverBasePath, "/"); + // Extract the response items (directory contents) + const { + multistatus: { response: responseItems } + } = result; + return ( + responseItems + // Map all items to a consistent output structure (results) + .map(item => { + // HREF is the file path (in full) + const href = normaliseHREF(item.href); + // Each item should contain a stat object + const { + propstat: { prop: props } + } = item; + // Process the true full filename (minus the base server path) + const filename = + serverBase === "/" + ? decodeURIComponent(normalisePath(href)) + : decodeURIComponent(normalisePath(pathPosix.relative(serverBase, href))); + return prepareFileFromProps(props, filename, isDetailed); + }) + // Filter out the item pointing to the current directory (not needed) + .filter( + item => + item.basename && + (item.type === "file" || item.filename !== requestPath.replace(/\/$/, "")) + ) + ); +} diff --git a/oafAdmin/src/libs/webdav/operations/exists.ts b/oafAdmin/src/libs/webdav/operations/exists.ts new file mode 100644 index 0000000..6e79b45 --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/exists.ts @@ -0,0 +1,18 @@ +import { getStat } from "./stat"; +import { WebDAVClientContext, WebDAVMethodOptions } from "../types"; + +export async function exists( + context: WebDAVClientContext, + remotePath: string, + options: WebDAVMethodOptions = {} +): Promise { + try { + await getStat(context, remotePath, options); + return true; + } catch (err) { + if (err.status === 404) { + return false; + } + throw err; + } +} diff --git a/oafAdmin/src/libs/webdav/operations/getFileContents.ts b/oafAdmin/src/libs/webdav/operations/getFileContents.ts new file mode 100644 index 0000000..4373f78 --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/getFileContents.ts @@ -0,0 +1,102 @@ +import { Layerr } from "layerr"; +import { joinURL } from "../tools/url"; +import { encodePath } from "../tools/path"; +import { fromBase64 } from "../tools/encode"; +import { request, prepareRequestOptions } from "../request"; +import { handleResponseCode, processResponsePayload } from "../response"; +import { + AuthType, + BufferLike, + ErrorCode, + GetFileContentsOptions, + ResponseDataDetailed, + WebDAVClientContext +} from "../types"; + +const TRANSFORM_RETAIN_FORMAT = (v: any) => v; + +export async function getFileContents( + context: WebDAVClientContext, + filePath: string, + options: GetFileContentsOptions = {} +): Promise> { + const { format = "binary" } = options; + if (format !== "binary" && format !== "text") { + throw new Layerr( + { + info: { + code: ErrorCode.InvalidOutputFormat + } + }, + `Invalid output format: ${format}` + ); + } + return format === "text" + ? getFileContentsString(context, filePath, options) + : getFileContentsBuffer(context, filePath, options); +} + +async function getFileContentsBuffer( + context: WebDAVClientContext, + filePath: string, + options: GetFileContentsOptions = {} +): Promise> { + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(filePath)), + method: "GET", + responseType: "arraybuffer" + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); + return processResponsePayload(response, response.data as BufferLike, options.details); +} + +async function getFileContentsString( + context: WebDAVClientContext, + filePath: string, + options: GetFileContentsOptions = {} +): Promise> { + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(filePath)), + method: "GET", + responseType: "text", + transformResponse: [TRANSFORM_RETAIN_FORMAT] + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); + return processResponsePayload(response, response.data as string, options.details); +} + +export function getFileDownloadLink(context: WebDAVClientContext, filePath: string): string { + let url = joinURL(context.remoteURL, encodePath(filePath)); + const protocol = /^https:/i.test(url) ? "https" : "http"; + switch (context.authType) { + case AuthType.None: + // Do nothing + break; + case AuthType.Password: { + const authPart = context.headers.Authorization.replace(/^Basic /i, "").trim(); + const authContents = fromBase64(authPart); + url = url.replace(/^https?:\/\//, `${protocol}://${authContents}@`); + break; + } + default: + throw new Layerr( + { + info: { + code: ErrorCode.LinkUnsupportedAuthType + } + }, + `Unsupported auth type for file link: ${context.authType}` + ); + } + return url; +} diff --git a/oafAdmin/src/libs/webdav/operations/getQuota.ts b/oafAdmin/src/libs/webdav/operations/getQuota.ts new file mode 100644 index 0000000..70b8206 --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/getQuota.ts @@ -0,0 +1,30 @@ +import { prepareRequestOptions, request } from "../request"; +import { handleResponseCode, processResponsePayload } from "../response"; +import { parseXML } from "../tools/dav"; +import { joinURL } from "../tools/url"; +import { parseQuota } from "../tools/quota"; +import { DiskQuota, GetQuotaOptions, ResponseDataDetailed, WebDAVClientContext } from "../types"; + +export async function getQuota( + context: WebDAVClientContext, + options: GetQuotaOptions = {} +): Promise> { + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, "/"), + method: "PROPFIND", + headers: { + Accept: "text/plain", + Depth: "0" + }, + responseType: "text" + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); + const result = await parseXML(response.data as string); + const quota = parseQuota(result); + return processResponsePayload(response, quota, options.details); +} diff --git a/oafAdmin/src/libs/webdav/operations/lock.ts b/oafAdmin/src/libs/webdav/operations/lock.ts new file mode 100644 index 0000000..316a357 --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/lock.ts @@ -0,0 +1,79 @@ +import nestedProp from "nested-property"; +import { joinURL } from "../tools/url"; +import { encodePath } from "../tools/path"; +import { generateLockXML, parseGenericResponse } from "../tools/xml"; +import { request, prepareRequestOptions } from "../request"; +import { createErrorFromResponse, handleResponseCode } from "../response"; +import { + Headers, + LockOptions, + LockResponse, + WebDAVClientContext, + WebDAVMethodOptions +} from "../types"; + +const DEFAULT_TIMEOUT = "Infinite, Second-4100000000"; + +export async function lock( + context: WebDAVClientContext, + path: string, + options: LockOptions = {} +): Promise { + const { refreshToken, timeout = DEFAULT_TIMEOUT } = options; + const headers: Headers = { + Accept: "text/plain,application/xml", + Timeout: timeout + }; + if (refreshToken) { + headers.If = refreshToken; + } + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(path)), + method: "LOCK", + headers, + data: generateLockXML(context.contactHref), + responseType: "text" + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); + const lockPayload = parseGenericResponse(response.data as string); + const token = nestedProp.get(lockPayload, "prop.lockdiscovery.activelock.locktoken.href"); + const serverTimeout = nestedProp.get(lockPayload, "prop.lockdiscovery.activelock.timeout"); + if (!token) { + const err = createErrorFromResponse(response, "No lock token received: "); + throw err; + } + return { + token, + serverTimeout + }; +} + +export async function unlock( + context: WebDAVClientContext, + path: string, + token: string, + options: WebDAVMethodOptions = {} +): Promise { + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(path)), + method: "UNLOCK", + headers: { + "Lock-Token": token + } + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); + if (response.status !== 204 && response.status !== 200) { + const err = createErrorFromResponse(response); + throw err; + } +} diff --git a/oafAdmin/src/libs/webdav/operations/moveFile.ts b/oafAdmin/src/libs/webdav/operations/moveFile.ts new file mode 100644 index 0000000..f0d67c5 --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/moveFile.ts @@ -0,0 +1,26 @@ +import { joinURL } from "../tools/url"; +import { encodePath } from "../tools/path"; +import { request, prepareRequestOptions } from "../request"; +import { handleResponseCode } from "../response"; +import { WebDAVClientContext, WebDAVMethodOptions } from "../types"; + +export async function moveFile( + context: WebDAVClientContext, + filename: string, + destination: string, + options: WebDAVMethodOptions = {} +): Promise { + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(filename)), + method: "MOVE", + headers: { + Destination: joinURL(context.remoteURL, encodePath(destination)) + } + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); +} diff --git a/oafAdmin/src/libs/webdav/operations/putFileContents.ts b/oafAdmin/src/libs/webdav/operations/putFileContents.ts new file mode 100644 index 0000000..b4aa784 --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/putFileContents.ts @@ -0,0 +1,94 @@ +import { Layerr } from "layerr"; +import Stream from "stream"; +import { fromBase64 } from "../tools/encode"; +import { joinURL } from "../tools/url"; +import { encodePath } from "../tools/path"; +import { request, prepareRequestOptions } from "../request"; +import { handleResponseCode } from "../response"; +import { calculateDataLength } from "../tools/size"; +import { + AuthType, + BufferLike, + ErrorCode, + Headers, + PutFileContentsOptions, + WebDAVClientContext, + WebDAVClientError +} from "../types"; + +declare var WEB: boolean; + +export async function putFileContents( + context: WebDAVClientContext, + filePath: string, + data: string | BufferLike | Stream.Readable, + options: PutFileContentsOptions = {} +): Promise { + const { contentLength = true, overwrite = true } = options; + const headers: Headers = { + "Content-Type": "application/octet-stream" + }; + if (typeof WEB === "undefined") { + // Skip, no content-length + } else if (contentLength === false) { + // Skip, disabled + } else if (typeof contentLength === "number") { + headers["Content-Length"] = `${contentLength}`; + } else { + headers["Content-Length"] = `${calculateDataLength(data as string | BufferLike)}`; + } + if (!overwrite) { + headers["If-None-Match"] = "*"; + } + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(filePath)), + method: "PUT", + headers, + data + }, + context, + options + ); + const response = await request(requestOptions); + try { + handleResponseCode(context, response); + } catch (err) { + const error = err as WebDAVClientError; + if (error.status === 412 && !overwrite) { + return false; + } else { + throw error; + } + } + return true; +} + +export function getFileUploadLink(context: WebDAVClientContext, filePath: string): string { + let url: string = `${joinURL( + context.remoteURL, + encodePath(filePath) + )}?Content-Type=application/octet-stream`; + const protocol = /^https:/i.test(url) ? "https" : "http"; + switch (context.authType) { + case AuthType.None: + // Do nothing + break; + case AuthType.Password: { + const authPart = context.headers.Authorization.replace(/^Basic /i, "").trim(); + const authContents = fromBase64(authPart); + url = url.replace(/^https?:\/\//, `${protocol}://${authContents}@`); + break; + } + default: + throw new Layerr( + { + info: { + code: ErrorCode.LinkUnsupportedAuthType + } + }, + `Unsupported auth type for file link: ${context.authType}` + ); + } + return url; +} diff --git a/oafAdmin/src/libs/webdav/operations/stat.ts b/oafAdmin/src/libs/webdav/operations/stat.ts new file mode 100644 index 0000000..8ad7367 --- /dev/null +++ b/oafAdmin/src/libs/webdav/operations/stat.ts @@ -0,0 +1,32 @@ +import { parseStat, parseXML } from "../tools/dav"; +import { joinURL } from "../tools/url"; +import { encodePath } from "../tools/path"; +import { request, prepareRequestOptions } from "../request"; +import { handleResponseCode, processResponsePayload } from "../response"; +import { FileStat, ResponseDataDetailed, StatOptions, WebDAVClientContext } from "../types"; + +export async function getStat( + context: WebDAVClientContext, + filename: string, + options: StatOptions = {} +): Promise> { + const { details: isDetailed = false } = options; + const requestOptions = prepareRequestOptions( + { + url: joinURL(context.remoteURL, encodePath(filename)), + method: "PROPFIND", + headers: { + Accept: "text/plain,application/xml", + Depth: "0" + }, + responseType: "text" + }, + context, + options + ); + const response = await request(requestOptions); + handleResponseCode(context, response); + const result = await parseXML(response.data as string); + const stat = parseStat(result, filename, isDetailed); + return processResponsePayload(response, stat, isDetailed); +} diff --git a/oafAdmin/src/libs/webdav/request.ts b/oafAdmin/src/libs/webdav/request.ts new file mode 100644 index 0000000..0f60421 --- /dev/null +++ b/oafAdmin/src/libs/webdav/request.ts @@ -0,0 +1,108 @@ +import axios from "axios"; +import { getPatcher } from "./compat/patcher"; +import { generateDigestAuthHeader, parseDigestAuth } from "./auth/digest"; +import { cloneShallow, merge } from "./tools/merge"; +import { mergeHeaders } from "./tools/headers"; +import { + RequestOptionsCustom, + RequestOptionsWithState, + RequestOptions, + Response, + WebDAVClientContext, + WebDAVMethodOptions +} from "./types"; + +function _request(requestOptions: RequestOptions) { + return getPatcher().patchInline( + "request", + (options: RequestOptions) => axios(options as any), + requestOptions + ); +} + +export function prepareRequestOptions( + requestOptions: RequestOptionsCustom | RequestOptionsWithState, + context: WebDAVClientContext, + userOptions: WebDAVMethodOptions +): RequestOptionsWithState { + const finalOptions = cloneShallow(requestOptions) as RequestOptionsWithState; + finalOptions.headers = mergeHeaders( + context.headers, + finalOptions.headers || {}, + userOptions.headers || {} + ); + if (typeof userOptions.data !== "undefined") { + finalOptions.data = userOptions.data; + } + if (context.httpAgent) { + finalOptions.httpAgent = context.httpAgent; + } + if (context.httpsAgent) { + finalOptions.httpsAgent = context.httpsAgent; + } + if (context.digest) { + finalOptions._digest = context.digest; + } + if (typeof context.withCredentials === "boolean") { + finalOptions.withCredentials = context.withCredentials; + } + if (context.maxContentLength) { + finalOptions.maxContentLength = context.maxContentLength; + } + if (context.maxBodyLength) { + finalOptions.maxBodyLength = context.maxBodyLength; + } + if (userOptions.hasOwnProperty("onUploadProgress")) { + finalOptions.onUploadProgress = userOptions["onUploadProgress"]; + } + // Take full control of all response status codes + finalOptions.validateStatus = () => true; + return finalOptions; +} + +export function request(requestOptions: RequestOptionsWithState): Promise { + // Client not configured for digest authentication + if (!requestOptions._digest) { + return _request(requestOptions); + } + + // Remove client's digest authentication object from request options + const _digest = requestOptions._digest; + delete requestOptions._digest; + + // If client is already using digest authentication, include the digest authorization header + if (_digest.hasDigestAuth) { + requestOptions = merge(requestOptions, { + headers: { + Authorization: generateDigestAuthHeader(requestOptions, _digest) + } + }); + } + + // Perform the request and handle digest authentication + return _request(requestOptions).then(function(response: Response) { + if (response.status == 401) { + _digest.hasDigestAuth = parseDigestAuth(response, _digest); + + if (_digest.hasDigestAuth) { + requestOptions = merge(requestOptions, { + headers: { + Authorization: generateDigestAuthHeader(requestOptions, _digest) + } + }); + + return _request(requestOptions).then(function(response2: Response) { + if (response2.status == 401) { + _digest.hasDigestAuth = false; + } else { + _digest.nc++; + } + return response2; + }); + } + } else { + _digest.nc++; + } + return response; + }); +} diff --git a/oafAdmin/src/libs/webdav/response.ts b/oafAdmin/src/libs/webdav/response.ts new file mode 100644 index 0000000..cca7caa --- /dev/null +++ b/oafAdmin/src/libs/webdav/response.ts @@ -0,0 +1,46 @@ +import minimatch from "minimatch"; +import { + FileStat, + Response, + ResponseDataDetailed, + WebDAVClientContext, + WebDAVClientError +} from "./types"; + +export function createErrorFromResponse(response: Response, prefix: string = ""): Error { + const err: WebDAVClientError = new Error( + `${prefix}Invalid response: ${response.status} ${response.statusText}` + ) as WebDAVClientError; + err.status = response.status; + err.response = response; + return err; +} + +export function handleResponseCode(context: WebDAVClientContext, response: Response): Response { + const { status } = response; + if (status === 401 && context.digest) return response; + if (status >= 400) { + const err = createErrorFromResponse(response); + throw err; + } + return response; +} + +export function processGlobFilter(files: Array, glob: string): Array { + return files.filter(file => minimatch(file.filename, glob, { matchBase: true })); +} + +export function processResponsePayload( + response: Response, + data: T, + isDetailed: boolean = false +): ResponseDataDetailed | T { + return isDetailed + ? { + data, + headers: response.headers || {}, + status: response.status, + statusText: response.statusText + } + : data; +} diff --git a/oafAdmin/src/libs/webdav/tools/crypto.ts b/oafAdmin/src/libs/webdav/tools/crypto.ts new file mode 100644 index 0000000..10bee66 --- /dev/null +++ b/oafAdmin/src/libs/webdav/tools/crypto.ts @@ -0,0 +1,16 @@ +import md5 from "md5"; + +export function ha1Compute( + algorithm: string, + user: string, + realm: string, + pass: string, + nonce: string, + cnonce: string +): string { + const ha1 = md5(`${user}:${realm}:${pass}`) as string; + if (algorithm && algorithm.toLowerCase() === "md5-sess") { + return md5(`${ha1}:${nonce}:${cnonce}`) as string; + } + return ha1; +} diff --git a/oafAdmin/src/libs/webdav/tools/dav.ts b/oafAdmin/src/libs/webdav/tools/dav.ts new file mode 100644 index 0000000..8267fa4 --- /dev/null +++ b/oafAdmin/src/libs/webdav/tools/dav.ts @@ -0,0 +1,171 @@ +import path from "path-posix"; +import xmlParser from "fast-xml-parser"; +import nestedProp from "nested-property"; +import { decodeHTMLEntities } from "./encode"; +import { normalisePath } from "./path"; +import { + DAVResult, + DAVResultRaw, + DAVResultResponse, + DAVResultResponseProps, + DiskQuotaAvailable, + FileStat, + WebDAVClientError +} from "../types"; + +enum PropertyType { + Array = "array", + Object = "object", + Original = "original" +} + +function getPropertyOfType( + obj: Object, + prop: string, + type: PropertyType = PropertyType.Original +): any { + const val = nestedProp.get(obj, prop); + if (type === "array" && Array.isArray(val) === false) { + return [val]; + } else if (type === "object" && Array.isArray(val)) { + return val[0]; + } + return val; +} + +function normaliseResponse(response: any): DAVResultResponse { + const output = Object.assign({}, response); + nestedProp.set(output, "propstat", getPropertyOfType(output, "propstat", PropertyType.Object)); + nestedProp.set( + output, + "propstat.prop", + getPropertyOfType(output, "propstat.prop", PropertyType.Object) + ); + return output; +} + +function normaliseResult(result: DAVResultRaw): DAVResult { + const { multistatus } = result; + if (multistatus === "") { + return { + multistatus: { + response: [] + } + }; + } + if (!multistatus) { + throw new Error("Invalid response: No root multistatus found"); + } + const output: any = { + multistatus: Array.isArray(multistatus) ? multistatus[0] : multistatus + }; + nestedProp.set( + output, + "multistatus.response", + getPropertyOfType(output, "multistatus.response", PropertyType.Array) + ); + nestedProp.set( + output, + "multistatus.response", + nestedProp.get(output, "multistatus.response").map(response => normaliseResponse(response)) + ); + return output as DAVResult; +} + +export function parseXML(xml: string): Promise { + return new Promise(resolve => { + const result = xmlParser.parse(xml, { + arrayMode: false, + ignoreNameSpace: true + // // We don't use the processors here as decoding is done manually + // // later on - decoding early would break some path checks. + // attrValueProcessor: val => decodeHTMLEntities(decodeURIComponent(val)), + // tagValueProcessor: val => decodeHTMLEntities(decodeURIComponent(val)) + }); + resolve(normaliseResult(result)); + }); +} + +export function prepareFileFromProps( + props: DAVResultResponseProps, + rawFilename: string, + isDetailed: boolean = false +): FileStat { + // Last modified time, raw size, item type and mime + const { + getlastmodified: lastMod = null, + getcontentlength: rawSize = "0", + resourcetype: resourceType = null, + getcontenttype: mimeType = null, + getetag: etag = null + } = props; + const type = + resourceType && + typeof resourceType === "object" && + typeof resourceType.collection !== "undefined" + ? "directory" + : "file"; + const filename = decodeHTMLEntities(rawFilename); + const stat: FileStat = { + filename, + basename: path.basename(filename), + lastmod: lastMod, + size: parseInt(rawSize, 10), + type, + etag: typeof etag === "string" ? etag.replace(/"/g, "") : null + }; + if (type === "file") { + stat.mime = mimeType && typeof mimeType === "string" ? mimeType.split(";")[0] : ""; + } + if (isDetailed) { + stat.props = props; + } + return stat; +} + +export function parseStat( + result: DAVResult, + filename: string, + isDetailed: boolean = false +): FileStat { + let responseItem: DAVResultResponse = null; + try { + responseItem = result.multistatus.response[0]; + } catch (e) { + /* ignore */ + } + if (!responseItem) { + throw new Error("Failed getting item stat: bad response"); + } + const { + propstat: { prop: props, status: statusLine } + } = responseItem; + + // As defined in https://tools.ietf.org/html/rfc2068#section-6.1 + const [_, statusCodeStr, statusText] = statusLine.split(" ", 3); + const statusCode = parseInt(statusCodeStr, 10); + if (statusCode >= 400) { + const err: WebDAVClientError = new Error( + `Invalid response: ${statusCode} ${statusText}` + ) as WebDAVClientError; + err.status = statusCode; + throw err; + } + + const filePath = normalisePath(filename); + return prepareFileFromProps(props, filePath, isDetailed); +} + +export function translateDiskSpace(value: string | number): DiskQuotaAvailable { + switch (value.toString()) { + case "-3": + return "unlimited"; + case "-2": + /* falls-through */ + case "-1": + // -1 is non-computed + return "unknown"; + default: + return parseInt(value as string, 10); + } +} diff --git a/oafAdmin/src/libs/webdav/tools/encode.ts b/oafAdmin/src/libs/webdav/tools/encode.ts new file mode 100644 index 0000000..9b3e34a --- /dev/null +++ b/oafAdmin/src/libs/webdav/tools/encode.ts @@ -0,0 +1,24 @@ +import { decode, encode } from "base-64"; + +declare var WEB: boolean; + +export function decodeHTMLEntities(text: string): string { + if (typeof WEB === "undefined") { + // Node + const he = require("he"); + return he.decode(text); + } else { + // Nasty browser way + const txt = document.createElement("textarea"); + txt.innerHTML = text; + return txt.value; + } +} + +export function fromBase64(text: string): string { + return decode(text); +} + +export function toBase64(text: string): string { + return encode(text); +} diff --git a/oafAdmin/src/libs/webdav/tools/headers.ts b/oafAdmin/src/libs/webdav/tools/headers.ts new file mode 100644 index 0000000..65236f0 --- /dev/null +++ b/oafAdmin/src/libs/webdav/tools/headers.ts @@ -0,0 +1,18 @@ +import { Headers } from "../types"; + +export function mergeHeaders(...headerPayloads: Headers[]): Headers { + if (headerPayloads.length === 0) return {}; + const headerKeys = {}; + return headerPayloads.reduce((output: Headers, headers: Headers) => { + Object.keys(headers).forEach(header => { + const lowerHeader = header.toLowerCase(); + if (headerKeys.hasOwnProperty(lowerHeader)) { + output[headerKeys[lowerHeader]] = headers[header]; + } else { + headerKeys[lowerHeader] = header; + output[header] = headers[header]; + } + }); + return output; + }, {}); +} diff --git a/oafAdmin/src/libs/webdav/tools/merge.ts b/oafAdmin/src/libs/webdav/tools/merge.ts new file mode 100644 index 0000000..a92f708 --- /dev/null +++ b/oafAdmin/src/libs/webdav/tools/merge.ts @@ -0,0 +1,62 @@ +export function cloneShallow(obj: T): T { + return isPlainObject(obj) + ? Object.assign({}, obj) + : Object.setPrototypeOf(Object.assign({}, obj), Object.getPrototypeOf(obj)); +} + +function isPlainObject(obj: Object | any): boolean { + if ( + typeof obj !== "object" || + obj === null || + Object.prototype.toString.call(obj) != "[object Object]" + ) { + // Not an object + return false; + } + if (Object.getPrototypeOf(obj) === null) { + return true; + } + let proto = obj; + // Find the prototype + while (Object.getPrototypeOf(proto) !== null) { + proto = Object.getPrototypeOf(proto); + } + return Object.getPrototypeOf(obj) === proto; +} + +export function merge(...args: Object[]) { + let output = null, + items = [...args]; + while (items.length > 0) { + const nextItem = items.shift(); + if (!output) { + output = cloneShallow(nextItem); + } else { + output = mergeObjects(output, nextItem); + } + } + return output; +} + +function mergeObjects(obj1: Object, obj2: Object): Object { + const output = cloneShallow(obj1); + Object.keys(obj2).forEach(key => { + if (!output.hasOwnProperty(key)) { + output[key] = obj2[key]; + return; + } + if (Array.isArray(obj2[key])) { + output[key] = Array.isArray(output[key]) + ? [...output[key], ...obj2[key]] + : [...obj2[key]]; + } else if (typeof obj2[key] === "object" && !!obj2[key]) { + output[key] = + typeof output[key] === "object" && !!output[key] + ? mergeObjects(output[key], obj2[key]) + : cloneShallow(obj2[key]); + } else { + output[key] = obj2[key]; + } + }); + return output; +} diff --git a/oafAdmin/src/libs/webdav/tools/path.ts b/oafAdmin/src/libs/webdav/tools/path.ts new file mode 100644 index 0000000..bb786fc --- /dev/null +++ b/oafAdmin/src/libs/webdav/tools/path.ts @@ -0,0 +1,36 @@ +import { dirname } from "path-posix"; + +const SEP_PATH_POSIX = "__PATH_SEPARATOR_POSIX__"; +const SEP_PATH_WINDOWS = "__PATH_SEPARATOR_WINDOWS__"; + +export function encodePath(path) { + const replaced = path.replace(/\//g, SEP_PATH_POSIX).replace(/\\\\/g, SEP_PATH_WINDOWS); + const formatted = encodeURIComponent(replaced); + return formatted + .split(SEP_PATH_WINDOWS) + .join("\\\\") + .split(SEP_PATH_POSIX) + .join("/"); +} + +export function getAllDirectories(path: string): Array { + if (!path || path === "/") return []; + let currentPath = path; + const output: Array = []; + do { + output.push(currentPath); + currentPath = dirname(currentPath); + } while (currentPath && currentPath !== "/"); + return output; +} + +export function normalisePath(pathStr: string): string { + let normalisedPath = pathStr; + if (normalisedPath[0] !== "/") { + normalisedPath = "/" + normalisedPath; + } + if (/^.+\/$/.test(normalisedPath)) { + normalisedPath = normalisedPath.substr(0, normalisedPath.length - 1); + } + return normalisedPath; +} diff --git a/oafAdmin/src/libs/webdav/tools/quota.ts b/oafAdmin/src/libs/webdav/tools/quota.ts new file mode 100644 index 0000000..4adf650 --- /dev/null +++ b/oafAdmin/src/libs/webdav/tools/quota.ts @@ -0,0 +1,22 @@ +import { translateDiskSpace } from "./dav"; +import { DAVResult, DiskQuota } from "../types"; + +export function parseQuota(result: DAVResult): DiskQuota | null { + try { + const [responseItem] = result.multistatus.response; + const { + propstat: { + prop: { "quota-used-bytes": quotaUsed, "quota-available-bytes": quotaAvail } + } + } = responseItem; + return typeof quotaUsed !== "undefined" && typeof quotaAvail !== "undefined" + ? { + used: parseInt(quotaUsed, 10), + available: translateDiskSpace(quotaAvail) + } + : null; + } catch (err) { + /* ignore */ + } + return null; +} diff --git a/oafAdmin/src/libs/webdav/tools/size.ts b/oafAdmin/src/libs/webdav/tools/size.ts new file mode 100644 index 0000000..f5b09f0 --- /dev/null +++ b/oafAdmin/src/libs/webdav/tools/size.ts @@ -0,0 +1,22 @@ +import { Layerr } from "layerr"; +import { isArrayBuffer } from "../compat/arrayBuffer"; +import { isBuffer } from "../compat/buffer"; +import { BufferLike, ErrorCode } from "../types"; + +export function calculateDataLength(data: string | BufferLike): number { + if (isArrayBuffer(data)) { + return (data).byteLength; + } else if (isBuffer(data)) { + return (data).length; + } else if (typeof data === "string") { + return (data).length; + } + throw new Layerr( + { + info: { + code: ErrorCode.DataTypeNoLength + } + }, + "Cannot calculate data length: Invalid type" + ); +} diff --git a/oafAdmin/src/libs/webdav/tools/url.ts b/oafAdmin/src/libs/webdav/tools/url.ts new file mode 100644 index 0000000..94ca9d4 --- /dev/null +++ b/oafAdmin/src/libs/webdav/tools/url.ts @@ -0,0 +1,32 @@ +import URL from "url-parse"; +import _joinURL from "url-join"; +import { normalisePath } from "./path"; + +export function extractURLPath(fullURL: string): string { + const url = new URL(fullURL); + let urlPath = url.pathname; + if (urlPath.length <= 0) { + urlPath = "/"; + } + return normalisePath(urlPath); +} + +export function joinURL(...parts: Array): string { + return _joinURL( + parts.reduce((output, nextPart, partIndex) => { + if ( + partIndex === 0 || + nextPart !== "/" || + (nextPart === "/" && output[output.length - 1] !== "/") + ) { + output.push(nextPart); + } + return output; + }, []) + ); +} + +export function normaliseHREF(href: string): string { + const normalisedHref = href.replace(/^https?:\/\/[^\/]+/, ""); + return normalisedHref; +} diff --git a/oafAdmin/src/libs/webdav/tools/xml.ts b/oafAdmin/src/libs/webdav/tools/xml.ts new file mode 100644 index 0000000..ba2e588 --- /dev/null +++ b/oafAdmin/src/libs/webdav/tools/xml.ts @@ -0,0 +1,55 @@ +import xmlParser, { j2xParser as XMLParser } from "fast-xml-parser"; + +export function generateLockXML(ownerHREF: string): string { + return getParser().parse( + namespace( + { + lockinfo: { + "@_xmlns:d": "DAV:", + lockscope: { + exclusive: {} + }, + locktype: { + write: {} + }, + owner: { + href: ownerHREF + } + } + }, + "d" + ) + ); +} + +function getParser(): XMLParser { + return new XMLParser({ + attributeNamePrefix: "@_", + format: true, + ignoreAttributes: false, + supressEmptyNode: true + }); +} + +function namespace(obj: T, ns: string): T { + const copy = { ...obj }; + for (const key in copy) { + if (copy[key] && typeof copy[key] === "object" && key.indexOf(":") === -1) { + copy[`${ns}:${key}`] = namespace(copy[key], ns); + delete copy[key]; + } else if (/^@_/.test(key) === false) { + copy[`${ns}:${key}`] = copy[key]; + delete copy[key]; + } + } + return copy; +} + +export function parseGenericResponse(xml: string): Object { + return xmlParser.parse(xml, { + arrayMode: false, + ignoreNameSpace: true, + parseAttributeValue: true, + parseNodeValue: true + }); +} diff --git a/oafAdmin/src/libs/webdav/types.ts b/oafAdmin/src/libs/webdav/types.ts new file mode 100644 index 0000000..e0d6c1e --- /dev/null +++ b/oafAdmin/src/libs/webdav/types.ts @@ -0,0 +1,288 @@ +import Stream from "stream"; + +export type AuthHeader = string; + +export enum AuthType { + Digest = "digest", + None = "none", + Password = "password", + Token = "token" +} + +export type BufferLike = Buffer | ArrayBuffer; + +export interface CreateDirectoryOptions extends WebDAVMethodOptions { + recursive?: boolean; +} + +export interface CreateReadStreamOptions extends WebDAVMethodOptions { + callback?: (response: Response) => void; + range?: { + start: number; + end?: number; + }; +} + +export type CreateWriteStreamCallback = (response: Response) => void; + +export interface CreateWriteStreamOptions extends WebDAVMethodOptions { + overwrite?: boolean; +} + +export interface DAVResultResponse { + href: string; + propstat: { + prop: DAVResultResponseProps; + status: string; + }; +} + +export interface DAVResultResponseProps { + displayname: string; + resourcetype: { + collection?: boolean; + }; + getlastmodified?: string; + getetag?: string; + getcontentlength?: string; + getcontenttype?: string; + "quota-available-bytes"?: any; + "quota-used-bytes"?: string; +} + +export interface DAVResult { + multistatus: { + response: Array; + }; +} + +export interface DAVResultRawMultistatus { + response: DAVResultResponse | [DAVResultResponse]; +} + +export interface DAVResultRaw { + multistatus: "" | DAVResultRawMultistatus | [DAVResultRawMultistatus]; +} + +export interface DigestContext { + username: string; + password: string; + nc: number; + algorithm: string; + hasDigestAuth: boolean; + cnonce?: string; + nonce?: string; + realm?: string; + qop?: string; + opaque?: string; +} + +export interface DiskQuota { + used: number; + available: DiskQuotaAvailable; +} + +export type DiskQuotaAvailable = "unknown" | "unlimited" | number; + +export enum ErrorCode { + DataTypeNoLength = "data-type-no-length", + InvalidAuthType = "invalid-auth-type", + InvalidOutputFormat = "invalid-output-format", + LinkUnsupportedAuthType = "link-unsupported-auth" +} + +export interface FileStat { + filename: string; + basename: string; + lastmod: string; + size: number; + type: "file" | "directory"; + etag: string | null; + mime?: string; + props?: DAVResultResponseProps; +} + +export interface GetDirectoryContentsOptions extends WebDAVMethodOptions { + deep?: boolean; + details?: boolean; + glob?: string; +} + +export interface GetFileContentsOptions extends WebDAVMethodOptions { + details?: boolean; + format?: "binary" | "text"; +} + +export interface GetQuotaOptions extends WebDAVMethodOptions { + details?: boolean; +} + +export interface Headers { + [key: string]: string; +} + +export interface LockOptions extends WebDAVMethodOptions { + refreshToken?: string; + timeout?: string; +} + +export interface LockResponse { + serverTimeout: string; + token: string; +} + +export interface OAuthToken { + access_token: string; + token_type: string; + refresh_token?: string; +} + +export interface PutFileContentsOptions extends WebDAVMethodOptions { + contentLength?: boolean | number; + overwrite?: boolean; + onUploadProgress?: UploadProgressCallback; +} + +export type RequestDataPayload = string | Buffer | ArrayBuffer | { [key: string]: any }; + +interface RequestOptionsBase { + data?: RequestDataPayload; + headers?: Headers; + httpAgent?: any; + httpsAgent?: any; + maxBodyLength?: number; + maxContentLength?: number; + maxRedirects?: number; + method: string; + onUploadProgress?: UploadProgressCallback; + responseType?: string; + transformResponse?: Array<(value: any) => any>; + url?: string; + validateStatus?: (status: number) => boolean; + withCredentials?: boolean; +} + +export interface RequestOptionsCustom extends RequestOptionsBase {} + +export interface RequestOptions extends RequestOptionsBase { + url: string; +} + +export interface RequestOptionsWithState extends RequestOptions { + _digest?: DigestContext; +} + +export interface Response { + data: ResponseData; + status: number; + headers: Headers; + statusText: string; +} + +export type ResponseData = string | Buffer | ArrayBuffer | Object | Array; + +export interface ResponseDataDetailed { + data: T; + headers: Headers; + status: number; + statusText: string; +} + +export interface ResponseStatusValidator { + (status: number): boolean; +} + +export interface StatOptions extends WebDAVMethodOptions { + details?: boolean; +} + +export interface UploadProgress { + loaded: number; + total: number; +} + +export interface UploadProgressCallback { + (progress: UploadProgress): void; +} + +export interface WebDAVClient { + copyFile: (filename: string, destination: string) => Promise; + createDirectory: (path: string, options?: CreateDirectoryOptions) => Promise; + createReadStream: (filename: string, options?: CreateReadStreamOptions) => Stream.Readable; + createWriteStream: ( + filename: string, + options?: CreateWriteStreamOptions, + callback?: CreateWriteStreamCallback + ) => Stream.Writable; + customRequest: (path: string, requestOptions: RequestOptionsCustom) => Promise; + deleteFile: (filename: string) => Promise; + exists: (path: string) => Promise; + getDirectoryContents: ( + path: string, + options?: GetDirectoryContentsOptions + ) => Promise | ResponseDataDetailed>>; + getFileContents: ( + filename: string, + options?: GetFileContentsOptions + ) => Promise>; + getFileDownloadLink: (filename: string) => string; + getFileUploadLink: (filename: string) => string; + getHeaders: () => Headers; + getQuota: ( + options?: GetQuotaOptions + ) => Promise>; + lock: (path: string, options?: LockOptions) => Promise; + moveFile: (filename: string, destinationFilename: string) => Promise; + putFileContents: ( + filename: string, + data: string | BufferLike | Stream.Readable, + options?: PutFileContentsOptions + ) => Promise; + setHeaders: (headers: Headers) => void; + stat: ( + path: string, + options?: StatOptions + ) => Promise>; + unlock: (path: string, token: string, options?: WebDAVMethodOptions) => Promise; +} + +export interface WebDAVClientContext { + authType: AuthType; + contactHref: string; + digest?: DigestContext; + headers: Headers; + httpAgent?: any; + httpsAgent?: any; + maxBodyLength?: number; + maxContentLength?: number; + password?: string; + remotePath: string; + remoteURL: string; + token?: OAuthToken; + username?: string; + withCredentials?: boolean; +} + +export interface WebDAVClientError extends Error { + status?: number; + response?: Response; +} + +export interface WebDAVClientOptions { + authType?: AuthType; + contactHref?: string; + headers?: Headers; + httpAgent?: any; + httpsAgent?: any; + maxBodyLength?: number; + maxContentLength?: number; + password?: string; + token?: OAuthToken; + username?: string; + withCredentials?: boolean; +} + +export interface WebDAVMethodOptions { + data?: RequestDataPayload; + headers?: Headers; +} diff --git a/oafAdmin/src/libs/wwLogin.js b/oafAdmin/src/libs/wwLogin.js new file mode 100644 index 0000000..e62111b --- /dev/null +++ b/oafAdmin/src/libs/wwLogin.js @@ -0,0 +1,2 @@ +!function(a,b,c){function d(c){var d=b.createElement("iframe"),e="https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid="+c.appid+"&agentid="+c.agentid+"&redirect_uri="+c.redirect_uri+"&state="+c.state+"&login_type=jssdk";e+=c.style?"&style="+c.style:"",e+=c.href?"&href="+c.href:"",d.src=e,d.frameBorder="0",d.allowTransparency="true",d.scrolling="no",d.width="300px",d.height="400px";var f=b.getElementById(c.id);f.innerHTML="",f.appendChild(d),d.onload=function(){d.contentWindow.postMessage&&a.addEventListener&&(a.addEventListener("message",function(b){ +b.data&&b.origin.indexOf("work.weixin.qq.com")>-1&&(a.location.href=b.data)}),d.contentWindow.postMessage("ask_usePostMessage","*"))}}a.WwLogin=d}(window,document); \ No newline at end of file diff --git a/oafAdmin/src/main.ts b/oafAdmin/src/main.ts new file mode 100644 index 0000000..3376ffa --- /dev/null +++ b/oafAdmin/src/main.ts @@ -0,0 +1,14 @@ +import { createApp } from 'vue' +import App from './App.vue' +import OneIcon from '@veypi/one-icon' +import router from '@/router' +import { createPinia } from 'pinia' +import 'animate.css' +import './assets/icon.js' +import './index.css' + +let app = createApp(App) +app.use(OneIcon) +app.use(router) +app.use(createPinia()) +app.mount('#app') diff --git a/oafAdmin/src/router/index.ts b/oafAdmin/src/router/index.ts new file mode 100644 index 0000000..f712a70 --- /dev/null +++ b/oafAdmin/src/router/index.ts @@ -0,0 +1,44 @@ +/* +* @name: index +* @author: veypi +* @date: 2022-04-16 10:14 +* @description:index +*/ + + +import {createRouter, createWebHistory} from 'vue-router' + +declare module 'vue-router' { + interface RouteMeta { + title?: string + requiresAuth: boolean + checkAuth?: (r?: RouteLocationNormalized) => boolean + } +} +const router = createRouter({ + history: createWebHistory(), + routes: [ + { + path: '/', + name: 'home', + component: () => import('../views/home.vue'), + }, + { + path: '/:path(.*)', + name: '404', + component: () => import('@/views/404.vue'), + }, + ], +}) + +router.beforeEach((to, from) => { + if (to.meta.requiresAuth && to.meta.checkAuth) { + return { + name: 'login', + // 保存我们所在的位置,以便以后再来 + query: {redirect: to.fullPath}, + } + } +}) + +export default router diff --git a/oafAdmin/src/store/app.ts b/oafAdmin/src/store/app.ts new file mode 100644 index 0000000..0d3cc6c --- /dev/null +++ b/oafAdmin/src/store/app.ts @@ -0,0 +1,20 @@ +/* +* @name: user +* @author: veypi +* @date: 2022-04-16 10:57 +* @description:user +*/ + +import { defineStore } from 'pinia' + +export const useAppStore = defineStore('app', { + state: () => { + return { + hideHeader: false, + title: '', + isDark: false, + } + }, + actions: { + }, +}) diff --git a/oafAdmin/src/store/user.ts b/oafAdmin/src/store/user.ts new file mode 100644 index 0000000..3006446 --- /dev/null +++ b/oafAdmin/src/store/user.ts @@ -0,0 +1,23 @@ +/* +* @name: user +* @author: veypi +* @date: 2022-04-16 10:57 +* @description:user +*/ + +import {defineStore} from 'pinia' + +export const useUserStore = defineStore('user', { + state: () => { + return { + id: 0, + username: '', + } + }, + actions: { + setUser() { + this.id = 1 + this.username = 'admin' + }, + }, +}) diff --git a/oafAdmin/src/views/404.vue b/oafAdmin/src/views/404.vue new file mode 100644 index 0000000..c01ae34 --- /dev/null +++ b/oafAdmin/src/views/404.vue @@ -0,0 +1,21 @@ + + + diff --git a/oafAdmin/src/views/demo.vue b/oafAdmin/src/views/demo.vue new file mode 100644 index 0000000..28d32c1 --- /dev/null +++ b/oafAdmin/src/views/demo.vue @@ -0,0 +1,6 @@ + + + diff --git a/oafAdmin/src/views/home.vue b/oafAdmin/src/views/home.vue new file mode 100644 index 0000000..5023f43 --- /dev/null +++ b/oafAdmin/src/views/home.vue @@ -0,0 +1,7 @@ + + + diff --git a/oafAdmin/tailwind.config.js b/oafAdmin/tailwind.config.js new file mode 100644 index 0000000..c3d7982 --- /dev/null +++ b/oafAdmin/tailwind.config.js @@ -0,0 +1,10 @@ +module.exports = { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/oafAdmin/tsconfig.json b/oafAdmin/tsconfig.json new file mode 100644 index 0000000..b689b89 --- /dev/null +++ b/oafAdmin/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "@/*": [ + "./src/*" + ] + }, + "lib": [ + "esnext", + "dom" + ] + }, + "exclude": [ + "node_modules" + ], + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/oafAdmin/tsconfig.node.json b/oafAdmin/tsconfig.node.json new file mode 100644 index 0000000..e993792 --- /dev/null +++ b/oafAdmin/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "composite": true, + "module": "esnext", + "moduleResolution": "node" + }, + "include": ["vite.config.ts"] +} diff --git a/oafAdmin/vite.config.ts b/oafAdmin/vite.config.ts new file mode 100644 index 0000000..d77a274 --- /dev/null +++ b/oafAdmin/vite.config.ts @@ -0,0 +1,40 @@ +import {defineConfig} from 'vite' +import vue from '@vitejs/plugin-vue' +import {resolve} from 'path' + +// https://vitejs.dev/config/ +export default defineConfig({ + resolve: { + alias: { + '@': resolve(__dirname, 'src'), + }, + }, + server: { + host: '127.0.0.1', + port: 3000, + proxy: { + '/api': { + target: 'http://127.0.0.1:4000/', + changeOrigin: true, + ws: true, + }, + '/media': { + target: 'http://127.0.0.1:4000/', + changeOrigin: true, + ws: true, + }, + }, + }, + plugins: [vue()], + build: { + outDir: './dist/', + assetsDir: './', + rollupOptions: { + output: { + entryFileNames: `static/[name].[hash].js`, + chunkFileNames: `static/[name].[hash].js`, + assetFileNames: `static/[name].[hash].[ext]`, + }, + }, + }, +}) diff --git a/oafAdmin/yarn.lock b/oafAdmin/yarn.lock new file mode 100644 index 0000000..25ce594 --- /dev/null +++ b/oafAdmin/yarn.lock @@ -0,0 +1,868 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/parser@^7.16.4": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8" + integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA== + +"@esbuild/linux-loong64@0.14.54": + version "0.14.54" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" + integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@types/node@^17.0.24": + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== + +"@veypi/one-icon@2": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.6.tgz#158c692971848524cd59db1a61d88805d2e45646" + integrity sha512-ldfRE8vDSqZEFk+94wqieWP4s1Mz1EDG1VhXmckWI0cat2RT/Kk9hcICImkLhsOmhNRX7nwxSU4UbUiJVix/Jw== + dependencies: + vue "^3.2.20" + +"@vitejs/plugin-vue@^2.3.1": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.4.tgz#966a6279060eb2d9d1a02ea1a331af071afdcf9e" + integrity sha512-IfFNbtkbIm36O9KB8QodlwwYvTEsJb4Lll4c2IwB3VHc2gie2mSPtSzL0eYay7X2jd/2WX02FjSGTWR6OPr/zg== + +"@volar/code-gen@0.34.17": + version "0.34.17" + resolved "https://registry.yarnpkg.com/@volar/code-gen/-/code-gen-0.34.17.tgz#fd46e369454e6bd9599b511500b4c43acb9730bd" + integrity sha512-rHR7BA71BJ/4S7xUOPMPiB7uk6iU9oTWpEMZxFi5VGC9iJmDncE82WzU5iYpcbOBCVHsOjMh0+5CGMgdO6SaPA== + dependencies: + "@volar/source-map" "0.34.17" + +"@volar/source-map@0.34.17": + version "0.34.17" + resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-0.34.17.tgz#79efc4d088e11f59fc857953185a1f852df70968" + integrity sha512-3yn1IMXJGGWB/G817/VFlFMi8oh5pmE7VzUqvgMZMrppaZpKj6/juvJIEiXNxRsgWc0RxIO8OSp4htdPUg1Raw== + +"@volar/vue-code-gen@0.34.17": + version "0.34.17" + resolved "https://registry.yarnpkg.com/@volar/vue-code-gen/-/vue-code-gen-0.34.17.tgz#55ca9c21b38c91bf362761b268a77b9f0ecae8bf" + integrity sha512-17pzcK29fyFWUc+C82J3JYSnA+jy3QNrIldb9kPaP9Itbik05ZjEIyEue9FjhgIAuHeYSn4LDM5s6nGjxyfhsQ== + dependencies: + "@volar/code-gen" "0.34.17" + "@volar/source-map" "0.34.17" + "@vue/compiler-core" "^3.2.36" + "@vue/compiler-dom" "^3.2.36" + "@vue/shared" "^3.2.36" + +"@volar/vue-typescript@0.34.17": + version "0.34.17" + resolved "https://registry.yarnpkg.com/@volar/vue-typescript/-/vue-typescript-0.34.17.tgz#497eb471ebac25ff61af04031b78ec71a34d470b" + integrity sha512-U0YSVIBPRWVPmgJHNa4nrfq88+oS+tmyZNxmnfajIw9A/GOGZQiKXHC0k09SVvbYXlsjgJ6NIjhm9NuAhGRQjg== + dependencies: + "@volar/code-gen" "0.34.17" + "@volar/source-map" "0.34.17" + "@volar/vue-code-gen" "0.34.17" + "@vue/compiler-sfc" "^3.2.36" + "@vue/reactivity" "^3.2.36" + +"@vue/compiler-core@3.2.45", "@vue/compiler-core@^3.2.36": + version "3.2.45" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.45.tgz#d9311207d96f6ebd5f4660be129fb99f01ddb41b" + integrity sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/shared" "3.2.45" + estree-walker "^2.0.2" + source-map "^0.6.1" + +"@vue/compiler-dom@3.2.45", "@vue/compiler-dom@^3.2.36": + version "3.2.45" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz#c43cc15e50da62ecc16a42f2622d25dc5fd97dce" + integrity sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw== + dependencies: + "@vue/compiler-core" "3.2.45" + "@vue/shared" "3.2.45" + +"@vue/compiler-sfc@3.2.45", "@vue/compiler-sfc@^3.2.36": + version "3.2.45" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz#7f7989cc04ec9e7c55acd406827a2c4e96872c70" + integrity sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/compiler-core" "3.2.45" + "@vue/compiler-dom" "3.2.45" + "@vue/compiler-ssr" "3.2.45" + "@vue/reactivity-transform" "3.2.45" + "@vue/shared" "3.2.45" + estree-walker "^2.0.2" + magic-string "^0.25.7" + postcss "^8.1.10" + source-map "^0.6.1" + +"@vue/compiler-ssr@3.2.45": + version "3.2.45" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz#bd20604b6e64ea15344d5b6278c4141191c983b2" + integrity sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ== + dependencies: + "@vue/compiler-dom" "3.2.45" + "@vue/shared" "3.2.45" + +"@vue/devtools-api@^6.4.5": + version "6.4.5" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.4.5.tgz#d54e844c1adbb1e677c81c665ecef1a2b4bb8380" + integrity sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ== + +"@vue/reactivity-transform@3.2.45": + version "3.2.45" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz#07ac83b8138550c83dfb50db43cde1e0e5e8124d" + integrity sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/compiler-core" "3.2.45" + "@vue/shared" "3.2.45" + estree-walker "^2.0.2" + magic-string "^0.25.7" + +"@vue/reactivity@3.2.45", "@vue/reactivity@^3.2.36": + version "3.2.45" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.45.tgz#412a45b574de601be5a4a5d9a8cbd4dee4662ff0" + integrity sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A== + dependencies: + "@vue/shared" "3.2.45" + +"@vue/runtime-core@3.2.45": + version "3.2.45" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.45.tgz#7ad7ef9b2519d41062a30c6fa001ec43ac549c7f" + integrity sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A== + dependencies: + "@vue/reactivity" "3.2.45" + "@vue/shared" "3.2.45" + +"@vue/runtime-dom@3.2.45": + version "3.2.45" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz#1a2ef6ee2ad876206fbbe2a884554bba2d0faf59" + integrity sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA== + dependencies: + "@vue/runtime-core" "3.2.45" + "@vue/shared" "3.2.45" + csstype "^2.6.8" + +"@vue/server-renderer@3.2.45": + version "3.2.45" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.45.tgz#ca9306a0c12b0530a1a250e44f4a0abac6b81f3f" + integrity sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g== + dependencies: + "@vue/compiler-ssr" "3.2.45" + "@vue/shared" "3.2.45" + +"@vue/shared@3.2.45", "@vue/shared@^3.2.36": + version "3.2.45" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.45.tgz#a3fffa7489eafff38d984e23d0236e230c818bc2" + integrity sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg== + +acorn-node@^1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^7.0.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +animate.css@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/animate.css/-/animate.css-4.1.1.tgz#614ec5a81131d7e4dc362a58143f7406abd68075" + integrity sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +autoprefixer@^10.4.4: + version "10.4.13" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.13.tgz#b5136b59930209a321e9fa3dca2e7c4d223e83a8" + integrity sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg== + dependencies: + browserslist "^4.21.4" + caniuse-lite "^1.0.30001426" + fraction.js "^4.2.0" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.21.4: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== + dependencies: + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" + node-releases "^2.0.6" + update-browserslist-db "^1.0.9" + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001426: + version "1.0.30001439" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz#ab7371faeb4adff4b74dad1718a6fd122e45d9cb" + integrity sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A== + +chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +color-name@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +csstype@^2.6.8: + version "2.6.21" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e" + integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w== + +defined@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf" + integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== + +detective@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" + integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== + dependencies: + acorn-node "^1.8.2" + defined "^1.0.0" + minimist "^1.2.6" + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +electron-to-chromium@^1.4.251: + version "1.4.284" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" + integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== + +esbuild-android-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be" + integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ== + +esbuild-android-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771" + integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg== + +esbuild-darwin-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25" + integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug== + +esbuild-darwin-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73" + integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw== + +esbuild-freebsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d" + integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg== + +esbuild-freebsd-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48" + integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q== + +esbuild-linux-32@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5" + integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw== + +esbuild-linux-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652" + integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg== + +esbuild-linux-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b" + integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== + +esbuild-linux-arm@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59" + integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw== + +esbuild-linux-mips64le@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34" + integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw== + +esbuild-linux-ppc64le@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e" + integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ== + +esbuild-linux-riscv64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8" + integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg== + +esbuild-linux-s390x@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6" + integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA== + +esbuild-netbsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81" + integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w== + +esbuild-openbsd-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b" + integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw== + +esbuild-sunos-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da" + integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw== + +esbuild-windows-32@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31" + integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w== + +esbuild-windows-64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4" + integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ== + +esbuild-windows-arm64@0.14.54: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982" + integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg== + +esbuild@^0.14.27: + version "0.14.54" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2" + integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA== + optionalDependencies: + "@esbuild/linux-loong64" "0.14.54" + esbuild-android-64 "0.14.54" + esbuild-android-arm64 "0.14.54" + esbuild-darwin-64 "0.14.54" + esbuild-darwin-arm64 "0.14.54" + esbuild-freebsd-64 "0.14.54" + esbuild-freebsd-arm64 "0.14.54" + esbuild-linux-32 "0.14.54" + esbuild-linux-64 "0.14.54" + esbuild-linux-arm "0.14.54" + esbuild-linux-arm64 "0.14.54" + esbuild-linux-mips64le "0.14.54" + esbuild-linux-ppc64le "0.14.54" + esbuild-linux-riscv64 "0.14.54" + esbuild-linux-s390x "0.14.54" + esbuild-netbsd-64 "0.14.54" + esbuild-openbsd-64 "0.14.54" + esbuild-sunos-64 "0.14.54" + esbuild-windows-32 "0.14.54" + esbuild-windows-64 "0.14.54" + esbuild-windows-arm64 "0.14.54" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +fast-glob@^3.2.12: + version "3.2.12" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fastq@^1.6.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.14.0.tgz#107f69d7295b11e0fccc264e1fc6389f623731ce" + integrity sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg== + dependencies: + reusify "^1.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +fraction.js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" + integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +lilconfig@^2.0.5, lilconfig@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" + integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== + +magic-string@^0.25.7: + version "0.25.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +minimist@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + +mitt@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd" + integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ== + +nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + +node-releases@^2.0.6: + version "2.0.8" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae" + integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pinia@^2.0.13: + version "2.0.28" + resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.0.28.tgz#887c982d854972042d9bdfd5bc4fad3b9d6ab02a" + integrity sha512-YClq9DkqCblq9rlyUual7ezMu/iICWdBtfJrDt4oWU9Zxpijyz7xB2xTwx57DaBQ96UGvvTMORzALr+iO5PVMw== + dependencies: + "@vue/devtools-api" "^6.4.5" + vue-demi "*" + +postcss-import@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" + integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00" + integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== + dependencies: + camelcase-css "^2.0.1" + +postcss-load-config@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" + integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== + dependencies: + lilconfig "^2.0.5" + yaml "^1.10.2" + +postcss-nested@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.0.tgz#1572f1984736578f360cffc7eb7dca69e30d1735" + integrity sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w== + dependencies: + postcss-selector-parser "^6.0.10" + +postcss-selector-parser@^6.0.10: + version "6.0.11" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz#2e41dc39b7ad74046e1615185185cd0b17d0c8dc" + integrity sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.1.10, postcss@^8.4.12, postcss@^8.4.13, postcss@^8.4.18: + version "8.4.20" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.20.tgz#64c52f509644cecad8567e949f4081d98349dc56" + integrity sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +resolve@^1.1.7, resolve@^1.22.0, resolve@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +"rollup@>=2.59.0 <2.78.0": + version "2.77.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12" + integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g== + optionalDependencies: + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tailwindcss@^3.0.24: + version "3.2.4" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.2.4.tgz#afe3477e7a19f3ceafb48e4b083e292ce0dc0250" + integrity sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ== + dependencies: + arg "^5.0.2" + chokidar "^3.5.3" + color-name "^1.1.4" + detective "^5.2.1" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.12" + glob-parent "^6.0.2" + is-glob "^4.0.3" + lilconfig "^2.0.6" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.18" + postcss-import "^14.1.0" + postcss-js "^4.0.0" + postcss-load-config "^3.1.4" + postcss-nested "6.0.0" + postcss-selector-parser "^6.0.10" + postcss-value-parser "^4.2.0" + quick-lru "^5.1.1" + resolve "^1.22.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +typescript@^4.6.2: + version "4.9.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== + +update-browserslist-db@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +vite@^2.9.2: + version "2.9.15" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.15.tgz#2858dd5b2be26aa394a283e62324281892546f0b" + integrity sha512-fzMt2jK4vQ3yK56te3Kqpkaeq9DkcZfBbzHwYpobasvgYmP2SoAr6Aic05CsB4CzCZbsDv4sujX3pkEGhLabVQ== + dependencies: + esbuild "^0.14.27" + postcss "^8.4.13" + resolve "^1.22.0" + rollup ">=2.59.0 <2.78.0" + optionalDependencies: + fsevents "~2.3.2" + +vue-demi@*: + version "0.13.11" + resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99" + integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A== + +vue-router@4: + version "4.1.6" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.1.6.tgz#b70303737e12b4814578d21d68d21618469375a1" + integrity sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ== + dependencies: + "@vue/devtools-api" "^6.4.5" + +vue-tsc@^0.34.6: + version "0.34.17" + resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-0.34.17.tgz#332fc5c31d64bb9b74b0f26050f3ab067a9a7d6f" + integrity sha512-jzUXky44ZLHC4daaJag7FQr3idlPYN719/K1eObGljz5KaS2UnVGTU/XSYCd7d6ampYYg4OsyalbHyJIxV0aEQ== + dependencies: + "@volar/vue-typescript" "0.34.17" + +vue@^3.2.20, vue@^3.2.25: + version "3.2.45" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.45.tgz#94a116784447eb7dbd892167784619fef379b3c8" + integrity sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA== + dependencies: + "@vue/compiler-dom" "3.2.45" + "@vue/compiler-sfc" "3.2.45" + "@vue/runtime-dom" "3.2.45" + "@vue/server-renderer" "3.2.45" + "@vue/shared" "3.2.45" + +xtend@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yaml@^1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== diff --git a/oafLib/.prettierrc.js b/oafLib/.prettierrc.js new file mode 100644 index 0000000..5df303c --- /dev/null +++ b/oafLib/.prettierrc.js @@ -0,0 +1,28 @@ +/* + * .prettierrc.js + * Copyright (C) 2022 veypi + * + * Distributed under terms of the Apache license. + */ + +module.exports = { + printWidth: 100, //单行长度 + tabWidth: 2, //缩进长度 + useTabs: false, //使用空格代替tab缩进 + semi: false, //句末使用分号 + singleQuote: true, //使用单引号 + quoteProps: 'as-needed', //仅在必需时为对象的key添加引号 + jsxSingleQuote: true, // jsx中使用单引号 + trailingComma: 'all', //多行时尽可能打印尾随逗号 + bracketSpacing: true, //在对象前后添加空格-eg: { foo: bar } + jsxBracketSameLine: true, //多属性html标签的‘>’折行放置 + arrowParens: 'always', //单参数箭头函数参数周围使用圆括号-eg: (x) => x + requirePragma: false, //无需顶部注释即可格式化 + insertPragma: false, //在已被preitter格式化的文件顶部加上标注 + proseWrap: 'preserve', //不知道怎么翻译 + htmlWhitespaceSensitivity: 'ignore', //对HTML全局空白不敏感 + vueIndentScriptAndStyle: false, //不对vue中的script及style标签缩进 + endOfLine: 'lf', //结束行形式 + embeddedLanguageFormatting: 'auto', //对引用代码进行格式化 +} + diff --git a/oafLib/package.json b/oafLib/package.json new file mode 100644 index 0000000..f8fa2d5 --- /dev/null +++ b/oafLib/package.json @@ -0,0 +1,11 @@ +{ + "name": "oaflib", + "version": "1.0.0", + "description": "", + "main": "index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "veypi", + "license": "MIT" +}