diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8fcb840 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + # Enable version updates for npm + - package-ecosystem: npm + # Look for `package.json` and `lock` files in the `root` directory + directory: / + # Check the npm registry for updates every day (weekdays) + schedule: + interval: weekly diff --git a/.github/workflows/dependabot-auto-approve.yml b/.github/workflows/dependabot-auto-approve.yml new file mode 100644 index 0000000..00d9d5a --- /dev/null +++ b/.github/workflows/dependabot-auto-approve.yml @@ -0,0 +1,21 @@ +name: Dependabot auto-approve +on: pull_request + +permissions: + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Approve a PR + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..a801868 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,100 @@ +# 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程 +# +name: Deploy VitePress site to Pages + +on: + # 在针对 `main` 分支的推送上运行。如果你 + # 使用 `master` 分支作为默认分支,请将其更改为 `master` + push: + branches: [main] + + # 允许你从 Actions 选项卡手动运行此工作流程 + workflow_dispatch: + +# 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列 +# 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成 +concurrency: + group: pages + cancel-in-progress: false + +jobs: + # 构建工作 + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 # 如果未启用 lastUpdated,则不需要 + submodules: recursive + - uses: pnpm/action-setup@v3 # 如果使用 pnpm,请取消注释 + with: + version: 9 + run_install: false + # - uses: oven-sh/setup-bun@v1 # 如果使用 Bun,请取消注释 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 21 + cache: pnpm # 或 pnpm / yarn + + - name: Clone EasyTier + uses: GuillaumeFalourd/clone-github-repo-action@v2.3 + with: + depth: 1 + branch: 'main' + owner: 'EasyTier' + repository: 'EasyTier' + - name: Build EasyTier Web + run: | + cd EasyTier + pnpm -r install + WEB_BASE_URL=/web pnpm -r build + cd .. + mkdir -p public/web + cp -r EasyTier/easytier-web/frontend/dist/* public/web/ + rm -rf EasyTier + + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Install dependencies + run: pnpm -r install # 或 pnpm install / yarn install / bun install + - name: Build with VitePress + run: pnpm run docs:build # 或 pnpm docs:build / yarn docs:build / bun run docs:build + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: .vitepress/dist + - name: Upload OSS + if: ${{ github.ref == 'refs/heads/main' }} + uses: Menci/upload-to-oss@main + with: + access-key-id: ${{ secrets.ALIYUN_OSS_ACCESS_ID }} + access-key-secret: ${{ secrets.ALIYUN_OSS_ACCESS_KEY }} + endpoint: ${{ secrets.ALIYUN_OSS_ENDPOINT }} + bucket: ${{ secrets.ALIYUN_OSS_BUCKET }} + local-path: .vitepress/dist + remote-path: / + no-delete-remote-files: false + retry: 5 + delay-html-file-upload: true + + + # 部署工作 + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + name: Deploy + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a30c2b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.log +*.tgz +.DS_Store +.idea +.temp +.vite_opt_cache +.vscode +!.vscode/settings.json +!.vscode/extensions.json +dist +cache +temp +node_modules +.vitepress/cache \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e4d3f1f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule ".vitepress/third_party/lumen"] + path = .vitepress/third_party/lumen + url = https://github.com/KKRainbow/lumen.git diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..1cfbd6f --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +shamefully-hoist=true +strict-peer-dependencies=false \ No newline at end of file diff --git a/.vitepress/cache/deps/@theme_index.js b/.vitepress/cache/deps/@theme_index.js deleted file mode 100644 index 9c6c63c..0000000 --- a/.vitepress/cache/deps/@theme_index.js +++ /dev/null @@ -1,41 +0,0 @@ -// ../../Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/index.js -import "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/styles/fonts.css"; - -// ../../Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/without-fonts.js -import "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/styles/vars.css"; -import "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/styles/base.css"; -import "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/styles/utils.css"; -import "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css"; -import "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css"; -import "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css"; -import "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css"; -import "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css"; -import VPBadge from "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue"; -import Layout from "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/Layout.vue"; -import { default as default2 } from "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue"; -import { default as default3 } from "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue"; -import { default as default4 } from "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue"; -import { default as default5 } from "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue"; -import { default as default6 } from "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue"; -import { default as default7 } from "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue"; -import { default as default8 } from "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue"; -import { default as default9 } from "C:/Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue"; -var theme = { - Layout, - enhanceApp: ({ app }) => { - app.component("Badge", VPBadge); - } -}; -var without_fonts_default = theme; -export { - default5 as VPDocAsideSponsors, - default3 as VPHomeFeatures, - default2 as VPHomeHero, - default4 as VPHomeSponsors, - default9 as VPTeamMembers, - default6 as VPTeamPage, - default8 as VPTeamPageSection, - default7 as VPTeamPageTitle, - without_fonts_default as default -}; -//# sourceMappingURL=@theme_index.js.map diff --git a/.vitepress/cache/deps/@theme_index.js.map b/.vitepress/cache/deps/@theme_index.js.map deleted file mode 100644 index bc8abb1..0000000 --- a/.vitepress/cache/deps/@theme_index.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../../../../Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/index.js", "../../../../../Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/without-fonts.js"], - "sourcesContent": ["import './styles/fonts.css';\nexport * from './without-fonts';\nexport { default as default } from './without-fonts';\n", "import './styles/vars.css';\nimport './styles/base.css';\nimport './styles/utils.css';\nimport './styles/components/custom-block.css';\nimport './styles/components/vp-code.css';\nimport './styles/components/vp-code-group.css';\nimport './styles/components/vp-doc.css';\nimport './styles/components/vp-sponsor.css';\nimport VPBadge from './components/VPBadge.vue';\nimport Layout from './Layout.vue';\n// Note: if we add more optional components here, i.e. components that are not\n// used in the theme by default unless the user imports them, make sure to update\n// the `lazyDefaultThemeComponentsRE` regex in src/node/build/bundle.ts.\nexport { default as VPHomeHero } from './components/VPHomeHero.vue';\nexport { default as VPHomeFeatures } from './components/VPHomeFeatures.vue';\nexport { default as VPHomeSponsors } from './components/VPHomeSponsors.vue';\nexport { default as VPDocAsideSponsors } from './components/VPDocAsideSponsors.vue';\nexport { default as VPTeamPage } from './components/VPTeamPage.vue';\nexport { default as VPTeamPageTitle } from './components/VPTeamPageTitle.vue';\nexport { default as VPTeamPageSection } from './components/VPTeamPageSection.vue';\nexport { default as VPTeamMembers } from './components/VPTeamMembers.vue';\nconst theme = {\n Layout,\n enhanceApp: ({ app }) => {\n app.component('Badge', VPBadge);\n }\n};\nexport default theme;\n"], - "mappings": ";AAAA,OAAO;;;ACAP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO,aAAa;AACpB,OAAO,YAAY;AAInB,SAAoB,WAAXA,gBAA6B;AACtC,SAAoB,WAAXA,gBAAiC;AAC1C,SAAoB,WAAXA,gBAAiC;AAC1C,SAAoB,WAAXA,gBAAqC;AAC9C,SAAoB,WAAXA,gBAA6B;AACtC,SAAoB,WAAXA,gBAAkC;AAC3C,SAAoB,WAAXA,gBAAoC;AAC7C,SAAoB,WAAXA,gBAAgC;AACzC,IAAM,QAAQ;AAAA,EACV;AAAA,EACA,YAAY,CAAC,EAAE,IAAI,MAAM;AACrB,QAAI,UAAU,SAAS,OAAO;AAAA,EAClC;AACJ;AACA,IAAO,wBAAQ;", - "names": ["default"] -} diff --git a/.vitepress/cache/deps/_metadata.json b/.vitepress/cache/deps/_metadata.json deleted file mode 100644 index 8faf890..0000000 --- a/.vitepress/cache/deps/_metadata.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "hash": "7958035b", - "browserHash": "24330d08", - "optimized": { - "vue": { - "src": "../../../../../Users/35154/AppData/Local/pnpm/global/5/.pnpm/vue@3.2.47/node_modules/vue/dist/vue.runtime.esm-bundler.js", - "file": "vue.js", - "fileHash": "0201f78f", - "needsInterop": false - }, - "@theme/index": { - "src": "../../../../../Users/35154/AppData/Local/pnpm/global/5/.pnpm/vitepress@1.0.0-alpha.75_@algolia+client-search@4.17.0_@types+node@18.16.3/node_modules/vitepress/dist/client/theme-default/index.js", - "file": "@theme_index.js", - "fileHash": "925c57a5", - "needsInterop": false - } - }, - "chunks": {} -} \ No newline at end of file diff --git a/.vitepress/cache/deps/package.json b/.vitepress/cache/deps/package.json deleted file mode 100644 index 3dbc1ca..0000000 --- a/.vitepress/cache/deps/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "module" -} diff --git a/.vitepress/cache/deps/vue.js b/.vitepress/cache/deps/vue.js deleted file mode 100644 index 832f311..0000000 --- a/.vitepress/cache/deps/vue.js +++ /dev/null @@ -1,9516 +0,0 @@ -// ../../Users/35154/AppData/Local/pnpm/global/5/.pnpm/@vue+shared@3.2.47/node_modules/@vue/shared/dist/shared.esm-bundler.js -function makeMap(str, expectsLowerCase) { - const map2 = /* @__PURE__ */ Object.create(null); - const list = str.split(","); - for (let i = 0; i < list.length; i++) { - map2[list[i]] = true; - } - return expectsLowerCase ? (val) => !!map2[val.toLowerCase()] : (val) => !!map2[val]; -} -var GLOBALS_WHITE_LISTED = "Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt"; -var isGloballyWhitelisted = makeMap(GLOBALS_WHITE_LISTED); -function normalizeStyle(value) { - if (isArray(value)) { - const res = {}; - for (let i = 0; i < value.length; i++) { - const item = value[i]; - const normalized = isString(item) ? parseStringStyle(item) : normalizeStyle(item); - if (normalized) { - for (const key in normalized) { - res[key] = normalized[key]; - } - } - } - return res; - } else if (isString(value)) { - return value; - } else if (isObject(value)) { - return value; - } -} -var listDelimiterRE = /;(?![^(]*\))/g; -var propertyDelimiterRE = /:([^]+)/; -var styleCommentRE = /\/\*.*?\*\//gs; -function parseStringStyle(cssText) { - const ret = {}; - cssText.replace(styleCommentRE, "").split(listDelimiterRE).forEach((item) => { - if (item) { - const tmp = item.split(propertyDelimiterRE); - tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim()); - } - }); - return ret; -} -function normalizeClass(value) { - let res = ""; - if (isString(value)) { - res = value; - } else if (isArray(value)) { - for (let i = 0; i < value.length; i++) { - const normalized = normalizeClass(value[i]); - if (normalized) { - res += normalized + " "; - } - } - } else if (isObject(value)) { - for (const name in value) { - if (value[name]) { - res += name + " "; - } - } - } - return res.trim(); -} -function normalizeProps(props) { - if (!props) - return null; - let { class: klass, style } = props; - if (klass && !isString(klass)) { - props.class = normalizeClass(klass); - } - if (style) { - props.style = normalizeStyle(style); - } - return props; -} -var HTML_TAGS = "html,body,base,head,link,meta,style,title,address,article,aside,footer,header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot"; -var SVG_TAGS = "svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view"; -var VOID_TAGS = "area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr"; -var isHTMLTag = makeMap(HTML_TAGS); -var isSVGTag = makeMap(SVG_TAGS); -var isVoidTag = makeMap(VOID_TAGS); -var specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`; -var isSpecialBooleanAttr = makeMap(specialBooleanAttrs); -var isBooleanAttr = makeMap(specialBooleanAttrs + `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected`); -function includeBooleanAttr(value) { - return !!value || value === ""; -} -var isKnownHtmlAttr = makeMap(`accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap`); -var isKnownSvgAttr = makeMap(`xmlns,accent-height,accumulate,additive,alignment-baseline,alphabetic,amplitude,arabic-form,ascent,attributeName,attributeType,azimuth,baseFrequency,baseline-shift,baseProfile,bbox,begin,bias,by,calcMode,cap-height,class,clip,clipPathUnits,clip-path,clip-rule,color,color-interpolation,color-interpolation-filters,color-profile,color-rendering,contentScriptType,contentStyleType,crossorigin,cursor,cx,cy,d,decelerate,descent,diffuseConstant,direction,display,divisor,dominant-baseline,dur,dx,dy,edgeMode,elevation,enable-background,end,exponent,fill,fill-opacity,fill-rule,filter,filterRes,filterUnits,flood-color,flood-opacity,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,format,from,fr,fx,fy,g1,g2,glyph-name,glyph-orientation-horizontal,glyph-orientation-vertical,glyphRef,gradientTransform,gradientUnits,hanging,height,href,hreflang,horiz-adv-x,horiz-origin-x,id,ideographic,image-rendering,in,in2,intercept,k,k1,k2,k3,k4,kernelMatrix,kernelUnitLength,kerning,keyPoints,keySplines,keyTimes,lang,lengthAdjust,letter-spacing,lighting-color,limitingConeAngle,local,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mask,maskContentUnits,maskUnits,mathematical,max,media,method,min,mode,name,numOctaves,offset,opacity,operator,order,orient,orientation,origin,overflow,overline-position,overline-thickness,panose-1,paint-order,path,pathLength,patternContentUnits,patternTransform,patternUnits,ping,pointer-events,points,pointsAtX,pointsAtY,pointsAtZ,preserveAlpha,preserveAspectRatio,primitiveUnits,r,radius,referrerPolicy,refX,refY,rel,rendering-intent,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,result,rotate,rx,ry,scale,seed,shape-rendering,slope,spacing,specularConstant,specularExponent,speed,spreadMethod,startOffset,stdDeviation,stemh,stemv,stitchTiles,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,string,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,style,surfaceScale,systemLanguage,tabindex,tableValues,target,targetX,targetY,text-anchor,text-decoration,text-rendering,textLength,to,transform,transform-origin,type,u1,u2,underline-position,underline-thickness,unicode,unicode-bidi,unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`); -function looseCompareArrays(a, b) { - if (a.length !== b.length) - return false; - let equal = true; - for (let i = 0; equal && i < a.length; i++) { - equal = looseEqual(a[i], b[i]); - } - return equal; -} -function looseEqual(a, b) { - if (a === b) - return true; - let aValidType = isDate(a); - let bValidType = isDate(b); - if (aValidType || bValidType) { - return aValidType && bValidType ? a.getTime() === b.getTime() : false; - } - aValidType = isSymbol(a); - bValidType = isSymbol(b); - if (aValidType || bValidType) { - return a === b; - } - aValidType = isArray(a); - bValidType = isArray(b); - if (aValidType || bValidType) { - return aValidType && bValidType ? looseCompareArrays(a, b) : false; - } - aValidType = isObject(a); - bValidType = isObject(b); - if (aValidType || bValidType) { - if (!aValidType || !bValidType) { - return false; - } - const aKeysCount = Object.keys(a).length; - const bKeysCount = Object.keys(b).length; - if (aKeysCount !== bKeysCount) { - return false; - } - for (const key in a) { - const aHasKey = a.hasOwnProperty(key); - const bHasKey = b.hasOwnProperty(key); - if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key])) { - return false; - } - } - } - return String(a) === String(b); -} -function looseIndexOf(arr, val) { - return arr.findIndex((item) => looseEqual(item, val)); -} -var toDisplayString = (val) => { - return isString(val) ? val : val == null ? "" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? JSON.stringify(val, replacer, 2) : String(val); -}; -var replacer = (_key, val) => { - if (val && val.__v_isRef) { - return replacer(_key, val.value); - } else if (isMap(val)) { - return { - [`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val2]) => { - entries[`${key} =>`] = val2; - return entries; - }, {}) - }; - } else if (isSet(val)) { - return { - [`Set(${val.size})`]: [...val.values()] - }; - } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) { - return String(val); - } - return val; -}; -var EMPTY_OBJ = true ? Object.freeze({}) : {}; -var EMPTY_ARR = true ? Object.freeze([]) : []; -var NOOP = () => { -}; -var NO = () => false; -var onRE = /^on[^a-z]/; -var isOn = (key) => onRE.test(key); -var isModelListener = (key) => key.startsWith("onUpdate:"); -var extend = Object.assign; -var remove = (arr, el) => { - const i = arr.indexOf(el); - if (i > -1) { - arr.splice(i, 1); - } -}; -var hasOwnProperty = Object.prototype.hasOwnProperty; -var hasOwn = (val, key) => hasOwnProperty.call(val, key); -var isArray = Array.isArray; -var isMap = (val) => toTypeString(val) === "[object Map]"; -var isSet = (val) => toTypeString(val) === "[object Set]"; -var isDate = (val) => toTypeString(val) === "[object Date]"; -var isRegExp = (val) => toTypeString(val) === "[object RegExp]"; -var isFunction = (val) => typeof val === "function"; -var isString = (val) => typeof val === "string"; -var isSymbol = (val) => typeof val === "symbol"; -var isObject = (val) => val !== null && typeof val === "object"; -var isPromise = (val) => { - return isObject(val) && isFunction(val.then) && isFunction(val.catch); -}; -var objectToString = Object.prototype.toString; -var toTypeString = (value) => objectToString.call(value); -var toRawType = (value) => { - return toTypeString(value).slice(8, -1); -}; -var isPlainObject = (val) => toTypeString(val) === "[object Object]"; -var isIntegerKey = (key) => isString(key) && key !== "NaN" && key[0] !== "-" && "" + parseInt(key, 10) === key; -var isReservedProp = makeMap( - // the leading comma is intentional so empty string "" is also included - ",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted" -); -var isBuiltInDirective = makeMap("bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo"); -var cacheStringFunction = (fn) => { - const cache = /* @__PURE__ */ Object.create(null); - return (str) => { - const hit = cache[str]; - return hit || (cache[str] = fn(str)); - }; -}; -var camelizeRE = /-(\w)/g; -var camelize = cacheStringFunction((str) => { - return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : ""); -}); -var hyphenateRE = /\B([A-Z])/g; -var hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, "-$1").toLowerCase()); -var capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1)); -var toHandlerKey = cacheStringFunction((str) => str ? `on${capitalize(str)}` : ``); -var hasChanged = (value, oldValue) => !Object.is(value, oldValue); -var invokeArrayFns = (fns, arg) => { - for (let i = 0; i < fns.length; i++) { - fns[i](arg); - } -}; -var def = (obj, key, value) => { - Object.defineProperty(obj, key, { - configurable: true, - enumerable: false, - value - }); -}; -var looseToNumber = (val) => { - const n = parseFloat(val); - return isNaN(n) ? val : n; -}; -var toNumber = (val) => { - const n = isString(val) ? Number(val) : NaN; - return isNaN(n) ? val : n; -}; -var _globalThis; -var getGlobalThis = () => { - return _globalThis || (_globalThis = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {}); -}; - -// ../../Users/35154/AppData/Local/pnpm/global/5/.pnpm/@vue+reactivity@3.2.47/node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js -function warn(msg, ...args) { - console.warn(`[Vue warn] ${msg}`, ...args); -} -var activeEffectScope; -var EffectScope = class { - constructor(detached = false) { - this.detached = detached; - this._active = true; - this.effects = []; - this.cleanups = []; - this.parent = activeEffectScope; - if (!detached && activeEffectScope) { - this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(this) - 1; - } - } - get active() { - return this._active; - } - run(fn) { - if (this._active) { - const currentEffectScope = activeEffectScope; - try { - activeEffectScope = this; - return fn(); - } finally { - activeEffectScope = currentEffectScope; - } - } else if (true) { - warn(`cannot run an inactive effect scope.`); - } - } - /** - * This should only be called on non-detached scopes - * @internal - */ - on() { - activeEffectScope = this; - } - /** - * This should only be called on non-detached scopes - * @internal - */ - off() { - activeEffectScope = this.parent; - } - stop(fromParent) { - if (this._active) { - let i, l; - for (i = 0, l = this.effects.length; i < l; i++) { - this.effects[i].stop(); - } - for (i = 0, l = this.cleanups.length; i < l; i++) { - this.cleanups[i](); - } - if (this.scopes) { - for (i = 0, l = this.scopes.length; i < l; i++) { - this.scopes[i].stop(true); - } - } - if (!this.detached && this.parent && !fromParent) { - const last = this.parent.scopes.pop(); - if (last && last !== this) { - this.parent.scopes[this.index] = last; - last.index = this.index; - } - } - this.parent = void 0; - this._active = false; - } - } -}; -function effectScope(detached) { - return new EffectScope(detached); -} -function recordEffectScope(effect2, scope = activeEffectScope) { - if (scope && scope.active) { - scope.effects.push(effect2); - } -} -function getCurrentScope() { - return activeEffectScope; -} -function onScopeDispose(fn) { - if (activeEffectScope) { - activeEffectScope.cleanups.push(fn); - } else if (true) { - warn(`onScopeDispose() is called when there is no active effect scope to be associated with.`); - } -} -var createDep = (effects) => { - const dep = new Set(effects); - dep.w = 0; - dep.n = 0; - return dep; -}; -var wasTracked = (dep) => (dep.w & trackOpBit) > 0; -var newTracked = (dep) => (dep.n & trackOpBit) > 0; -var initDepMarkers = ({ deps }) => { - if (deps.length) { - for (let i = 0; i < deps.length; i++) { - deps[i].w |= trackOpBit; - } - } -}; -var finalizeDepMarkers = (effect2) => { - const { deps } = effect2; - if (deps.length) { - let ptr = 0; - for (let i = 0; i < deps.length; i++) { - const dep = deps[i]; - if (wasTracked(dep) && !newTracked(dep)) { - dep.delete(effect2); - } else { - deps[ptr++] = dep; - } - dep.w &= ~trackOpBit; - dep.n &= ~trackOpBit; - } - deps.length = ptr; - } -}; -var targetMap = /* @__PURE__ */ new WeakMap(); -var effectTrackDepth = 0; -var trackOpBit = 1; -var maxMarkerBits = 30; -var activeEffect; -var ITERATE_KEY = Symbol(true ? "iterate" : ""); -var MAP_KEY_ITERATE_KEY = Symbol(true ? "Map key iterate" : ""); -var ReactiveEffect = class { - constructor(fn, scheduler = null, scope) { - this.fn = fn; - this.scheduler = scheduler; - this.active = true; - this.deps = []; - this.parent = void 0; - recordEffectScope(this, scope); - } - run() { - if (!this.active) { - return this.fn(); - } - let parent = activeEffect; - let lastShouldTrack = shouldTrack; - while (parent) { - if (parent === this) { - return; - } - parent = parent.parent; - } - try { - this.parent = activeEffect; - activeEffect = this; - shouldTrack = true; - trackOpBit = 1 << ++effectTrackDepth; - if (effectTrackDepth <= maxMarkerBits) { - initDepMarkers(this); - } else { - cleanupEffect(this); - } - return this.fn(); - } finally { - if (effectTrackDepth <= maxMarkerBits) { - finalizeDepMarkers(this); - } - trackOpBit = 1 << --effectTrackDepth; - activeEffect = this.parent; - shouldTrack = lastShouldTrack; - this.parent = void 0; - if (this.deferStop) { - this.stop(); - } - } - } - stop() { - if (activeEffect === this) { - this.deferStop = true; - } else if (this.active) { - cleanupEffect(this); - if (this.onStop) { - this.onStop(); - } - this.active = false; - } - } -}; -function cleanupEffect(effect2) { - const { deps } = effect2; - if (deps.length) { - for (let i = 0; i < deps.length; i++) { - deps[i].delete(effect2); - } - deps.length = 0; - } -} -function effect(fn, options) { - if (fn.effect) { - fn = fn.effect.fn; - } - const _effect = new ReactiveEffect(fn); - if (options) { - extend(_effect, options); - if (options.scope) - recordEffectScope(_effect, options.scope); - } - if (!options || !options.lazy) { - _effect.run(); - } - const runner = _effect.run.bind(_effect); - runner.effect = _effect; - return runner; -} -function stop(runner) { - runner.effect.stop(); -} -var shouldTrack = true; -var trackStack = []; -function pauseTracking() { - trackStack.push(shouldTrack); - shouldTrack = false; -} -function resetTracking() { - const last = trackStack.pop(); - shouldTrack = last === void 0 ? true : last; -} -function track(target, type, key) { - if (shouldTrack && activeEffect) { - let depsMap = targetMap.get(target); - if (!depsMap) { - targetMap.set(target, depsMap = /* @__PURE__ */ new Map()); - } - let dep = depsMap.get(key); - if (!dep) { - depsMap.set(key, dep = createDep()); - } - const eventInfo = true ? { effect: activeEffect, target, type, key } : void 0; - trackEffects(dep, eventInfo); - } -} -function trackEffects(dep, debuggerEventExtraInfo) { - let shouldTrack2 = false; - if (effectTrackDepth <= maxMarkerBits) { - if (!newTracked(dep)) { - dep.n |= trackOpBit; - shouldTrack2 = !wasTracked(dep); - } - } else { - shouldTrack2 = !dep.has(activeEffect); - } - if (shouldTrack2) { - dep.add(activeEffect); - activeEffect.deps.push(dep); - if (activeEffect.onTrack) { - activeEffect.onTrack(Object.assign({ effect: activeEffect }, debuggerEventExtraInfo)); - } - } -} -function trigger(target, type, key, newValue, oldValue, oldTarget) { - const depsMap = targetMap.get(target); - if (!depsMap) { - return; - } - let deps = []; - if (type === "clear") { - deps = [...depsMap.values()]; - } else if (key === "length" && isArray(target)) { - const newLength = Number(newValue); - depsMap.forEach((dep, key2) => { - if (key2 === "length" || key2 >= newLength) { - deps.push(dep); - } - }); - } else { - if (key !== void 0) { - deps.push(depsMap.get(key)); - } - switch (type) { - case "add": - if (!isArray(target)) { - deps.push(depsMap.get(ITERATE_KEY)); - if (isMap(target)) { - deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)); - } - } else if (isIntegerKey(key)) { - deps.push(depsMap.get("length")); - } - break; - case "delete": - if (!isArray(target)) { - deps.push(depsMap.get(ITERATE_KEY)); - if (isMap(target)) { - deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)); - } - } - break; - case "set": - if (isMap(target)) { - deps.push(depsMap.get(ITERATE_KEY)); - } - break; - } - } - const eventInfo = true ? { target, type, key, newValue, oldValue, oldTarget } : void 0; - if (deps.length === 1) { - if (deps[0]) { - if (true) { - triggerEffects(deps[0], eventInfo); - } else { - triggerEffects(deps[0]); - } - } - } else { - const effects = []; - for (const dep of deps) { - if (dep) { - effects.push(...dep); - } - } - if (true) { - triggerEffects(createDep(effects), eventInfo); - } else { - triggerEffects(createDep(effects)); - } - } -} -function triggerEffects(dep, debuggerEventExtraInfo) { - const effects = isArray(dep) ? dep : [...dep]; - for (const effect2 of effects) { - if (effect2.computed) { - triggerEffect(effect2, debuggerEventExtraInfo); - } - } - for (const effect2 of effects) { - if (!effect2.computed) { - triggerEffect(effect2, debuggerEventExtraInfo); - } - } -} -function triggerEffect(effect2, debuggerEventExtraInfo) { - if (effect2 !== activeEffect || effect2.allowRecurse) { - if (effect2.onTrigger) { - effect2.onTrigger(extend({ effect: effect2 }, debuggerEventExtraInfo)); - } - if (effect2.scheduler) { - effect2.scheduler(); - } else { - effect2.run(); - } - } -} -function getDepFromReactive(object, key) { - var _a2; - return (_a2 = targetMap.get(object)) === null || _a2 === void 0 ? void 0 : _a2.get(key); -} -var isNonTrackableKeys = makeMap(`__proto__,__v_isRef,__isVue`); -var builtInSymbols = new Set( - Object.getOwnPropertyNames(Symbol).filter((key) => key !== "arguments" && key !== "caller").map((key) => Symbol[key]).filter(isSymbol) -); -var get$1 = createGetter(); -var shallowGet = createGetter(false, true); -var readonlyGet = createGetter(true); -var shallowReadonlyGet = createGetter(true, true); -var arrayInstrumentations = createArrayInstrumentations(); -function createArrayInstrumentations() { - const instrumentations = {}; - ["includes", "indexOf", "lastIndexOf"].forEach((key) => { - instrumentations[key] = function(...args) { - const arr = toRaw(this); - for (let i = 0, l = this.length; i < l; i++) { - track(arr, "get", i + ""); - } - const res = arr[key](...args); - if (res === -1 || res === false) { - return arr[key](...args.map(toRaw)); - } else { - return res; - } - }; - }); - ["push", "pop", "shift", "unshift", "splice"].forEach((key) => { - instrumentations[key] = function(...args) { - pauseTracking(); - const res = toRaw(this)[key].apply(this, args); - resetTracking(); - return res; - }; - }); - return instrumentations; -} -function hasOwnProperty2(key) { - const obj = toRaw(this); - track(obj, "has", key); - return obj.hasOwnProperty(key); -} -function createGetter(isReadonly2 = false, shallow = false) { - return function get2(target, key, receiver) { - if (key === "__v_isReactive") { - return !isReadonly2; - } else if (key === "__v_isReadonly") { - return isReadonly2; - } else if (key === "__v_isShallow") { - return shallow; - } else if (key === "__v_raw" && receiver === (isReadonly2 ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap).get(target)) { - return target; - } - const targetIsArray = isArray(target); - if (!isReadonly2) { - if (targetIsArray && hasOwn(arrayInstrumentations, key)) { - return Reflect.get(arrayInstrumentations, key, receiver); - } - if (key === "hasOwnProperty") { - return hasOwnProperty2; - } - } - const res = Reflect.get(target, key, receiver); - if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { - return res; - } - if (!isReadonly2) { - track(target, "get", key); - } - if (shallow) { - return res; - } - if (isRef(res)) { - return targetIsArray && isIntegerKey(key) ? res : res.value; - } - if (isObject(res)) { - return isReadonly2 ? readonly(res) : reactive(res); - } - return res; - }; -} -var set$1 = createSetter(); -var shallowSet = createSetter(true); -function createSetter(shallow = false) { - return function set2(target, key, value, receiver) { - let oldValue = target[key]; - if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) { - return false; - } - if (!shallow) { - if (!isShallow(value) && !isReadonly(value)) { - oldValue = toRaw(oldValue); - value = toRaw(value); - } - if (!isArray(target) && isRef(oldValue) && !isRef(value)) { - oldValue.value = value; - return true; - } - } - const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key); - const result = Reflect.set(target, key, value, receiver); - if (target === toRaw(receiver)) { - if (!hadKey) { - trigger(target, "add", key, value); - } else if (hasChanged(value, oldValue)) { - trigger(target, "set", key, value, oldValue); - } - } - return result; - }; -} -function deleteProperty(target, key) { - const hadKey = hasOwn(target, key); - const oldValue = target[key]; - const result = Reflect.deleteProperty(target, key); - if (result && hadKey) { - trigger(target, "delete", key, void 0, oldValue); - } - return result; -} -function has$1(target, key) { - const result = Reflect.has(target, key); - if (!isSymbol(key) || !builtInSymbols.has(key)) { - track(target, "has", key); - } - return result; -} -function ownKeys(target) { - track(target, "iterate", isArray(target) ? "length" : ITERATE_KEY); - return Reflect.ownKeys(target); -} -var mutableHandlers = { - get: get$1, - set: set$1, - deleteProperty, - has: has$1, - ownKeys -}; -var readonlyHandlers = { - get: readonlyGet, - set(target, key) { - if (true) { - warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target); - } - return true; - }, - deleteProperty(target, key) { - if (true) { - warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target); - } - return true; - } -}; -var shallowReactiveHandlers = extend({}, mutableHandlers, { - get: shallowGet, - set: shallowSet -}); -var shallowReadonlyHandlers = extend({}, readonlyHandlers, { - get: shallowReadonlyGet -}); -var toShallow = (value) => value; -var getProto = (v) => Reflect.getPrototypeOf(v); -function get(target, key, isReadonly2 = false, isShallow3 = false) { - target = target[ - "__v_raw" - /* ReactiveFlags.RAW */ - ]; - const rawTarget = toRaw(target); - const rawKey = toRaw(key); - if (!isReadonly2) { - if (key !== rawKey) { - track(rawTarget, "get", key); - } - track(rawTarget, "get", rawKey); - } - const { has: has2 } = getProto(rawTarget); - const wrap = isShallow3 ? toShallow : isReadonly2 ? toReadonly : toReactive; - if (has2.call(rawTarget, key)) { - return wrap(target.get(key)); - } else if (has2.call(rawTarget, rawKey)) { - return wrap(target.get(rawKey)); - } else if (target !== rawTarget) { - target.get(key); - } -} -function has(key, isReadonly2 = false) { - const target = this[ - "__v_raw" - /* ReactiveFlags.RAW */ - ]; - const rawTarget = toRaw(target); - const rawKey = toRaw(key); - if (!isReadonly2) { - if (key !== rawKey) { - track(rawTarget, "has", key); - } - track(rawTarget, "has", rawKey); - } - return key === rawKey ? target.has(key) : target.has(key) || target.has(rawKey); -} -function size(target, isReadonly2 = false) { - target = target[ - "__v_raw" - /* ReactiveFlags.RAW */ - ]; - !isReadonly2 && track(toRaw(target), "iterate", ITERATE_KEY); - return Reflect.get(target, "size", target); -} -function add(value) { - value = toRaw(value); - const target = toRaw(this); - const proto = getProto(target); - const hadKey = proto.has.call(target, value); - if (!hadKey) { - target.add(value); - trigger(target, "add", value, value); - } - return this; -} -function set(key, value) { - value = toRaw(value); - const target = toRaw(this); - const { has: has2, get: get2 } = getProto(target); - let hadKey = has2.call(target, key); - if (!hadKey) { - key = toRaw(key); - hadKey = has2.call(target, key); - } else if (true) { - checkIdentityKeys(target, has2, key); - } - const oldValue = get2.call(target, key); - target.set(key, value); - if (!hadKey) { - trigger(target, "add", key, value); - } else if (hasChanged(value, oldValue)) { - trigger(target, "set", key, value, oldValue); - } - return this; -} -function deleteEntry(key) { - const target = toRaw(this); - const { has: has2, get: get2 } = getProto(target); - let hadKey = has2.call(target, key); - if (!hadKey) { - key = toRaw(key); - hadKey = has2.call(target, key); - } else if (true) { - checkIdentityKeys(target, has2, key); - } - const oldValue = get2 ? get2.call(target, key) : void 0; - const result = target.delete(key); - if (hadKey) { - trigger(target, "delete", key, void 0, oldValue); - } - return result; -} -function clear() { - const target = toRaw(this); - const hadItems = target.size !== 0; - const oldTarget = true ? isMap(target) ? new Map(target) : new Set(target) : void 0; - const result = target.clear(); - if (hadItems) { - trigger(target, "clear", void 0, void 0, oldTarget); - } - return result; -} -function createForEach(isReadonly2, isShallow3) { - return function forEach(callback, thisArg) { - const observed = this; - const target = observed[ - "__v_raw" - /* ReactiveFlags.RAW */ - ]; - const rawTarget = toRaw(target); - const wrap = isShallow3 ? toShallow : isReadonly2 ? toReadonly : toReactive; - !isReadonly2 && track(rawTarget, "iterate", ITERATE_KEY); - return target.forEach((value, key) => { - return callback.call(thisArg, wrap(value), wrap(key), observed); - }); - }; -} -function createIterableMethod(method, isReadonly2, isShallow3) { - return function(...args) { - const target = this[ - "__v_raw" - /* ReactiveFlags.RAW */ - ]; - const rawTarget = toRaw(target); - const targetIsMap = isMap(rawTarget); - const isPair = method === "entries" || method === Symbol.iterator && targetIsMap; - const isKeyOnly = method === "keys" && targetIsMap; - const innerIterator = target[method](...args); - const wrap = isShallow3 ? toShallow : isReadonly2 ? toReadonly : toReactive; - !isReadonly2 && track(rawTarget, "iterate", isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY); - return { - // iterator protocol - next() { - const { value, done } = innerIterator.next(); - return done ? { value, done } : { - value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), - done - }; - }, - // iterable protocol - [Symbol.iterator]() { - return this; - } - }; - }; -} -function createReadonlyMethod(type) { - return function(...args) { - if (true) { - const key = args[0] ? `on key "${args[0]}" ` : ``; - console.warn(`${capitalize(type)} operation ${key}failed: target is readonly.`, toRaw(this)); - } - return type === "delete" ? false : this; - }; -} -function createInstrumentations() { - const mutableInstrumentations2 = { - get(key) { - return get(this, key); - }, - get size() { - return size(this); - }, - has, - add, - set, - delete: deleteEntry, - clear, - forEach: createForEach(false, false) - }; - const shallowInstrumentations2 = { - get(key) { - return get(this, key, false, true); - }, - get size() { - return size(this); - }, - has, - add, - set, - delete: deleteEntry, - clear, - forEach: createForEach(false, true) - }; - const readonlyInstrumentations2 = { - get(key) { - return get(this, key, true); - }, - get size() { - return size(this, true); - }, - has(key) { - return has.call(this, key, true); - }, - add: createReadonlyMethod( - "add" - /* TriggerOpTypes.ADD */ - ), - set: createReadonlyMethod( - "set" - /* TriggerOpTypes.SET */ - ), - delete: createReadonlyMethod( - "delete" - /* TriggerOpTypes.DELETE */ - ), - clear: createReadonlyMethod( - "clear" - /* TriggerOpTypes.CLEAR */ - ), - forEach: createForEach(true, false) - }; - const shallowReadonlyInstrumentations2 = { - get(key) { - return get(this, key, true, true); - }, - get size() { - return size(this, true); - }, - has(key) { - return has.call(this, key, true); - }, - add: createReadonlyMethod( - "add" - /* TriggerOpTypes.ADD */ - ), - set: createReadonlyMethod( - "set" - /* TriggerOpTypes.SET */ - ), - delete: createReadonlyMethod( - "delete" - /* TriggerOpTypes.DELETE */ - ), - clear: createReadonlyMethod( - "clear" - /* TriggerOpTypes.CLEAR */ - ), - forEach: createForEach(true, true) - }; - const iteratorMethods = ["keys", "values", "entries", Symbol.iterator]; - iteratorMethods.forEach((method) => { - mutableInstrumentations2[method] = createIterableMethod(method, false, false); - readonlyInstrumentations2[method] = createIterableMethod(method, true, false); - shallowInstrumentations2[method] = createIterableMethod(method, false, true); - shallowReadonlyInstrumentations2[method] = createIterableMethod(method, true, true); - }); - return [ - mutableInstrumentations2, - readonlyInstrumentations2, - shallowInstrumentations2, - shallowReadonlyInstrumentations2 - ]; -} -var [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations, shallowReadonlyInstrumentations] = createInstrumentations(); -function createInstrumentationGetter(isReadonly2, shallow) { - const instrumentations = shallow ? isReadonly2 ? shallowReadonlyInstrumentations : shallowInstrumentations : isReadonly2 ? readonlyInstrumentations : mutableInstrumentations; - return (target, key, receiver) => { - if (key === "__v_isReactive") { - return !isReadonly2; - } else if (key === "__v_isReadonly") { - return isReadonly2; - } else if (key === "__v_raw") { - return target; - } - return Reflect.get(hasOwn(instrumentations, key) && key in target ? instrumentations : target, key, receiver); - }; -} -var mutableCollectionHandlers = { - get: createInstrumentationGetter(false, false) -}; -var shallowCollectionHandlers = { - get: createInstrumentationGetter(false, true) -}; -var readonlyCollectionHandlers = { - get: createInstrumentationGetter(true, false) -}; -var shallowReadonlyCollectionHandlers = { - get: createInstrumentationGetter(true, true) -}; -function checkIdentityKeys(target, has2, key) { - const rawKey = toRaw(key); - if (rawKey !== key && has2.call(target, rawKey)) { - const type = toRawType(target); - console.warn(`Reactive ${type} contains both the raw and reactive versions of the same object${type === `Map` ? ` as keys` : ``}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`); - } -} -var reactiveMap = /* @__PURE__ */ new WeakMap(); -var shallowReactiveMap = /* @__PURE__ */ new WeakMap(); -var readonlyMap = /* @__PURE__ */ new WeakMap(); -var shallowReadonlyMap = /* @__PURE__ */ new WeakMap(); -function targetTypeMap(rawType) { - switch (rawType) { - case "Object": - case "Array": - return 1; - case "Map": - case "Set": - case "WeakMap": - case "WeakSet": - return 2; - default: - return 0; - } -} -function getTargetType(value) { - return value[ - "__v_skip" - /* ReactiveFlags.SKIP */ - ] || !Object.isExtensible(value) ? 0 : targetTypeMap(toRawType(value)); -} -function reactive(target) { - if (isReadonly(target)) { - return target; - } - return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap); -} -function shallowReactive(target) { - return createReactiveObject(target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap); -} -function readonly(target) { - return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap); -} -function shallowReadonly(target) { - return createReactiveObject(target, true, shallowReadonlyHandlers, shallowReadonlyCollectionHandlers, shallowReadonlyMap); -} -function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) { - if (!isObject(target)) { - if (true) { - console.warn(`value cannot be made reactive: ${String(target)}`); - } - return target; - } - if (target[ - "__v_raw" - /* ReactiveFlags.RAW */ - ] && !(isReadonly2 && target[ - "__v_isReactive" - /* ReactiveFlags.IS_REACTIVE */ - ])) { - return target; - } - const existingProxy = proxyMap.get(target); - if (existingProxy) { - return existingProxy; - } - const targetType = getTargetType(target); - if (targetType === 0) { - return target; - } - const proxy = new Proxy(target, targetType === 2 ? collectionHandlers : baseHandlers); - proxyMap.set(target, proxy); - return proxy; -} -function isReactive(value) { - if (isReadonly(value)) { - return isReactive(value[ - "__v_raw" - /* ReactiveFlags.RAW */ - ]); - } - return !!(value && value[ - "__v_isReactive" - /* ReactiveFlags.IS_REACTIVE */ - ]); -} -function isReadonly(value) { - return !!(value && value[ - "__v_isReadonly" - /* ReactiveFlags.IS_READONLY */ - ]); -} -function isShallow(value) { - return !!(value && value[ - "__v_isShallow" - /* ReactiveFlags.IS_SHALLOW */ - ]); -} -function isProxy(value) { - return isReactive(value) || isReadonly(value); -} -function toRaw(observed) { - const raw = observed && observed[ - "__v_raw" - /* ReactiveFlags.RAW */ - ]; - return raw ? toRaw(raw) : observed; -} -function markRaw(value) { - def(value, "__v_skip", true); - return value; -} -var toReactive = (value) => isObject(value) ? reactive(value) : value; -var toReadonly = (value) => isObject(value) ? readonly(value) : value; -function trackRefValue(ref2) { - if (shouldTrack && activeEffect) { - ref2 = toRaw(ref2); - if (true) { - trackEffects(ref2.dep || (ref2.dep = createDep()), { - target: ref2, - type: "get", - key: "value" - }); - } else { - trackEffects(ref2.dep || (ref2.dep = createDep())); - } - } -} -function triggerRefValue(ref2, newVal) { - ref2 = toRaw(ref2); - const dep = ref2.dep; - if (dep) { - if (true) { - triggerEffects(dep, { - target: ref2, - type: "set", - key: "value", - newValue: newVal - }); - } else { - triggerEffects(dep); - } - } -} -function isRef(r) { - return !!(r && r.__v_isRef === true); -} -function ref(value) { - return createRef(value, false); -} -function shallowRef(value) { - return createRef(value, true); -} -function createRef(rawValue, shallow) { - if (isRef(rawValue)) { - return rawValue; - } - return new RefImpl(rawValue, shallow); -} -var RefImpl = class { - constructor(value, __v_isShallow) { - this.__v_isShallow = __v_isShallow; - this.dep = void 0; - this.__v_isRef = true; - this._rawValue = __v_isShallow ? value : toRaw(value); - this._value = __v_isShallow ? value : toReactive(value); - } - get value() { - trackRefValue(this); - return this._value; - } - set value(newVal) { - const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal); - newVal = useDirectValue ? newVal : toRaw(newVal); - if (hasChanged(newVal, this._rawValue)) { - this._rawValue = newVal; - this._value = useDirectValue ? newVal : toReactive(newVal); - triggerRefValue(this, newVal); - } - } -}; -function triggerRef(ref2) { - triggerRefValue(ref2, true ? ref2.value : void 0); -} -function unref(ref2) { - return isRef(ref2) ? ref2.value : ref2; -} -var shallowUnwrapHandlers = { - get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)), - set: (target, key, value, receiver) => { - const oldValue = target[key]; - if (isRef(oldValue) && !isRef(value)) { - oldValue.value = value; - return true; - } else { - return Reflect.set(target, key, value, receiver); - } - } -}; -function proxyRefs(objectWithRefs) { - return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers); -} -var CustomRefImpl = class { - constructor(factory) { - this.dep = void 0; - this.__v_isRef = true; - const { get: get2, set: set2 } = factory(() => trackRefValue(this), () => triggerRefValue(this)); - this._get = get2; - this._set = set2; - } - get value() { - return this._get(); - } - set value(newVal) { - this._set(newVal); - } -}; -function customRef(factory) { - return new CustomRefImpl(factory); -} -function toRefs(object) { - if (!isProxy(object)) { - console.warn(`toRefs() expects a reactive object but received a plain one.`); - } - const ret = isArray(object) ? new Array(object.length) : {}; - for (const key in object) { - ret[key] = toRef(object, key); - } - return ret; -} -var ObjectRefImpl = class { - constructor(_object, _key, _defaultValue) { - this._object = _object; - this._key = _key; - this._defaultValue = _defaultValue; - this.__v_isRef = true; - } - get value() { - const val = this._object[this._key]; - return val === void 0 ? this._defaultValue : val; - } - set value(newVal) { - this._object[this._key] = newVal; - } - get dep() { - return getDepFromReactive(toRaw(this._object), this._key); - } -}; -function toRef(object, key, defaultValue) { - const val = object[key]; - return isRef(val) ? val : new ObjectRefImpl(object, key, defaultValue); -} -var _a$1; -var ComputedRefImpl = class { - constructor(getter, _setter, isReadonly2, isSSR) { - this._setter = _setter; - this.dep = void 0; - this.__v_isRef = true; - this[_a$1] = false; - this._dirty = true; - this.effect = new ReactiveEffect(getter, () => { - if (!this._dirty) { - this._dirty = true; - triggerRefValue(this); - } - }); - this.effect.computed = this; - this.effect.active = this._cacheable = !isSSR; - this[ - "__v_isReadonly" - /* ReactiveFlags.IS_READONLY */ - ] = isReadonly2; - } - get value() { - const self2 = toRaw(this); - trackRefValue(self2); - if (self2._dirty || !self2._cacheable) { - self2._dirty = false; - self2._value = self2.effect.run(); - } - return self2._value; - } - set value(newValue) { - this._setter(newValue); - } -}; -_a$1 = "__v_isReadonly"; -function computed(getterOrOptions, debugOptions, isSSR = false) { - let getter; - let setter; - const onlyGetter = isFunction(getterOrOptions); - if (onlyGetter) { - getter = getterOrOptions; - setter = true ? () => { - console.warn("Write operation failed: computed value is readonly"); - } : NOOP; - } else { - getter = getterOrOptions.get; - setter = getterOrOptions.set; - } - const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR); - if (debugOptions && !isSSR) { - cRef.effect.onTrack = debugOptions.onTrack; - cRef.effect.onTrigger = debugOptions.onTrigger; - } - return cRef; -} -var _a; -var tick = Promise.resolve(); -_a = "__v_isReadonly"; - -// ../../Users/35154/AppData/Local/pnpm/global/5/.pnpm/@vue+runtime-core@3.2.47/node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js -var stack = []; -function pushWarningContext(vnode) { - stack.push(vnode); -} -function popWarningContext() { - stack.pop(); -} -function warn2(msg, ...args) { - if (false) - return; - pauseTracking(); - const instance = stack.length ? stack[stack.length - 1].component : null; - const appWarnHandler = instance && instance.appContext.config.warnHandler; - const trace = getComponentTrace(); - if (appWarnHandler) { - callWithErrorHandling(appWarnHandler, instance, 11, [ - msg + args.join(""), - instance && instance.proxy, - trace.map(({ vnode }) => `at <${formatComponentName(instance, vnode.type)}>`).join("\n"), - trace - ]); - } else { - const warnArgs = [`[Vue warn]: ${msg}`, ...args]; - if (trace.length && // avoid spamming console during tests - true) { - warnArgs.push(` -`, ...formatTrace(trace)); - } - console.warn(...warnArgs); - } - resetTracking(); -} -function getComponentTrace() { - let currentVNode = stack[stack.length - 1]; - if (!currentVNode) { - return []; - } - const normalizedStack = []; - while (currentVNode) { - const last = normalizedStack[0]; - if (last && last.vnode === currentVNode) { - last.recurseCount++; - } else { - normalizedStack.push({ - vnode: currentVNode, - recurseCount: 0 - }); - } - const parentInstance = currentVNode.component && currentVNode.component.parent; - currentVNode = parentInstance && parentInstance.vnode; - } - return normalizedStack; -} -function formatTrace(trace) { - const logs = []; - trace.forEach((entry, i) => { - logs.push(...i === 0 ? [] : [` -`], ...formatTraceEntry(entry)); - }); - return logs; -} -function formatTraceEntry({ vnode, recurseCount }) { - const postfix = recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``; - const isRoot = vnode.component ? vnode.component.parent == null : false; - const open = ` at <${formatComponentName(vnode.component, vnode.type, isRoot)}`; - const close = `>` + postfix; - return vnode.props ? [open, ...formatProps(vnode.props), close] : [open + close]; -} -function formatProps(props) { - const res = []; - const keys = Object.keys(props); - keys.slice(0, 3).forEach((key) => { - res.push(...formatProp(key, props[key])); - }); - if (keys.length > 3) { - res.push(` ...`); - } - return res; -} -function formatProp(key, value, raw) { - if (isString(value)) { - value = JSON.stringify(value); - return raw ? value : [`${key}=${value}`]; - } else if (typeof value === "number" || typeof value === "boolean" || value == null) { - return raw ? value : [`${key}=${value}`]; - } else if (isRef(value)) { - value = formatProp(key, toRaw(value.value), true); - return raw ? value : [`${key}=Ref<`, value, `>`]; - } else if (isFunction(value)) { - return [`${key}=fn${value.name ? `<${value.name}>` : ``}`]; - } else { - value = toRaw(value); - return raw ? value : [`${key}=`, value]; - } -} -function assertNumber(val, type) { - if (false) - return; - if (val === void 0) { - return; - } else if (typeof val !== "number") { - warn2(`${type} is not a valid number - got ${JSON.stringify(val)}.`); - } else if (isNaN(val)) { - warn2(`${type} is NaN - the duration expression might be incorrect.`); - } -} -var ErrorTypeStrings = { - [ - "sp" - /* LifecycleHooks.SERVER_PREFETCH */ - ]: "serverPrefetch hook", - [ - "bc" - /* LifecycleHooks.BEFORE_CREATE */ - ]: "beforeCreate hook", - [ - "c" - /* LifecycleHooks.CREATED */ - ]: "created hook", - [ - "bm" - /* LifecycleHooks.BEFORE_MOUNT */ - ]: "beforeMount hook", - [ - "m" - /* LifecycleHooks.MOUNTED */ - ]: "mounted hook", - [ - "bu" - /* LifecycleHooks.BEFORE_UPDATE */ - ]: "beforeUpdate hook", - [ - "u" - /* LifecycleHooks.UPDATED */ - ]: "updated", - [ - "bum" - /* LifecycleHooks.BEFORE_UNMOUNT */ - ]: "beforeUnmount hook", - [ - "um" - /* LifecycleHooks.UNMOUNTED */ - ]: "unmounted hook", - [ - "a" - /* LifecycleHooks.ACTIVATED */ - ]: "activated hook", - [ - "da" - /* LifecycleHooks.DEACTIVATED */ - ]: "deactivated hook", - [ - "ec" - /* LifecycleHooks.ERROR_CAPTURED */ - ]: "errorCaptured hook", - [ - "rtc" - /* LifecycleHooks.RENDER_TRACKED */ - ]: "renderTracked hook", - [ - "rtg" - /* LifecycleHooks.RENDER_TRIGGERED */ - ]: "renderTriggered hook", - [ - 0 - /* ErrorCodes.SETUP_FUNCTION */ - ]: "setup function", - [ - 1 - /* ErrorCodes.RENDER_FUNCTION */ - ]: "render function", - [ - 2 - /* ErrorCodes.WATCH_GETTER */ - ]: "watcher getter", - [ - 3 - /* ErrorCodes.WATCH_CALLBACK */ - ]: "watcher callback", - [ - 4 - /* ErrorCodes.WATCH_CLEANUP */ - ]: "watcher cleanup function", - [ - 5 - /* ErrorCodes.NATIVE_EVENT_HANDLER */ - ]: "native event handler", - [ - 6 - /* ErrorCodes.COMPONENT_EVENT_HANDLER */ - ]: "component event handler", - [ - 7 - /* ErrorCodes.VNODE_HOOK */ - ]: "vnode hook", - [ - 8 - /* ErrorCodes.DIRECTIVE_HOOK */ - ]: "directive hook", - [ - 9 - /* ErrorCodes.TRANSITION_HOOK */ - ]: "transition hook", - [ - 10 - /* ErrorCodes.APP_ERROR_HANDLER */ - ]: "app errorHandler", - [ - 11 - /* ErrorCodes.APP_WARN_HANDLER */ - ]: "app warnHandler", - [ - 12 - /* ErrorCodes.FUNCTION_REF */ - ]: "ref function", - [ - 13 - /* ErrorCodes.ASYNC_COMPONENT_LOADER */ - ]: "async component loader", - [ - 14 - /* ErrorCodes.SCHEDULER */ - ]: "scheduler flush. This is likely a Vue internals bug. Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core" -}; -function callWithErrorHandling(fn, instance, type, args) { - let res; - try { - res = args ? fn(...args) : fn(); - } catch (err) { - handleError(err, instance, type); - } - return res; -} -function callWithAsyncErrorHandling(fn, instance, type, args) { - if (isFunction(fn)) { - const res = callWithErrorHandling(fn, instance, type, args); - if (res && isPromise(res)) { - res.catch((err) => { - handleError(err, instance, type); - }); - } - return res; - } - const values = []; - for (let i = 0; i < fn.length; i++) { - values.push(callWithAsyncErrorHandling(fn[i], instance, type, args)); - } - return values; -} -function handleError(err, instance, type, throwInDev = true) { - const contextVNode = instance ? instance.vnode : null; - if (instance) { - let cur = instance.parent; - const exposedInstance = instance.proxy; - const errorInfo = true ? ErrorTypeStrings[type] : type; - while (cur) { - const errorCapturedHooks = cur.ec; - if (errorCapturedHooks) { - for (let i = 0; i < errorCapturedHooks.length; i++) { - if (errorCapturedHooks[i](err, exposedInstance, errorInfo) === false) { - return; - } - } - } - cur = cur.parent; - } - const appErrorHandler = instance.appContext.config.errorHandler; - if (appErrorHandler) { - callWithErrorHandling(appErrorHandler, null, 10, [err, exposedInstance, errorInfo]); - return; - } - } - logError(err, type, contextVNode, throwInDev); -} -function logError(err, type, contextVNode, throwInDev = true) { - if (true) { - const info = ErrorTypeStrings[type]; - if (contextVNode) { - pushWarningContext(contextVNode); - } - warn2(`Unhandled error${info ? ` during execution of ${info}` : ``}`); - if (contextVNode) { - popWarningContext(); - } - if (throwInDev) { - throw err; - } else { - console.error(err); - } - } else { - console.error(err); - } -} -var isFlushing = false; -var isFlushPending = false; -var queue = []; -var flushIndex = 0; -var pendingPostFlushCbs = []; -var activePostFlushCbs = null; -var postFlushIndex = 0; -var resolvedPromise = Promise.resolve(); -var currentFlushPromise = null; -var RECURSION_LIMIT = 100; -function nextTick(fn) { - const p2 = currentFlushPromise || resolvedPromise; - return fn ? p2.then(this ? fn.bind(this) : fn) : p2; -} -function findInsertionIndex(id) { - let start = flushIndex + 1; - let end = queue.length; - while (start < end) { - const middle = start + end >>> 1; - const middleJobId = getId(queue[middle]); - middleJobId < id ? start = middle + 1 : end = middle; - } - return start; -} -function queueJob(job) { - if (!queue.length || !queue.includes(job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex)) { - if (job.id == null) { - queue.push(job); - } else { - queue.splice(findInsertionIndex(job.id), 0, job); - } - queueFlush(); - } -} -function queueFlush() { - if (!isFlushing && !isFlushPending) { - isFlushPending = true; - currentFlushPromise = resolvedPromise.then(flushJobs); - } -} -function invalidateJob(job) { - const i = queue.indexOf(job); - if (i > flushIndex) { - queue.splice(i, 1); - } -} -function queuePostFlushCb(cb) { - if (!isArray(cb)) { - if (!activePostFlushCbs || !activePostFlushCbs.includes(cb, cb.allowRecurse ? postFlushIndex + 1 : postFlushIndex)) { - pendingPostFlushCbs.push(cb); - } - } else { - pendingPostFlushCbs.push(...cb); - } - queueFlush(); -} -function flushPreFlushCbs(seen, i = isFlushing ? flushIndex + 1 : 0) { - if (true) { - seen = seen || /* @__PURE__ */ new Map(); - } - for (; i < queue.length; i++) { - const cb = queue[i]; - if (cb && cb.pre) { - if (checkRecursiveUpdates(seen, cb)) { - continue; - } - queue.splice(i, 1); - i--; - cb(); - } - } -} -function flushPostFlushCbs(seen) { - if (pendingPostFlushCbs.length) { - const deduped = [...new Set(pendingPostFlushCbs)]; - pendingPostFlushCbs.length = 0; - if (activePostFlushCbs) { - activePostFlushCbs.push(...deduped); - return; - } - activePostFlushCbs = deduped; - if (true) { - seen = seen || /* @__PURE__ */ new Map(); - } - activePostFlushCbs.sort((a, b) => getId(a) - getId(b)); - for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) { - if (checkRecursiveUpdates(seen, activePostFlushCbs[postFlushIndex])) { - continue; - } - activePostFlushCbs[postFlushIndex](); - } - activePostFlushCbs = null; - postFlushIndex = 0; - } -} -var getId = (job) => job.id == null ? Infinity : job.id; -var comparator = (a, b) => { - const diff = getId(a) - getId(b); - if (diff === 0) { - if (a.pre && !b.pre) - return -1; - if (b.pre && !a.pre) - return 1; - } - return diff; -}; -function flushJobs(seen) { - isFlushPending = false; - isFlushing = true; - if (true) { - seen = seen || /* @__PURE__ */ new Map(); - } - queue.sort(comparator); - const check = true ? (job) => checkRecursiveUpdates(seen, job) : NOOP; - try { - for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { - const job = queue[flushIndex]; - if (job && job.active !== false) { - if (check(job)) { - continue; - } - callWithErrorHandling( - job, - null, - 14 - /* ErrorCodes.SCHEDULER */ - ); - } - } - } finally { - flushIndex = 0; - queue.length = 0; - flushPostFlushCbs(seen); - isFlushing = false; - currentFlushPromise = null; - if (queue.length || pendingPostFlushCbs.length) { - flushJobs(seen); - } - } -} -function checkRecursiveUpdates(seen, fn) { - if (!seen.has(fn)) { - seen.set(fn, 1); - } else { - const count = seen.get(fn); - if (count > RECURSION_LIMIT) { - const instance = fn.ownerInstance; - const componentName = instance && getComponentName(instance.type); - warn2(`Maximum recursive updates exceeded${componentName ? ` in component <${componentName}>` : ``}. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.`); - return true; - } else { - seen.set(fn, count + 1); - } - } -} -var isHmrUpdating = false; -var hmrDirtyComponents = /* @__PURE__ */ new Set(); -if (true) { - getGlobalThis().__VUE_HMR_RUNTIME__ = { - createRecord: tryWrap(createRecord), - rerender: tryWrap(rerender), - reload: tryWrap(reload) - }; -} -var map = /* @__PURE__ */ new Map(); -function registerHMR(instance) { - const id = instance.type.__hmrId; - let record = map.get(id); - if (!record) { - createRecord(id, instance.type); - record = map.get(id); - } - record.instances.add(instance); -} -function unregisterHMR(instance) { - map.get(instance.type.__hmrId).instances.delete(instance); -} -function createRecord(id, initialDef) { - if (map.has(id)) { - return false; - } - map.set(id, { - initialDef: normalizeClassComponent(initialDef), - instances: /* @__PURE__ */ new Set() - }); - return true; -} -function normalizeClassComponent(component) { - return isClassComponent(component) ? component.__vccOpts : component; -} -function rerender(id, newRender) { - const record = map.get(id); - if (!record) { - return; - } - record.initialDef.render = newRender; - [...record.instances].forEach((instance) => { - if (newRender) { - instance.render = newRender; - normalizeClassComponent(instance.type).render = newRender; - } - instance.renderCache = []; - isHmrUpdating = true; - instance.update(); - isHmrUpdating = false; - }); -} -function reload(id, newComp) { - const record = map.get(id); - if (!record) - return; - newComp = normalizeClassComponent(newComp); - updateComponentDef(record.initialDef, newComp); - const instances = [...record.instances]; - for (const instance of instances) { - const oldComp = normalizeClassComponent(instance.type); - if (!hmrDirtyComponents.has(oldComp)) { - if (oldComp !== record.initialDef) { - updateComponentDef(oldComp, newComp); - } - hmrDirtyComponents.add(oldComp); - } - instance.appContext.optionsCache.delete(instance.type); - if (instance.ceReload) { - hmrDirtyComponents.add(oldComp); - instance.ceReload(newComp.styles); - hmrDirtyComponents.delete(oldComp); - } else if (instance.parent) { - queueJob(instance.parent.update); - } else if (instance.appContext.reload) { - instance.appContext.reload(); - } else if (typeof window !== "undefined") { - window.location.reload(); - } else { - console.warn("[HMR] Root or manually mounted instance modified. Full reload required."); - } - } - queuePostFlushCb(() => { - for (const instance of instances) { - hmrDirtyComponents.delete(normalizeClassComponent(instance.type)); - } - }); -} -function updateComponentDef(oldComp, newComp) { - extend(oldComp, newComp); - for (const key in oldComp) { - if (key !== "__file" && !(key in newComp)) { - delete oldComp[key]; - } - } -} -function tryWrap(fn) { - return (id, arg) => { - try { - return fn(id, arg); - } catch (e) { - console.error(e); - console.warn(`[HMR] Something went wrong during Vue component hot-reload. Full reload required.`); - } - }; -} -var devtools; -var buffer = []; -var devtoolsNotInstalled = false; -function emit$1(event, ...args) { - if (devtools) { - devtools.emit(event, ...args); - } else if (!devtoolsNotInstalled) { - buffer.push({ event, args }); - } -} -function setDevtoolsHook(hook, target) { - var _a2, _b; - devtools = hook; - if (devtools) { - devtools.enabled = true; - buffer.forEach(({ event, args }) => devtools.emit(event, ...args)); - buffer = []; - } else if ( - // handle late devtools injection - only do this if we are in an actual - // browser environment to avoid the timer handle stalling test runner exit - // (#4815) - typeof window !== "undefined" && // some envs mock window but not fully - window.HTMLElement && // also exclude jsdom - !((_b = (_a2 = window.navigator) === null || _a2 === void 0 ? void 0 : _a2.userAgent) === null || _b === void 0 ? void 0 : _b.includes("jsdom")) - ) { - const replay = target.__VUE_DEVTOOLS_HOOK_REPLAY__ = target.__VUE_DEVTOOLS_HOOK_REPLAY__ || []; - replay.push((newHook) => { - setDevtoolsHook(newHook, target); - }); - setTimeout(() => { - if (!devtools) { - target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null; - devtoolsNotInstalled = true; - buffer = []; - } - }, 3e3); - } else { - devtoolsNotInstalled = true; - buffer = []; - } -} -function devtoolsInitApp(app, version2) { - emit$1("app:init", app, version2, { - Fragment, - Text, - Comment, - Static - }); -} -function devtoolsUnmountApp(app) { - emit$1("app:unmount", app); -} -var devtoolsComponentAdded = createDevtoolsComponentHook( - "component:added" - /* DevtoolsHooks.COMPONENT_ADDED */ -); -var devtoolsComponentUpdated = createDevtoolsComponentHook( - "component:updated" - /* DevtoolsHooks.COMPONENT_UPDATED */ -); -var _devtoolsComponentRemoved = createDevtoolsComponentHook( - "component:removed" - /* DevtoolsHooks.COMPONENT_REMOVED */ -); -var devtoolsComponentRemoved = (component) => { - if (devtools && typeof devtools.cleanupBuffer === "function" && // remove the component if it wasn't buffered - !devtools.cleanupBuffer(component)) { - _devtoolsComponentRemoved(component); - } -}; -function createDevtoolsComponentHook(hook) { - return (component) => { - emit$1(hook, component.appContext.app, component.uid, component.parent ? component.parent.uid : void 0, component); - }; -} -var devtoolsPerfStart = createDevtoolsPerformanceHook( - "perf:start" - /* DevtoolsHooks.PERFORMANCE_START */ -); -var devtoolsPerfEnd = createDevtoolsPerformanceHook( - "perf:end" - /* DevtoolsHooks.PERFORMANCE_END */ -); -function createDevtoolsPerformanceHook(hook) { - return (component, type, time) => { - emit$1(hook, component.appContext.app, component.uid, component, type, time); - }; -} -function devtoolsComponentEmit(component, event, params) { - emit$1("component:emit", component.appContext.app, component, event, params); -} -function emit(instance, event, ...rawArgs) { - if (instance.isUnmounted) - return; - const props = instance.vnode.props || EMPTY_OBJ; - if (true) { - const { emitsOptions, propsOptions: [propsOptions] } = instance; - if (emitsOptions) { - if (!(event in emitsOptions) && true) { - if (!propsOptions || !(toHandlerKey(event) in propsOptions)) { - warn2(`Component emitted event "${event}" but it is neither declared in the emits option nor as an "${toHandlerKey(event)}" prop.`); - } - } else { - const validator = emitsOptions[event]; - if (isFunction(validator)) { - const isValid = validator(...rawArgs); - if (!isValid) { - warn2(`Invalid event arguments: event validation failed for event "${event}".`); - } - } - } - } - } - let args = rawArgs; - const isModelListener2 = event.startsWith("update:"); - const modelArg = isModelListener2 && event.slice(7); - if (modelArg && modelArg in props) { - const modifiersKey = `${modelArg === "modelValue" ? "model" : modelArg}Modifiers`; - const { number, trim } = props[modifiersKey] || EMPTY_OBJ; - if (trim) { - args = rawArgs.map((a) => isString(a) ? a.trim() : a); - } - if (number) { - args = rawArgs.map(looseToNumber); - } - } - if (true) { - devtoolsComponentEmit(instance, event, args); - } - if (true) { - const lowerCaseEvent = event.toLowerCase(); - if (lowerCaseEvent !== event && props[toHandlerKey(lowerCaseEvent)]) { - warn2(`Event "${lowerCaseEvent}" is emitted in component ${formatComponentName(instance, instance.type)} but the handler is registered for "${event}". Note that HTML attributes are case-insensitive and you cannot use v-on to listen to camelCase events when using in-DOM templates. You should probably use "${hyphenate(event)}" instead of "${event}".`); - } - } - let handlerName; - let handler = props[handlerName = toHandlerKey(event)] || // also try camelCase event handler (#2249) - props[handlerName = toHandlerKey(camelize(event))]; - if (!handler && isModelListener2) { - handler = props[handlerName = toHandlerKey(hyphenate(event))]; - } - if (handler) { - callWithAsyncErrorHandling(handler, instance, 6, args); - } - const onceHandler = props[handlerName + `Once`]; - if (onceHandler) { - if (!instance.emitted) { - instance.emitted = {}; - } else if (instance.emitted[handlerName]) { - return; - } - instance.emitted[handlerName] = true; - callWithAsyncErrorHandling(onceHandler, instance, 6, args); - } -} -function normalizeEmitsOptions(comp, appContext, asMixin = false) { - const cache = appContext.emitsCache; - const cached = cache.get(comp); - if (cached !== void 0) { - return cached; - } - const raw = comp.emits; - let normalized = {}; - let hasExtends = false; - if (__VUE_OPTIONS_API__ && !isFunction(comp)) { - const extendEmits = (raw2) => { - const normalizedFromExtend = normalizeEmitsOptions(raw2, appContext, true); - if (normalizedFromExtend) { - hasExtends = true; - extend(normalized, normalizedFromExtend); - } - }; - if (!asMixin && appContext.mixins.length) { - appContext.mixins.forEach(extendEmits); - } - if (comp.extends) { - extendEmits(comp.extends); - } - if (comp.mixins) { - comp.mixins.forEach(extendEmits); - } - } - if (!raw && !hasExtends) { - if (isObject(comp)) { - cache.set(comp, null); - } - return null; - } - if (isArray(raw)) { - raw.forEach((key) => normalized[key] = null); - } else { - extend(normalized, raw); - } - if (isObject(comp)) { - cache.set(comp, normalized); - } - return normalized; -} -function isEmitListener(options, key) { - if (!options || !isOn(key)) { - return false; - } - key = key.slice(2).replace(/Once$/, ""); - return hasOwn(options, key[0].toLowerCase() + key.slice(1)) || hasOwn(options, hyphenate(key)) || hasOwn(options, key); -} -var currentRenderingInstance = null; -var currentScopeId = null; -function setCurrentRenderingInstance(instance) { - const prev = currentRenderingInstance; - currentRenderingInstance = instance; - currentScopeId = instance && instance.type.__scopeId || null; - return prev; -} -function pushScopeId(id) { - currentScopeId = id; -} -function popScopeId() { - currentScopeId = null; -} -var withScopeId = (_id) => withCtx; -function withCtx(fn, ctx = currentRenderingInstance, isNonScopedSlot) { - if (!ctx) - return fn; - if (fn._n) { - return fn; - } - const renderFnWithContext = (...args) => { - if (renderFnWithContext._d) { - setBlockTracking(-1); - } - const prevInstance = setCurrentRenderingInstance(ctx); - let res; - try { - res = fn(...args); - } finally { - setCurrentRenderingInstance(prevInstance); - if (renderFnWithContext._d) { - setBlockTracking(1); - } - } - if (true) { - devtoolsComponentUpdated(ctx); - } - return res; - }; - renderFnWithContext._n = true; - renderFnWithContext._c = true; - renderFnWithContext._d = true; - return renderFnWithContext; -} -var accessedAttrs = false; -function markAttrsAccessed() { - accessedAttrs = true; -} -function renderComponentRoot(instance) { - const { type: Component, vnode, proxy, withProxy, props, propsOptions: [propsOptions], slots, attrs, emit: emit2, render: render2, renderCache, data, setupState, ctx, inheritAttrs } = instance; - let result; - let fallthroughAttrs; - const prev = setCurrentRenderingInstance(instance); - if (true) { - accessedAttrs = false; - } - try { - if (vnode.shapeFlag & 4) { - const proxyToUse = withProxy || proxy; - result = normalizeVNode(render2.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx)); - fallthroughAttrs = attrs; - } else { - const render3 = Component; - if (attrs === props) { - markAttrsAccessed(); - } - result = normalizeVNode(render3.length > 1 ? render3(props, true ? { - get attrs() { - markAttrsAccessed(); - return attrs; - }, - slots, - emit: emit2 - } : { attrs, slots, emit: emit2 }) : render3( - props, - null - /* we know it doesn't need it */ - )); - fallthroughAttrs = Component.props ? attrs : getFunctionalFallthrough(attrs); - } - } catch (err) { - blockStack.length = 0; - handleError( - err, - instance, - 1 - /* ErrorCodes.RENDER_FUNCTION */ - ); - result = createVNode(Comment); - } - let root = result; - let setRoot = void 0; - if (result.patchFlag > 0 && result.patchFlag & 2048) { - [root, setRoot] = getChildRoot(result); - } - if (fallthroughAttrs && inheritAttrs !== false) { - const keys = Object.keys(fallthroughAttrs); - const { shapeFlag } = root; - if (keys.length) { - if (shapeFlag & (1 | 6)) { - if (propsOptions && keys.some(isModelListener)) { - fallthroughAttrs = filterModelListeners(fallthroughAttrs, propsOptions); - } - root = cloneVNode(root, fallthroughAttrs); - } else if (!accessedAttrs && root.type !== Comment) { - const allAttrs = Object.keys(attrs); - const eventAttrs = []; - const extraAttrs = []; - for (let i = 0, l = allAttrs.length; i < l; i++) { - const key = allAttrs[i]; - if (isOn(key)) { - if (!isModelListener(key)) { - eventAttrs.push(key[2].toLowerCase() + key.slice(3)); - } - } else { - extraAttrs.push(key); - } - } - if (extraAttrs.length) { - warn2(`Extraneous non-props attributes (${extraAttrs.join(", ")}) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.`); - } - if (eventAttrs.length) { - warn2(`Extraneous non-emits event listeners (${eventAttrs.join(", ")}) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits" option.`); - } - } - } - } - if (vnode.dirs) { - if (!isElementRoot(root)) { - warn2(`Runtime directive used on component with non-element root node. The directives will not function as intended.`); - } - root = cloneVNode(root); - root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs; - } - if (vnode.transition) { - if (!isElementRoot(root)) { - warn2(`Component inside renders non-element root node that cannot be animated.`); - } - root.transition = vnode.transition; - } - if (setRoot) { - setRoot(root); - } else { - result = root; - } - setCurrentRenderingInstance(prev); - return result; -} -var getChildRoot = (vnode) => { - const rawChildren = vnode.children; - const dynamicChildren = vnode.dynamicChildren; - const childRoot = filterSingleRoot(rawChildren); - if (!childRoot) { - return [vnode, void 0]; - } - const index = rawChildren.indexOf(childRoot); - const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1; - const setRoot = (updatedRoot) => { - rawChildren[index] = updatedRoot; - if (dynamicChildren) { - if (dynamicIndex > -1) { - dynamicChildren[dynamicIndex] = updatedRoot; - } else if (updatedRoot.patchFlag > 0) { - vnode.dynamicChildren = [...dynamicChildren, updatedRoot]; - } - } - }; - return [normalizeVNode(childRoot), setRoot]; -}; -function filterSingleRoot(children) { - let singleRoot; - for (let i = 0; i < children.length; i++) { - const child = children[i]; - if (isVNode(child)) { - if (child.type !== Comment || child.children === "v-if") { - if (singleRoot) { - return; - } else { - singleRoot = child; - } - } - } else { - return; - } - } - return singleRoot; -} -var getFunctionalFallthrough = (attrs) => { - let res; - for (const key in attrs) { - if (key === "class" || key === "style" || isOn(key)) { - (res || (res = {}))[key] = attrs[key]; - } - } - return res; -}; -var filterModelListeners = (attrs, props) => { - const res = {}; - for (const key in attrs) { - if (!isModelListener(key) || !(key.slice(9) in props)) { - res[key] = attrs[key]; - } - } - return res; -}; -var isElementRoot = (vnode) => { - return vnode.shapeFlag & (6 | 1) || vnode.type === Comment; -}; -function shouldUpdateComponent(prevVNode, nextVNode, optimized) { - const { props: prevProps, children: prevChildren, component } = prevVNode; - const { props: nextProps, children: nextChildren, patchFlag } = nextVNode; - const emits = component.emitsOptions; - if ((prevChildren || nextChildren) && isHmrUpdating) { - return true; - } - if (nextVNode.dirs || nextVNode.transition) { - return true; - } - if (optimized && patchFlag >= 0) { - if (patchFlag & 1024) { - return true; - } - if (patchFlag & 16) { - if (!prevProps) { - return !!nextProps; - } - return hasPropsChanged(prevProps, nextProps, emits); - } else if (patchFlag & 8) { - const dynamicProps = nextVNode.dynamicProps; - for (let i = 0; i < dynamicProps.length; i++) { - const key = dynamicProps[i]; - if (nextProps[key] !== prevProps[key] && !isEmitListener(emits, key)) { - return true; - } - } - } - } else { - if (prevChildren || nextChildren) { - if (!nextChildren || !nextChildren.$stable) { - return true; - } - } - if (prevProps === nextProps) { - return false; - } - if (!prevProps) { - return !!nextProps; - } - if (!nextProps) { - return true; - } - return hasPropsChanged(prevProps, nextProps, emits); - } - return false; -} -function hasPropsChanged(prevProps, nextProps, emitsOptions) { - const nextKeys = Object.keys(nextProps); - if (nextKeys.length !== Object.keys(prevProps).length) { - return true; - } - for (let i = 0; i < nextKeys.length; i++) { - const key = nextKeys[i]; - if (nextProps[key] !== prevProps[key] && !isEmitListener(emitsOptions, key)) { - return true; - } - } - return false; -} -function updateHOCHostEl({ vnode, parent }, el) { - while (parent && parent.subTree === vnode) { - (vnode = parent.vnode).el = el; - parent = parent.parent; - } -} -var isSuspense = (type) => type.__isSuspense; -var SuspenseImpl = { - name: "Suspense", - // In order to make Suspense tree-shakable, we need to avoid importing it - // directly in the renderer. The renderer checks for the __isSuspense flag - // on a vnode's type and calls the `process` method, passing in renderer - // internals. - __isSuspense: true, - process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, rendererInternals) { - if (n1 == null) { - mountSuspense(n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, rendererInternals); - } else { - patchSuspense(n1, n2, container, anchor, parentComponent, isSVG, slotScopeIds, optimized, rendererInternals); - } - }, - hydrate: hydrateSuspense, - create: createSuspenseBoundary, - normalize: normalizeSuspenseChildren -}; -var Suspense = SuspenseImpl; -function triggerEvent(vnode, name) { - const eventListener = vnode.props && vnode.props[name]; - if (isFunction(eventListener)) { - eventListener(); - } -} -function mountSuspense(vnode, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, rendererInternals) { - const { p: patch, o: { createElement } } = rendererInternals; - const hiddenContainer = createElement("div"); - const suspense = vnode.suspense = createSuspenseBoundary(vnode, parentSuspense, parentComponent, container, hiddenContainer, anchor, isSVG, slotScopeIds, optimized, rendererInternals); - patch(null, suspense.pendingBranch = vnode.ssContent, hiddenContainer, null, parentComponent, suspense, isSVG, slotScopeIds); - if (suspense.deps > 0) { - triggerEvent(vnode, "onPending"); - triggerEvent(vnode, "onFallback"); - patch( - null, - vnode.ssFallback, - container, - anchor, - parentComponent, - null, - // fallback tree will not have suspense context - isSVG, - slotScopeIds - ); - setActiveBranch(suspense, vnode.ssFallback); - } else { - suspense.resolve(); - } -} -function patchSuspense(n1, n2, container, anchor, parentComponent, isSVG, slotScopeIds, optimized, { p: patch, um: unmount, o: { createElement } }) { - const suspense = n2.suspense = n1.suspense; - suspense.vnode = n2; - n2.el = n1.el; - const newBranch = n2.ssContent; - const newFallback = n2.ssFallback; - const { activeBranch, pendingBranch, isInFallback, isHydrating } = suspense; - if (pendingBranch) { - suspense.pendingBranch = newBranch; - if (isSameVNodeType(newBranch, pendingBranch)) { - patch(pendingBranch, newBranch, suspense.hiddenContainer, null, parentComponent, suspense, isSVG, slotScopeIds, optimized); - if (suspense.deps <= 0) { - suspense.resolve(); - } else if (isInFallback) { - patch( - activeBranch, - newFallback, - container, - anchor, - parentComponent, - null, - // fallback tree will not have suspense context - isSVG, - slotScopeIds, - optimized - ); - setActiveBranch(suspense, newFallback); - } - } else { - suspense.pendingId++; - if (isHydrating) { - suspense.isHydrating = false; - suspense.activeBranch = pendingBranch; - } else { - unmount(pendingBranch, parentComponent, suspense); - } - suspense.deps = 0; - suspense.effects.length = 0; - suspense.hiddenContainer = createElement("div"); - if (isInFallback) { - patch(null, newBranch, suspense.hiddenContainer, null, parentComponent, suspense, isSVG, slotScopeIds, optimized); - if (suspense.deps <= 0) { - suspense.resolve(); - } else { - patch( - activeBranch, - newFallback, - container, - anchor, - parentComponent, - null, - // fallback tree will not have suspense context - isSVG, - slotScopeIds, - optimized - ); - setActiveBranch(suspense, newFallback); - } - } else if (activeBranch && isSameVNodeType(newBranch, activeBranch)) { - patch(activeBranch, newBranch, container, anchor, parentComponent, suspense, isSVG, slotScopeIds, optimized); - suspense.resolve(true); - } else { - patch(null, newBranch, suspense.hiddenContainer, null, parentComponent, suspense, isSVG, slotScopeIds, optimized); - if (suspense.deps <= 0) { - suspense.resolve(); - } - } - } - } else { - if (activeBranch && isSameVNodeType(newBranch, activeBranch)) { - patch(activeBranch, newBranch, container, anchor, parentComponent, suspense, isSVG, slotScopeIds, optimized); - setActiveBranch(suspense, newBranch); - } else { - triggerEvent(n2, "onPending"); - suspense.pendingBranch = newBranch; - suspense.pendingId++; - patch(null, newBranch, suspense.hiddenContainer, null, parentComponent, suspense, isSVG, slotScopeIds, optimized); - if (suspense.deps <= 0) { - suspense.resolve(); - } else { - const { timeout, pendingId } = suspense; - if (timeout > 0) { - setTimeout(() => { - if (suspense.pendingId === pendingId) { - suspense.fallback(newFallback); - } - }, timeout); - } else if (timeout === 0) { - suspense.fallback(newFallback); - } - } - } - } -} -var hasWarned = false; -function createSuspenseBoundary(vnode, parent, parentComponent, container, hiddenContainer, anchor, isSVG, slotScopeIds, optimized, rendererInternals, isHydrating = false) { - if (!hasWarned) { - hasWarned = true; - console[console.info ? "info" : "log"](` is an experimental feature and its API will likely change.`); - } - const { p: patch, m: move, um: unmount, n: next, o: { parentNode, remove: remove2 } } = rendererInternals; - const timeout = vnode.props ? toNumber(vnode.props.timeout) : void 0; - if (true) { - assertNumber(timeout, `Suspense timeout`); - } - const suspense = { - vnode, - parent, - parentComponent, - isSVG, - container, - hiddenContainer, - anchor, - deps: 0, - pendingId: 0, - timeout: typeof timeout === "number" ? timeout : -1, - activeBranch: null, - pendingBranch: null, - isInFallback: true, - isHydrating, - isUnmounted: false, - effects: [], - resolve(resume = false) { - if (true) { - if (!resume && !suspense.pendingBranch) { - throw new Error(`suspense.resolve() is called without a pending branch.`); - } - if (suspense.isUnmounted) { - throw new Error(`suspense.resolve() is called on an already unmounted suspense boundary.`); - } - } - const { vnode: vnode2, activeBranch, pendingBranch, pendingId, effects, parentComponent: parentComponent2, container: container2 } = suspense; - if (suspense.isHydrating) { - suspense.isHydrating = false; - } else if (!resume) { - const delayEnter = activeBranch && pendingBranch.transition && pendingBranch.transition.mode === "out-in"; - if (delayEnter) { - activeBranch.transition.afterLeave = () => { - if (pendingId === suspense.pendingId) { - move( - pendingBranch, - container2, - anchor2, - 0 - /* MoveType.ENTER */ - ); - } - }; - } - let { anchor: anchor2 } = suspense; - if (activeBranch) { - anchor2 = next(activeBranch); - unmount(activeBranch, parentComponent2, suspense, true); - } - if (!delayEnter) { - move( - pendingBranch, - container2, - anchor2, - 0 - /* MoveType.ENTER */ - ); - } - } - setActiveBranch(suspense, pendingBranch); - suspense.pendingBranch = null; - suspense.isInFallback = false; - let parent2 = suspense.parent; - let hasUnresolvedAncestor = false; - while (parent2) { - if (parent2.pendingBranch) { - parent2.effects.push(...effects); - hasUnresolvedAncestor = true; - break; - } - parent2 = parent2.parent; - } - if (!hasUnresolvedAncestor) { - queuePostFlushCb(effects); - } - suspense.effects = []; - triggerEvent(vnode2, "onResolve"); - }, - fallback(fallbackVNode) { - if (!suspense.pendingBranch) { - return; - } - const { vnode: vnode2, activeBranch, parentComponent: parentComponent2, container: container2, isSVG: isSVG2 } = suspense; - triggerEvent(vnode2, "onFallback"); - const anchor2 = next(activeBranch); - const mountFallback = () => { - if (!suspense.isInFallback) { - return; - } - patch( - null, - fallbackVNode, - container2, - anchor2, - parentComponent2, - null, - // fallback tree will not have suspense context - isSVG2, - slotScopeIds, - optimized - ); - setActiveBranch(suspense, fallbackVNode); - }; - const delayEnter = fallbackVNode.transition && fallbackVNode.transition.mode === "out-in"; - if (delayEnter) { - activeBranch.transition.afterLeave = mountFallback; - } - suspense.isInFallback = true; - unmount( - activeBranch, - parentComponent2, - null, - // no suspense so unmount hooks fire now - true - // shouldRemove - ); - if (!delayEnter) { - mountFallback(); - } - }, - move(container2, anchor2, type) { - suspense.activeBranch && move(suspense.activeBranch, container2, anchor2, type); - suspense.container = container2; - }, - next() { - return suspense.activeBranch && next(suspense.activeBranch); - }, - registerDep(instance, setupRenderEffect) { - const isInPendingSuspense = !!suspense.pendingBranch; - if (isInPendingSuspense) { - suspense.deps++; - } - const hydratedEl = instance.vnode.el; - instance.asyncDep.catch((err) => { - handleError( - err, - instance, - 0 - /* ErrorCodes.SETUP_FUNCTION */ - ); - }).then((asyncSetupResult) => { - if (instance.isUnmounted || suspense.isUnmounted || suspense.pendingId !== instance.suspenseId) { - return; - } - instance.asyncResolved = true; - const { vnode: vnode2 } = instance; - if (true) { - pushWarningContext(vnode2); - } - handleSetupResult(instance, asyncSetupResult, false); - if (hydratedEl) { - vnode2.el = hydratedEl; - } - const placeholder = !hydratedEl && instance.subTree.el; - setupRenderEffect( - instance, - vnode2, - // component may have been moved before resolve. - // if this is not a hydration, instance.subTree will be the comment - // placeholder. - parentNode(hydratedEl || instance.subTree.el), - // anchor will not be used if this is hydration, so only need to - // consider the comment placeholder case. - hydratedEl ? null : next(instance.subTree), - suspense, - isSVG, - optimized - ); - if (placeholder) { - remove2(placeholder); - } - updateHOCHostEl(instance, vnode2.el); - if (true) { - popWarningContext(); - } - if (isInPendingSuspense && --suspense.deps === 0) { - suspense.resolve(); - } - }); - }, - unmount(parentSuspense, doRemove) { - suspense.isUnmounted = true; - if (suspense.activeBranch) { - unmount(suspense.activeBranch, parentComponent, parentSuspense, doRemove); - } - if (suspense.pendingBranch) { - unmount(suspense.pendingBranch, parentComponent, parentSuspense, doRemove); - } - } - }; - return suspense; -} -function hydrateSuspense(node, vnode, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, rendererInternals, hydrateNode) { - const suspense = vnode.suspense = createSuspenseBoundary( - vnode, - parentSuspense, - parentComponent, - node.parentNode, - document.createElement("div"), - null, - isSVG, - slotScopeIds, - optimized, - rendererInternals, - true - /* hydrating */ - ); - const result = hydrateNode(node, suspense.pendingBranch = vnode.ssContent, parentComponent, suspense, slotScopeIds, optimized); - if (suspense.deps === 0) { - suspense.resolve(); - } - return result; -} -function normalizeSuspenseChildren(vnode) { - const { shapeFlag, children } = vnode; - const isSlotChildren = shapeFlag & 32; - vnode.ssContent = normalizeSuspenseSlot(isSlotChildren ? children.default : children); - vnode.ssFallback = isSlotChildren ? normalizeSuspenseSlot(children.fallback) : createVNode(Comment); -} -function normalizeSuspenseSlot(s) { - let block; - if (isFunction(s)) { - const trackBlock = isBlockTreeEnabled && s._c; - if (trackBlock) { - s._d = false; - openBlock(); - } - s = s(); - if (trackBlock) { - s._d = true; - block = currentBlock; - closeBlock(); - } - } - if (isArray(s)) { - const singleChild = filterSingleRoot(s); - if (!singleChild) { - warn2(` slots expect a single root node.`); - } - s = singleChild; - } - s = normalizeVNode(s); - if (block && !s.dynamicChildren) { - s.dynamicChildren = block.filter((c) => c !== s); - } - return s; -} -function queueEffectWithSuspense(fn, suspense) { - if (suspense && suspense.pendingBranch) { - if (isArray(fn)) { - suspense.effects.push(...fn); - } else { - suspense.effects.push(fn); - } - } else { - queuePostFlushCb(fn); - } -} -function setActiveBranch(suspense, branch) { - suspense.activeBranch = branch; - const { vnode, parentComponent } = suspense; - const el = vnode.el = branch.el; - if (parentComponent && parentComponent.subTree === vnode) { - parentComponent.vnode.el = el; - updateHOCHostEl(parentComponent, el); - } -} -function provide(key, value) { - if (!currentInstance) { - if (true) { - warn2(`provide() can only be used inside setup().`); - } - } else { - let provides = currentInstance.provides; - const parentProvides = currentInstance.parent && currentInstance.parent.provides; - if (parentProvides === provides) { - provides = currentInstance.provides = Object.create(parentProvides); - } - provides[key] = value; - } -} -function inject(key, defaultValue, treatDefaultAsFactory = false) { - const instance = currentInstance || currentRenderingInstance; - if (instance) { - const provides = instance.parent == null ? instance.vnode.appContext && instance.vnode.appContext.provides : instance.parent.provides; - if (provides && key in provides) { - return provides[key]; - } else if (arguments.length > 1) { - return treatDefaultAsFactory && isFunction(defaultValue) ? defaultValue.call(instance.proxy) : defaultValue; - } else if (true) { - warn2(`injection "${String(key)}" not found.`); - } - } else if (true) { - warn2(`inject() can only be used inside setup() or functional components.`); - } -} -function watchEffect(effect2, options) { - return doWatch(effect2, null, options); -} -function watchPostEffect(effect2, options) { - return doWatch(effect2, null, true ? Object.assign(Object.assign({}, options), { flush: "post" }) : { flush: "post" }); -} -function watchSyncEffect(effect2, options) { - return doWatch(effect2, null, true ? Object.assign(Object.assign({}, options), { flush: "sync" }) : { flush: "sync" }); -} -var INITIAL_WATCHER_VALUE = {}; -function watch(source, cb, options) { - if (!isFunction(cb)) { - warn2(`\`watch(fn, options?)\` signature has been moved to a separate API. Use \`watchEffect(fn, options?)\` instead. \`watch\` now only supports \`watch(source, cb, options?) signature.`); - } - return doWatch(source, cb, options); -} -function doWatch(source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ) { - if (!cb) { - if (immediate !== void 0) { - warn2(`watch() "immediate" option is only respected when using the watch(source, callback, options?) signature.`); - } - if (deep !== void 0) { - warn2(`watch() "deep" option is only respected when using the watch(source, callback, options?) signature.`); - } - } - const warnInvalidSource = (s) => { - warn2(`Invalid watch source: `, s, `A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.`); - }; - const instance = getCurrentScope() === (currentInstance === null || currentInstance === void 0 ? void 0 : currentInstance.scope) ? currentInstance : null; - let getter; - let forceTrigger = false; - let isMultiSource = false; - if (isRef(source)) { - getter = () => source.value; - forceTrigger = isShallow(source); - } else if (isReactive(source)) { - getter = () => source; - deep = true; - } else if (isArray(source)) { - isMultiSource = true; - forceTrigger = source.some((s) => isReactive(s) || isShallow(s)); - getter = () => source.map((s) => { - if (isRef(s)) { - return s.value; - } else if (isReactive(s)) { - return traverse(s); - } else if (isFunction(s)) { - return callWithErrorHandling( - s, - instance, - 2 - /* ErrorCodes.WATCH_GETTER */ - ); - } else { - warnInvalidSource(s); - } - }); - } else if (isFunction(source)) { - if (cb) { - getter = () => callWithErrorHandling( - source, - instance, - 2 - /* ErrorCodes.WATCH_GETTER */ - ); - } else { - getter = () => { - if (instance && instance.isUnmounted) { - return; - } - if (cleanup) { - cleanup(); - } - return callWithAsyncErrorHandling(source, instance, 3, [onCleanup]); - }; - } - } else { - getter = NOOP; - warnInvalidSource(source); - } - if (cb && deep) { - const baseGetter = getter; - getter = () => traverse(baseGetter()); - } - let cleanup; - let onCleanup = (fn) => { - cleanup = effect2.onStop = () => { - callWithErrorHandling( - fn, - instance, - 4 - /* ErrorCodes.WATCH_CLEANUP */ - ); - }; - }; - let ssrCleanup; - if (isInSSRComponentSetup) { - onCleanup = NOOP; - if (!cb) { - getter(); - } else if (immediate) { - callWithAsyncErrorHandling(cb, instance, 3, [ - getter(), - isMultiSource ? [] : void 0, - onCleanup - ]); - } - if (flush === "sync") { - const ctx = useSSRContext(); - ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = []); - } else { - return NOOP; - } - } - let oldValue = isMultiSource ? new Array(source.length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE; - const job = () => { - if (!effect2.active) { - return; - } - if (cb) { - const newValue = effect2.run(); - if (deep || forceTrigger || (isMultiSource ? newValue.some((v, i) => hasChanged(v, oldValue[i])) : hasChanged(newValue, oldValue)) || false) { - if (cleanup) { - cleanup(); - } - callWithAsyncErrorHandling(cb, instance, 3, [ - newValue, - // pass undefined as the old value when it's changed for the first time - oldValue === INITIAL_WATCHER_VALUE ? void 0 : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue, - onCleanup - ]); - oldValue = newValue; - } - } else { - effect2.run(); - } - }; - job.allowRecurse = !!cb; - let scheduler; - if (flush === "sync") { - scheduler = job; - } else if (flush === "post") { - scheduler = () => queuePostRenderEffect(job, instance && instance.suspense); - } else { - job.pre = true; - if (instance) - job.id = instance.uid; - scheduler = () => queueJob(job); - } - const effect2 = new ReactiveEffect(getter, scheduler); - if (true) { - effect2.onTrack = onTrack; - effect2.onTrigger = onTrigger; - } - if (cb) { - if (immediate) { - job(); - } else { - oldValue = effect2.run(); - } - } else if (flush === "post") { - queuePostRenderEffect(effect2.run.bind(effect2), instance && instance.suspense); - } else { - effect2.run(); - } - const unwatch = () => { - effect2.stop(); - if (instance && instance.scope) { - remove(instance.scope.effects, effect2); - } - }; - if (ssrCleanup) - ssrCleanup.push(unwatch); - return unwatch; -} -function instanceWatch(source, value, options) { - const publicThis = this.proxy; - const getter = isString(source) ? source.includes(".") ? createPathGetter(publicThis, source) : () => publicThis[source] : source.bind(publicThis, publicThis); - let cb; - if (isFunction(value)) { - cb = value; - } else { - cb = value.handler; - options = value; - } - const cur = currentInstance; - setCurrentInstance(this); - const res = doWatch(getter, cb.bind(publicThis), options); - if (cur) { - setCurrentInstance(cur); - } else { - unsetCurrentInstance(); - } - return res; -} -function createPathGetter(ctx, path) { - const segments = path.split("."); - return () => { - let cur = ctx; - for (let i = 0; i < segments.length && cur; i++) { - cur = cur[segments[i]]; - } - return cur; - }; -} -function traverse(value, seen) { - if (!isObject(value) || value[ - "__v_skip" - /* ReactiveFlags.SKIP */ - ]) { - return value; - } - seen = seen || /* @__PURE__ */ new Set(); - if (seen.has(value)) { - return value; - } - seen.add(value); - if (isRef(value)) { - traverse(value.value, seen); - } else if (isArray(value)) { - for (let i = 0; i < value.length; i++) { - traverse(value[i], seen); - } - } else if (isSet(value) || isMap(value)) { - value.forEach((v) => { - traverse(v, seen); - }); - } else if (isPlainObject(value)) { - for (const key in value) { - traverse(value[key], seen); - } - } - return value; -} -function useTransitionState() { - const state = { - isMounted: false, - isLeaving: false, - isUnmounting: false, - leavingVNodes: /* @__PURE__ */ new Map() - }; - onMounted(() => { - state.isMounted = true; - }); - onBeforeUnmount(() => { - state.isUnmounting = true; - }); - return state; -} -var TransitionHookValidator = [Function, Array]; -var BaseTransitionImpl = { - name: `BaseTransition`, - props: { - mode: String, - appear: Boolean, - persisted: Boolean, - // enter - onBeforeEnter: TransitionHookValidator, - onEnter: TransitionHookValidator, - onAfterEnter: TransitionHookValidator, - onEnterCancelled: TransitionHookValidator, - // leave - onBeforeLeave: TransitionHookValidator, - onLeave: TransitionHookValidator, - onAfterLeave: TransitionHookValidator, - onLeaveCancelled: TransitionHookValidator, - // appear - onBeforeAppear: TransitionHookValidator, - onAppear: TransitionHookValidator, - onAfterAppear: TransitionHookValidator, - onAppearCancelled: TransitionHookValidator - }, - setup(props, { slots }) { - const instance = getCurrentInstance(); - const state = useTransitionState(); - let prevTransitionKey; - return () => { - const children = slots.default && getTransitionRawChildren(slots.default(), true); - if (!children || !children.length) { - return; - } - let child = children[0]; - if (children.length > 1) { - let hasFound = false; - for (const c of children) { - if (c.type !== Comment) { - if (hasFound) { - warn2(" can only be used on a single element or component. Use for lists."); - break; - } - child = c; - hasFound = true; - if (false) - break; - } - } - } - const rawProps = toRaw(props); - const { mode } = rawProps; - if (mode && mode !== "in-out" && mode !== "out-in" && mode !== "default") { - warn2(`invalid mode: ${mode}`); - } - if (state.isLeaving) { - return emptyPlaceholder(child); - } - const innerChild = getKeepAliveChild(child); - if (!innerChild) { - return emptyPlaceholder(child); - } - const enterHooks = resolveTransitionHooks(innerChild, rawProps, state, instance); - setTransitionHooks(innerChild, enterHooks); - const oldChild = instance.subTree; - const oldInnerChild = oldChild && getKeepAliveChild(oldChild); - let transitionKeyChanged = false; - const { getTransitionKey } = innerChild.type; - if (getTransitionKey) { - const key = getTransitionKey(); - if (prevTransitionKey === void 0) { - prevTransitionKey = key; - } else if (key !== prevTransitionKey) { - prevTransitionKey = key; - transitionKeyChanged = true; - } - } - if (oldInnerChild && oldInnerChild.type !== Comment && (!isSameVNodeType(innerChild, oldInnerChild) || transitionKeyChanged)) { - const leavingHooks = resolveTransitionHooks(oldInnerChild, rawProps, state, instance); - setTransitionHooks(oldInnerChild, leavingHooks); - if (mode === "out-in") { - state.isLeaving = true; - leavingHooks.afterLeave = () => { - state.isLeaving = false; - if (instance.update.active !== false) { - instance.update(); - } - }; - return emptyPlaceholder(child); - } else if (mode === "in-out" && innerChild.type !== Comment) { - leavingHooks.delayLeave = (el, earlyRemove, delayedLeave) => { - const leavingVNodesCache = getLeavingNodesForType(state, oldInnerChild); - leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild; - el._leaveCb = () => { - earlyRemove(); - el._leaveCb = void 0; - delete enterHooks.delayedLeave; - }; - enterHooks.delayedLeave = delayedLeave; - }; - } - } - return child; - }; - } -}; -var BaseTransition = BaseTransitionImpl; -function getLeavingNodesForType(state, vnode) { - const { leavingVNodes } = state; - let leavingVNodesCache = leavingVNodes.get(vnode.type); - if (!leavingVNodesCache) { - leavingVNodesCache = /* @__PURE__ */ Object.create(null); - leavingVNodes.set(vnode.type, leavingVNodesCache); - } - return leavingVNodesCache; -} -function resolveTransitionHooks(vnode, props, state, instance) { - const { appear, mode, persisted = false, onBeforeEnter, onEnter, onAfterEnter, onEnterCancelled, onBeforeLeave, onLeave, onAfterLeave, onLeaveCancelled, onBeforeAppear, onAppear, onAfterAppear, onAppearCancelled } = props; - const key = String(vnode.key); - const leavingVNodesCache = getLeavingNodesForType(state, vnode); - const callHook3 = (hook, args) => { - hook && callWithAsyncErrorHandling(hook, instance, 9, args); - }; - const callAsyncHook = (hook, args) => { - const done = args[1]; - callHook3(hook, args); - if (isArray(hook)) { - if (hook.every((hook2) => hook2.length <= 1)) - done(); - } else if (hook.length <= 1) { - done(); - } - }; - const hooks = { - mode, - persisted, - beforeEnter(el) { - let hook = onBeforeEnter; - if (!state.isMounted) { - if (appear) { - hook = onBeforeAppear || onBeforeEnter; - } else { - return; - } - } - if (el._leaveCb) { - el._leaveCb( - true - /* cancelled */ - ); - } - const leavingVNode = leavingVNodesCache[key]; - if (leavingVNode && isSameVNodeType(vnode, leavingVNode) && leavingVNode.el._leaveCb) { - leavingVNode.el._leaveCb(); - } - callHook3(hook, [el]); - }, - enter(el) { - let hook = onEnter; - let afterHook = onAfterEnter; - let cancelHook = onEnterCancelled; - if (!state.isMounted) { - if (appear) { - hook = onAppear || onEnter; - afterHook = onAfterAppear || onAfterEnter; - cancelHook = onAppearCancelled || onEnterCancelled; - } else { - return; - } - } - let called = false; - const done = el._enterCb = (cancelled) => { - if (called) - return; - called = true; - if (cancelled) { - callHook3(cancelHook, [el]); - } else { - callHook3(afterHook, [el]); - } - if (hooks.delayedLeave) { - hooks.delayedLeave(); - } - el._enterCb = void 0; - }; - if (hook) { - callAsyncHook(hook, [el, done]); - } else { - done(); - } - }, - leave(el, remove2) { - const key2 = String(vnode.key); - if (el._enterCb) { - el._enterCb( - true - /* cancelled */ - ); - } - if (state.isUnmounting) { - return remove2(); - } - callHook3(onBeforeLeave, [el]); - let called = false; - const done = el._leaveCb = (cancelled) => { - if (called) - return; - called = true; - remove2(); - if (cancelled) { - callHook3(onLeaveCancelled, [el]); - } else { - callHook3(onAfterLeave, [el]); - } - el._leaveCb = void 0; - if (leavingVNodesCache[key2] === vnode) { - delete leavingVNodesCache[key2]; - } - }; - leavingVNodesCache[key2] = vnode; - if (onLeave) { - callAsyncHook(onLeave, [el, done]); - } else { - done(); - } - }, - clone(vnode2) { - return resolveTransitionHooks(vnode2, props, state, instance); - } - }; - return hooks; -} -function emptyPlaceholder(vnode) { - if (isKeepAlive(vnode)) { - vnode = cloneVNode(vnode); - vnode.children = null; - return vnode; - } -} -function getKeepAliveChild(vnode) { - return isKeepAlive(vnode) ? vnode.children ? vnode.children[0] : void 0 : vnode; -} -function setTransitionHooks(vnode, hooks) { - if (vnode.shapeFlag & 6 && vnode.component) { - setTransitionHooks(vnode.component.subTree, hooks); - } else if (vnode.shapeFlag & 128) { - vnode.ssContent.transition = hooks.clone(vnode.ssContent); - vnode.ssFallback.transition = hooks.clone(vnode.ssFallback); - } else { - vnode.transition = hooks; - } -} -function getTransitionRawChildren(children, keepComment = false, parentKey) { - let ret = []; - let keyedFragmentCount = 0; - for (let i = 0; i < children.length; i++) { - let child = children[i]; - const key = parentKey == null ? child.key : String(parentKey) + String(child.key != null ? child.key : i); - if (child.type === Fragment) { - if (child.patchFlag & 128) - keyedFragmentCount++; - ret = ret.concat(getTransitionRawChildren(child.children, keepComment, key)); - } else if (keepComment || child.type !== Comment) { - ret.push(key != null ? cloneVNode(child, { key }) : child); - } - } - if (keyedFragmentCount > 1) { - for (let i = 0; i < ret.length; i++) { - ret[i].patchFlag = -2; - } - } - return ret; -} -function defineComponent(options) { - return isFunction(options) ? { setup: options, name: options.name } : options; -} -var isAsyncWrapper = (i) => !!i.type.__asyncLoader; -function defineAsyncComponent(source) { - if (isFunction(source)) { - source = { loader: source }; - } - const { - loader, - loadingComponent, - errorComponent, - delay = 200, - timeout, - // undefined = never times out - suspensible = true, - onError: userOnError - } = source; - let pendingRequest = null; - let resolvedComp; - let retries = 0; - const retry = () => { - retries++; - pendingRequest = null; - return load(); - }; - const load = () => { - let thisRequest; - return pendingRequest || (thisRequest = pendingRequest = loader().catch((err) => { - err = err instanceof Error ? err : new Error(String(err)); - if (userOnError) { - return new Promise((resolve2, reject) => { - const userRetry = () => resolve2(retry()); - const userFail = () => reject(err); - userOnError(err, userRetry, userFail, retries + 1); - }); - } else { - throw err; - } - }).then((comp) => { - if (thisRequest !== pendingRequest && pendingRequest) { - return pendingRequest; - } - if (!comp) { - warn2(`Async component loader resolved to undefined. If you are using retry(), make sure to return its return value.`); - } - if (comp && (comp.__esModule || comp[Symbol.toStringTag] === "Module")) { - comp = comp.default; - } - if (comp && !isObject(comp) && !isFunction(comp)) { - throw new Error(`Invalid async component load result: ${comp}`); - } - resolvedComp = comp; - return comp; - })); - }; - return defineComponent({ - name: "AsyncComponentWrapper", - __asyncLoader: load, - get __asyncResolved() { - return resolvedComp; - }, - setup() { - const instance = currentInstance; - if (resolvedComp) { - return () => createInnerComp(resolvedComp, instance); - } - const onError = (err) => { - pendingRequest = null; - handleError( - err, - instance, - 13, - !errorComponent - /* do not throw in dev if user provided error component */ - ); - }; - if (suspensible && instance.suspense || isInSSRComponentSetup) { - return load().then((comp) => { - return () => createInnerComp(comp, instance); - }).catch((err) => { - onError(err); - return () => errorComponent ? createVNode(errorComponent, { - error: err - }) : null; - }); - } - const loaded = ref(false); - const error = ref(); - const delayed = ref(!!delay); - if (delay) { - setTimeout(() => { - delayed.value = false; - }, delay); - } - if (timeout != null) { - setTimeout(() => { - if (!loaded.value && !error.value) { - const err = new Error(`Async component timed out after ${timeout}ms.`); - onError(err); - error.value = err; - } - }, timeout); - } - load().then(() => { - loaded.value = true; - if (instance.parent && isKeepAlive(instance.parent.vnode)) { - queueJob(instance.parent.update); - } - }).catch((err) => { - onError(err); - error.value = err; - }); - return () => { - if (loaded.value && resolvedComp) { - return createInnerComp(resolvedComp, instance); - } else if (error.value && errorComponent) { - return createVNode(errorComponent, { - error: error.value - }); - } else if (loadingComponent && !delayed.value) { - return createVNode(loadingComponent); - } - }; - } - }); -} -function createInnerComp(comp, parent) { - const { ref: ref2, props, children, ce } = parent.vnode; - const vnode = createVNode(comp, props, children); - vnode.ref = ref2; - vnode.ce = ce; - delete parent.vnode.ce; - return vnode; -} -var isKeepAlive = (vnode) => vnode.type.__isKeepAlive; -var KeepAliveImpl = { - name: `KeepAlive`, - // Marker for special handling inside the renderer. We are not using a === - // check directly on KeepAlive in the renderer, because importing it directly - // would prevent it from being tree-shaken. - __isKeepAlive: true, - props: { - include: [String, RegExp, Array], - exclude: [String, RegExp, Array], - max: [String, Number] - }, - setup(props, { slots }) { - const instance = getCurrentInstance(); - const sharedContext = instance.ctx; - if (!sharedContext.renderer) { - return () => { - const children = slots.default && slots.default(); - return children && children.length === 1 ? children[0] : children; - }; - } - const cache = /* @__PURE__ */ new Map(); - const keys = /* @__PURE__ */ new Set(); - let current = null; - if (true) { - instance.__v_cache = cache; - } - const parentSuspense = instance.suspense; - const { renderer: { p: patch, m: move, um: _unmount, o: { createElement } } } = sharedContext; - const storageContainer = createElement("div"); - sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => { - const instance2 = vnode.component; - move(vnode, container, anchor, 0, parentSuspense); - patch(instance2.vnode, vnode, container, anchor, instance2, parentSuspense, isSVG, vnode.slotScopeIds, optimized); - queuePostRenderEffect(() => { - instance2.isDeactivated = false; - if (instance2.a) { - invokeArrayFns(instance2.a); - } - const vnodeHook = vnode.props && vnode.props.onVnodeMounted; - if (vnodeHook) { - invokeVNodeHook(vnodeHook, instance2.parent, vnode); - } - }, parentSuspense); - if (true) { - devtoolsComponentAdded(instance2); - } - }; - sharedContext.deactivate = (vnode) => { - const instance2 = vnode.component; - move(vnode, storageContainer, null, 1, parentSuspense); - queuePostRenderEffect(() => { - if (instance2.da) { - invokeArrayFns(instance2.da); - } - const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted; - if (vnodeHook) { - invokeVNodeHook(vnodeHook, instance2.parent, vnode); - } - instance2.isDeactivated = true; - }, parentSuspense); - if (true) { - devtoolsComponentAdded(instance2); - } - }; - function unmount(vnode) { - resetShapeFlag(vnode); - _unmount(vnode, instance, parentSuspense, true); - } - function pruneCache(filter) { - cache.forEach((vnode, key) => { - const name = getComponentName(vnode.type); - if (name && (!filter || !filter(name))) { - pruneCacheEntry(key); - } - }); - } - function pruneCacheEntry(key) { - const cached = cache.get(key); - if (!current || !isSameVNodeType(cached, current)) { - unmount(cached); - } else if (current) { - resetShapeFlag(current); - } - cache.delete(key); - keys.delete(key); - } - watch( - () => [props.include, props.exclude], - ([include, exclude]) => { - include && pruneCache((name) => matches(include, name)); - exclude && pruneCache((name) => !matches(exclude, name)); - }, - // prune post-render after `current` has been updated - { flush: "post", deep: true } - ); - let pendingCacheKey = null; - const cacheSubtree = () => { - if (pendingCacheKey != null) { - cache.set(pendingCacheKey, getInnerChild(instance.subTree)); - } - }; - onMounted(cacheSubtree); - onUpdated(cacheSubtree); - onBeforeUnmount(() => { - cache.forEach((cached) => { - const { subTree, suspense } = instance; - const vnode = getInnerChild(subTree); - if (cached.type === vnode.type && cached.key === vnode.key) { - resetShapeFlag(vnode); - const da = vnode.component.da; - da && queuePostRenderEffect(da, suspense); - return; - } - unmount(cached); - }); - }); - return () => { - pendingCacheKey = null; - if (!slots.default) { - return null; - } - const children = slots.default(); - const rawVNode = children[0]; - if (children.length > 1) { - if (true) { - warn2(`KeepAlive should contain exactly one component child.`); - } - current = null; - return children; - } else if (!isVNode(rawVNode) || !(rawVNode.shapeFlag & 4) && !(rawVNode.shapeFlag & 128)) { - current = null; - return rawVNode; - } - let vnode = getInnerChild(rawVNode); - const comp = vnode.type; - const name = getComponentName(isAsyncWrapper(vnode) ? vnode.type.__asyncResolved || {} : comp); - const { include, exclude, max } = props; - if (include && (!name || !matches(include, name)) || exclude && name && matches(exclude, name)) { - current = vnode; - return rawVNode; - } - const key = vnode.key == null ? comp : vnode.key; - const cachedVNode = cache.get(key); - if (vnode.el) { - vnode = cloneVNode(vnode); - if (rawVNode.shapeFlag & 128) { - rawVNode.ssContent = vnode; - } - } - pendingCacheKey = key; - if (cachedVNode) { - vnode.el = cachedVNode.el; - vnode.component = cachedVNode.component; - if (vnode.transition) { - setTransitionHooks(vnode, vnode.transition); - } - vnode.shapeFlag |= 512; - keys.delete(key); - keys.add(key); - } else { - keys.add(key); - if (max && keys.size > parseInt(max, 10)) { - pruneCacheEntry(keys.values().next().value); - } - } - vnode.shapeFlag |= 256; - current = vnode; - return isSuspense(rawVNode.type) ? rawVNode : vnode; - }; - } -}; -var KeepAlive = KeepAliveImpl; -function matches(pattern, name) { - if (isArray(pattern)) { - return pattern.some((p2) => matches(p2, name)); - } else if (isString(pattern)) { - return pattern.split(",").includes(name); - } else if (isRegExp(pattern)) { - return pattern.test(name); - } - return false; -} -function onActivated(hook, target) { - registerKeepAliveHook(hook, "a", target); -} -function onDeactivated(hook, target) { - registerKeepAliveHook(hook, "da", target); -} -function registerKeepAliveHook(hook, type, target = currentInstance) { - const wrappedHook = hook.__wdc || (hook.__wdc = () => { - let current = target; - while (current) { - if (current.isDeactivated) { - return; - } - current = current.parent; - } - return hook(); - }); - injectHook(type, wrappedHook, target); - if (target) { - let current = target.parent; - while (current && current.parent) { - if (isKeepAlive(current.parent.vnode)) { - injectToKeepAliveRoot(wrappedHook, type, target, current); - } - current = current.parent; - } - } -} -function injectToKeepAliveRoot(hook, type, target, keepAliveRoot) { - const injected = injectHook( - type, - hook, - keepAliveRoot, - true - /* prepend */ - ); - onUnmounted(() => { - remove(keepAliveRoot[type], injected); - }, target); -} -function resetShapeFlag(vnode) { - vnode.shapeFlag &= ~256; - vnode.shapeFlag &= ~512; -} -function getInnerChild(vnode) { - return vnode.shapeFlag & 128 ? vnode.ssContent : vnode; -} -function injectHook(type, hook, target = currentInstance, prepend = false) { - if (target) { - const hooks = target[type] || (target[type] = []); - const wrappedHook = hook.__weh || (hook.__weh = (...args) => { - if (target.isUnmounted) { - return; - } - pauseTracking(); - setCurrentInstance(target); - const res = callWithAsyncErrorHandling(hook, target, type, args); - unsetCurrentInstance(); - resetTracking(); - return res; - }); - if (prepend) { - hooks.unshift(wrappedHook); - } else { - hooks.push(wrappedHook); - } - return wrappedHook; - } else if (true) { - const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, "")); - warn2(`${apiName} is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup(). If you are using async setup(), make sure to register lifecycle hooks before the first await statement.`); - } -} -var createHook = (lifecycle) => (hook, target = currentInstance) => ( - // post-create lifecycle registrations are noops during SSR (except for serverPrefetch) - (!isInSSRComponentSetup || lifecycle === "sp") && injectHook(lifecycle, (...args) => hook(...args), target) -); -var onBeforeMount = createHook( - "bm" - /* LifecycleHooks.BEFORE_MOUNT */ -); -var onMounted = createHook( - "m" - /* LifecycleHooks.MOUNTED */ -); -var onBeforeUpdate = createHook( - "bu" - /* LifecycleHooks.BEFORE_UPDATE */ -); -var onUpdated = createHook( - "u" - /* LifecycleHooks.UPDATED */ -); -var onBeforeUnmount = createHook( - "bum" - /* LifecycleHooks.BEFORE_UNMOUNT */ -); -var onUnmounted = createHook( - "um" - /* LifecycleHooks.UNMOUNTED */ -); -var onServerPrefetch = createHook( - "sp" - /* LifecycleHooks.SERVER_PREFETCH */ -); -var onRenderTriggered = createHook( - "rtg" - /* LifecycleHooks.RENDER_TRIGGERED */ -); -var onRenderTracked = createHook( - "rtc" - /* LifecycleHooks.RENDER_TRACKED */ -); -function onErrorCaptured(hook, target = currentInstance) { - injectHook("ec", hook, target); -} -function validateDirectiveName(name) { - if (isBuiltInDirective(name)) { - warn2("Do not use built-in directive ids as custom directive id: " + name); - } -} -function withDirectives(vnode, directives) { - const internalInstance = currentRenderingInstance; - if (internalInstance === null) { - warn2(`withDirectives can only be used inside render functions.`); - return vnode; - } - const instance = getExposeProxy(internalInstance) || internalInstance.proxy; - const bindings = vnode.dirs || (vnode.dirs = []); - for (let i = 0; i < directives.length; i++) { - let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]; - if (dir) { - if (isFunction(dir)) { - dir = { - mounted: dir, - updated: dir - }; - } - if (dir.deep) { - traverse(value); - } - bindings.push({ - dir, - instance, - value, - oldValue: void 0, - arg, - modifiers - }); - } - } - return vnode; -} -function invokeDirectiveHook(vnode, prevVNode, instance, name) { - const bindings = vnode.dirs; - const oldBindings = prevVNode && prevVNode.dirs; - for (let i = 0; i < bindings.length; i++) { - const binding = bindings[i]; - if (oldBindings) { - binding.oldValue = oldBindings[i].value; - } - let hook = binding.dir[name]; - if (hook) { - pauseTracking(); - callWithAsyncErrorHandling(hook, instance, 8, [ - vnode.el, - binding, - vnode, - prevVNode - ]); - resetTracking(); - } - } -} -var COMPONENTS = "components"; -var DIRECTIVES = "directives"; -function resolveComponent(name, maybeSelfReference) { - return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name; -} -var NULL_DYNAMIC_COMPONENT = Symbol(); -function resolveDynamicComponent(component) { - if (isString(component)) { - return resolveAsset(COMPONENTS, component, false) || component; - } else { - return component || NULL_DYNAMIC_COMPONENT; - } -} -function resolveDirective(name) { - return resolveAsset(DIRECTIVES, name); -} -function resolveAsset(type, name, warnMissing = true, maybeSelfReference = false) { - const instance = currentRenderingInstance || currentInstance; - if (instance) { - const Component = instance.type; - if (type === COMPONENTS) { - const selfName = getComponentName( - Component, - false - /* do not include inferred name to avoid breaking existing code */ - ); - if (selfName && (selfName === name || selfName === camelize(name) || selfName === capitalize(camelize(name)))) { - return Component; - } - } - const res = ( - // local registration - // check instance[type] first which is resolved for options API - resolve(instance[type] || Component[type], name) || // global registration - resolve(instance.appContext[type], name) - ); - if (!res && maybeSelfReference) { - return Component; - } - if (warnMissing && !res) { - const extra = type === COMPONENTS ? ` -If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.` : ``; - warn2(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`); - } - return res; - } else if (true) { - warn2(`resolve${capitalize(type.slice(0, -1))} can only be used in render() or setup().`); - } -} -function resolve(registry, name) { - return registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))]); -} -function renderList(source, renderItem, cache, index) { - let ret; - const cached = cache && cache[index]; - if (isArray(source) || isString(source)) { - ret = new Array(source.length); - for (let i = 0, l = source.length; i < l; i++) { - ret[i] = renderItem(source[i], i, void 0, cached && cached[i]); - } - } else if (typeof source === "number") { - if (!Number.isInteger(source)) { - warn2(`The v-for range expect an integer value but got ${source}.`); - } - ret = new Array(source); - for (let i = 0; i < source; i++) { - ret[i] = renderItem(i + 1, i, void 0, cached && cached[i]); - } - } else if (isObject(source)) { - if (source[Symbol.iterator]) { - ret = Array.from(source, (item, i) => renderItem(item, i, void 0, cached && cached[i])); - } else { - const keys = Object.keys(source); - ret = new Array(keys.length); - for (let i = 0, l = keys.length; i < l; i++) { - const key = keys[i]; - ret[i] = renderItem(source[key], key, i, cached && cached[i]); - } - } - } else { - ret = []; - } - if (cache) { - cache[index] = ret; - } - return ret; -} -function createSlots(slots, dynamicSlots) { - for (let i = 0; i < dynamicSlots.length; i++) { - const slot = dynamicSlots[i]; - if (isArray(slot)) { - for (let j = 0; j < slot.length; j++) { - slots[slot[j].name] = slot[j].fn; - } - } else if (slot) { - slots[slot.name] = slot.key ? (...args) => { - const res = slot.fn(...args); - if (res) - res.key = slot.key; - return res; - } : slot.fn; - } - } - return slots; -} -function renderSlot(slots, name, props = {}, fallback, noSlotted) { - if (currentRenderingInstance.isCE || currentRenderingInstance.parent && isAsyncWrapper(currentRenderingInstance.parent) && currentRenderingInstance.parent.isCE) { - if (name !== "default") - props.name = name; - return createVNode("slot", props, fallback && fallback()); - } - let slot = slots[name]; - if (slot && slot.length > 1) { - warn2(`SSR-optimized slot function detected in a non-SSR-optimized render function. You need to mark this component with $dynamic-slots in the parent template.`); - slot = () => []; - } - if (slot && slot._c) { - slot._d = false; - } - openBlock(); - const validSlotContent = slot && ensureValidVNode(slot(props)); - const rendered = createBlock( - Fragment, - { - key: props.key || // slot content array of a dynamic conditional slot may have a branch - // key attached in the `createSlots` helper, respect that - validSlotContent && validSlotContent.key || `_${name}` - }, - validSlotContent || (fallback ? fallback() : []), - validSlotContent && slots._ === 1 ? 64 : -2 - /* PatchFlags.BAIL */ - ); - if (!noSlotted && rendered.scopeId) { - rendered.slotScopeIds = [rendered.scopeId + "-s"]; - } - if (slot && slot._c) { - slot._d = true; - } - return rendered; -} -function ensureValidVNode(vnodes) { - return vnodes.some((child) => { - if (!isVNode(child)) - return true; - if (child.type === Comment) - return false; - if (child.type === Fragment && !ensureValidVNode(child.children)) - return false; - return true; - }) ? vnodes : null; -} -function toHandlers(obj, preserveCaseIfNecessary) { - const ret = {}; - if (!isObject(obj)) { - warn2(`v-on with no argument expects an object value.`); - return ret; - } - for (const key in obj) { - ret[preserveCaseIfNecessary && /[A-Z]/.test(key) ? `on:${key}` : toHandlerKey(key)] = obj[key]; - } - return ret; -} -var getPublicInstance = (i) => { - if (!i) - return null; - if (isStatefulComponent(i)) - return getExposeProxy(i) || i.proxy; - return getPublicInstance(i.parent); -}; -var publicPropertiesMap = ( - // Move PURE marker to new line to workaround compiler discarding it - // due to type annotation - extend(/* @__PURE__ */ Object.create(null), { - $: (i) => i, - $el: (i) => i.vnode.el, - $data: (i) => i.data, - $props: (i) => true ? shallowReadonly(i.props) : i.props, - $attrs: (i) => true ? shallowReadonly(i.attrs) : i.attrs, - $slots: (i) => true ? shallowReadonly(i.slots) : i.slots, - $refs: (i) => true ? shallowReadonly(i.refs) : i.refs, - $parent: (i) => getPublicInstance(i.parent), - $root: (i) => getPublicInstance(i.root), - $emit: (i) => i.emit, - $options: (i) => __VUE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type, - $forceUpdate: (i) => i.f || (i.f = () => queueJob(i.update)), - $nextTick: (i) => i.n || (i.n = nextTick.bind(i.proxy)), - $watch: (i) => __VUE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP - }) -); -var isReservedPrefix = (key) => key === "_" || key === "$"; -var hasSetupBinding = (state, key) => state !== EMPTY_OBJ && !state.__isScriptSetup && hasOwn(state, key); -var PublicInstanceProxyHandlers = { - get({ _: instance }, key) { - const { ctx, setupState, data, props, accessCache, type, appContext } = instance; - if (key === "__isVue") { - return true; - } - let normalizedProps; - if (key[0] !== "$") { - const n = accessCache[key]; - if (n !== void 0) { - switch (n) { - case 1: - return setupState[key]; - case 2: - return data[key]; - case 4: - return ctx[key]; - case 3: - return props[key]; - } - } else if (hasSetupBinding(setupState, key)) { - accessCache[key] = 1; - return setupState[key]; - } else if (data !== EMPTY_OBJ && hasOwn(data, key)) { - accessCache[key] = 2; - return data[key]; - } else if ( - // only cache other properties when instance has declared (thus stable) - // props - (normalizedProps = instance.propsOptions[0]) && hasOwn(normalizedProps, key) - ) { - accessCache[key] = 3; - return props[key]; - } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) { - accessCache[key] = 4; - return ctx[key]; - } else if (!__VUE_OPTIONS_API__ || shouldCacheAccess) { - accessCache[key] = 0; - } - } - const publicGetter = publicPropertiesMap[key]; - let cssModule, globalProperties; - if (publicGetter) { - if (key === "$attrs") { - track(instance, "get", key); - markAttrsAccessed(); - } - return publicGetter(instance); - } else if ( - // css module (injected by vue-loader) - (cssModule = type.__cssModules) && (cssModule = cssModule[key]) - ) { - return cssModule; - } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) { - accessCache[key] = 4; - return ctx[key]; - } else if ( - // global properties - globalProperties = appContext.config.globalProperties, hasOwn(globalProperties, key) - ) { - { - return globalProperties[key]; - } - } else if (currentRenderingInstance && (!isString(key) || // #1091 avoid internal isRef/isVNode checks on component instance leading - // to infinite warning loop - key.indexOf("__v") !== 0)) { - if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) { - warn2(`Property ${JSON.stringify(key)} must be accessed via $data because it starts with a reserved character ("$" or "_") and is not proxied on the render context.`); - } else if (instance === currentRenderingInstance) { - warn2(`Property ${JSON.stringify(key)} was accessed during render but is not defined on instance.`); - } - } - }, - set({ _: instance }, key, value) { - const { data, setupState, ctx } = instance; - if (hasSetupBinding(setupState, key)) { - setupState[key] = value; - return true; - } else if (setupState.__isScriptSetup && hasOwn(setupState, key)) { - warn2(`Cannot mutate + + diff --git a/.vitepress/config.ts b/.vitepress/config.ts deleted file mode 100644 index 8d9be0d..0000000 --- a/.vitepress/config.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { defineConfig } from 'vitepress' - -// https://vitepress.dev/reference/site-config -export default defineConfig({ - title: 'Easytier', - description: '一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现', - locales: { - root: { - label: '简体中文', - lang: 'zh-CN', - title: 'Easytier', - description: '一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现' - }, - en: { - label: 'English', - lang: 'en', - title: 'Easytier', - description: 'A simple, secure, decentralized VPN mesh network solution, implemented using Rust language and Tokio framework.' - } - }, - themeConfig: { - // https://vitepress.dev/reference/default-theme-config - - sidebar: [ - { text: '简介', link: '/guide/introduction' }, - { text: '安装', link: '/guide/installation' }, - { - text: '组网', - link: '/guide/networking', - items: [ - { text: '双节点', link: '/guide/network/two-node' }, - { text: '多节点', link: '/guide/network/multi-node' }, - { text: '子网代理(点对网)', link: '/guide/network/point-to' }, - { text: '无公网IP', link: '/guide/network/without-public-ip' }, - { text: '使用 WireGuard 客户端接入', link: '/guide/network/use-wireguard-client' } - ] - }, - { - items: [ - { text: '路线图', link: '/guide/roadmap' }, - { text: '社区和贡献', link: '/guide/community-and-contribution' }, - { text: '许可证', link: '/guide/license' }, - { text: '联系方式', link: '/guide/contact' }, - ] - } - ], - - socialLinks: [ - { icon: 'github', link: 'https://github.com/KKRainbow/EasyTier' } - ] - } -}) diff --git a/.vitepress/config/cn.ts b/.vitepress/config/cn.ts new file mode 100644 index 0000000..f4e743c --- /dev/null +++ b/.vitepress/config/cn.ts @@ -0,0 +1,143 @@ +import fs from 'node:fs' +import { defineConfig } from 'vitepress' + +export const cn = defineConfig({ + title: 'EasyTier - 简单、安全、去中心化的异地组网方案', + lang: 'cn', + description: '一个简单、安全、去中心化的内网穿透 SD-WAN 异地组网方案,使用 Rust 语言和 Tokio 框架实现', + + themeConfig: { + sidebar: [ + { + text: '开始', + items: [ + { text: '功能简介', link: '/guide/introduction' }, + { text: '下载', link: '/guide/download' }, + { text: '安装 CLI', link: '/guide/installation' }, + { text: '安装 GUI', link: '/guide/installation_gui' }, + { text: '常见问题', link: '/guide/faq' }, + ], + }, + { + text: '命令行工具组网', + link: '/guide/networking', + items: [ + { text: '快速组网', link: '/guide/network/quick-networking' }, + { text: '去中心组网', link: '/guide/network/decentralized-networking' }, + { text: '使用 Web 控制台组网', link: '/guide/network/web-console' }, + { text: '使用 WireGuard 客户端接入', link: '/guide/network/use-easytier-with-wireguard-client' }, + { text: '子网代理(点对网)', link: '/guide/network/point-to-networking' }, + { text: '带宽延迟优化(KCP 代理)', link: '/guide/network/kcp-proxy' }, + { text: '高级功能', collapsed: true, items: [ + { text: '网对网', link: '/guide/network/network-to-network' }, + { text: '无 TUN 模式(免 Root 权限)', link: '/guide/network/no-root' }, + { text: 'SOCKS5', link: '/guide/network/socks5' }, + { text: '搭建共享节点', link: '/guide/network/host-public-server' }, + { text: '改善 P2P', link: '/guide/network/p2p-optimize' }, + { text: '魔法 DNS', link: '/guide/network/magic-dns' }, + { text: 'ACL', link: '/guide/config/acl' }, + ] }, + { text: '开机自启(注册服务)', collapsed: true, items: [ + { text: '一键安装服务', link: '/guide/network/oneclick-install-as-service' }, + { text: '安装为 Windows 服务', link: '/guide/network/install-as-a-windows-service' }, + { text: '安装为 Linux systemd 服务', link: '/guide/network/install-as-a-systemd-service' }, + { text: '安装为 macOS 服务', link: '/guide/network/install-as-a-macos-service' }, + ] }, + { text: '其他配置', link: '/guide/network/configurations' }, + { text: '配置文件', link: '/guide/network/config-file' }, + ], + }, + { + text: '图形界面 GUI 组网', + link: 'guide/gui/index', + items: [ + { text: '公共服务器组网', link: '/guide/gui/basic' }, + { text: '手动组网', link: '/guide/gui/manual' }, + { text: 'WireGuard 接入', link: '/guide/gui/vpn_portal' }, + { text: '子网代理', link: '/guide/gui/subnet_proxy' }, + { text: 'EasyTier 管理器', link: '/guide/gui/easytier-manager' }, + { text: 'EasyTier 游戏联机启动器', link: '/guide/gui/easytier-game' }, + { text: 'AstralGame 联机工具', link: '/guide/gui/astral-game' }, + ], + }, + { + items: [ + { text: '关于当前互联网状况', link: '/guide/aboutstateofInternet' }, + { text: '关于 P2P', link: '/guide/aboutp2p' }, + { text: '性能测试', link: '/guide/perf' }, + { text: '路线图', link: '/guide/roadmap' }, + { text: '社区和贡献', link: '/guide/community-and-contribution' }, + { text: '许可证', link: '/guide/license' }, + { text: '联系方式', link: '/guide/contact' }, + ], + }, + ], + nav: [ + { + text: '社区共享节点', + link: 'https://uptime.easytier.cn/', + }, + { + text: 'Web 控制台', + link: 'https://easytier.cn/web', + }, + { + text: '提交反馈', + link: 'https://github.com/EasyTier/EasyTier/issues', + }, + { + text: '变更日志', + link: 'https://github.com/EasyTier/EasyTier/releases', + }, + ], + footer: { + message: '基于 Apache License 2.0 许可发布', + copyright: '版权所有 © 2024-present EasyTier | ' + + '' + + 'ICP 备案' + + '浙ICP备2024137671号-1', + }, + editLink: { + pattern: 'https://github.com/EasyTier/easytier.github.io/edit/main/:path', + text: '在 GitHub 上编辑此页面', + }, + docFooter: { + prev: '上一页', + next: '下一页', + }, + outline: { + label: '页面导航', + }, + lastUpdated: { + text: '最后更新于', + formatOptions: { + dateStyle: 'short', + timeStyle: 'medium', + }, + }, + langMenuLabel: '多语言', + returnToTopLabel: '回到顶部', + sidebarMenuLabel: '菜单', + darkModeSwitchLabel: '主题', + lightModeSwitchTitle: '切换到浅色模式', + darkModeSwitchTitle: '切换到深色模式', + }, +}) + +export const cnSearch = { + translations: { + button: { + buttonText: '搜索文档', + buttonAriaLabel: '搜索文档', + }, + modal: { + noResultsText: '无法找到相关结果', + resetButtonTitle: '清除查询条件', + footer: { + selectText: '选择', + navigateText: '切换', + closeText: '关闭', + }, + }, + }, +} diff --git a/.vitepress/config/en.ts b/.vitepress/config/en.ts new file mode 100644 index 0000000..574ebba --- /dev/null +++ b/.vitepress/config/en.ts @@ -0,0 +1,141 @@ +import fs from 'node:fs' +import { defineConfig } from 'vitepress' + +export const en = defineConfig({ + title: 'EasyTier - A Simple, Secure, Decentralized SD-WAN Solution', + lang: 'en', + description: 'A simple, secure, decentralized SD-WAN solution for intranet penetration, implemented using Rust and the Tokio framework', + + themeConfig: { + sidebar: [ + { + text: 'Getting Started', + items: [ + { text: 'Introduction', link: '/en/guide/introduction' }, + { text: 'Download', link: '/en/guide/download' }, + { text: 'Installation CLI', link: '/en/guide/installation' }, + { text: 'Installation GUI', link: '/en/guide/installation_gui' }, + { text: 'FAQ', link: '/en/guide/faq' }, + ], + }, + { + text: 'Command Line Networking', + link: '/en/guide/networking', + items: [ + { text: 'Quick Networking', link: '/en/guide/network/quick-networking' }, + { text: 'Decentralized Networking', link: '/en/guide/network/decentralized-networking' }, + { text: 'Networking with Web Console', link: '/en/guide/network/web-console' }, + { text: 'Using WireGuard Client', link: '/en/guide/network/use-easytier-with-wireguard-client' }, + { text: 'Subnet Proxy (Point-to-Network)', link: '/en/guide/network/point-to-networking' }, + { text: 'Bandwidth and Latency Optimization (KCP Proxy)', link: '/en/guide/network/kcp-proxy' }, + { text: 'Advanced Features', collapsed: true, items: [ + { text: 'Network-to-Network', link: '/en/guide/network/network-to-network' }, + { text: 'No TUN Mode (No Root Required)', link: '/en/guide/network/no-root' }, + { text: 'SOCKS5', link: '/en/guide/network/socks5' }, + { text: 'Hosting Public Server', link: '/en/guide/network/host-public-server' }, + { text: 'P2P Optimization', link: '/en/guide/network/p2p-optimize' }, + { text: 'Magic DNS', link: '/en/guide/network/magic-dns' }, + { text: 'ACL', link: '/en/guide/config/acl' }, + ] }, + { text: 'Autostart (Register Service)', collapsed: true, items: [ + { text: 'One-Click Install Service', link: '/en/guide/network/oneclick-install-as-service' }, + { text: 'Install as Windows Service', link: '/en/guide/network/install-as-a-windows-service' }, + { text: 'Install as Linux systemd Service', link: '/en/guide/network/install-as-a-systemd-service' }, + { text: 'Install as macOS Service', link: '/en/guide/network/install-as-a-macos-service' }, + ] }, + { text: 'Other Configurations', link: '/en/guide/network/configurations' }, + { text: 'Configuration File', link: '/en/guide/network/config-file' }, + ], + }, + { + text: 'GUI Networking', + link: '/en/guide/gui/index', + items: [ + { text: 'Public Server Networking', link: '/en/guide/gui/basic' }, + { text: 'Manual Networking', link: '/en/guide/gui/manual' }, + { text: 'WireGuard Access', link: '/en/guide/gui/vpn_portal' }, + { text: 'Subnet Proxy', link: '/en/guide/gui/subnet_proxy' }, + { text: 'EasyTier Manager', link: '/en/guide/gui/easytier-manager' }, + { text: 'EasyTier Game Launcher', link: '/en/guide/gui/easytier-game' }, + { text: 'Astral Game Connection Tool', link: '/en/guide/gui/astral-game' }, + ], + }, + { + items: [ + { text: 'Performance Testing', link: '/en/guide/perf' }, + { text: 'Roadmap', link: '/en/guide/roadmap' }, + { text: 'Community and Contribution', link: '/en/guide/community-and-contribution' }, + { text: 'License', link: '/en/guide/license' }, + { text: 'Contact', link: '/en/guide/contact' }, + ], + }, + ], + nav: [ + { + text: 'Public Shared Nodes', + link: 'https://uptime.easytier.cn/', + }, + { + text: 'Web Console', + link: 'https://easytier.cn/web', + }, + { + text: 'Submit Feedback', + link: 'https://github.com/EasyTier/EasyTier/issues', + }, + { + text: 'Changelog', + link: 'https://github.com/EasyTier/EasyTier/releases', + }, + ], + footer: { + message: 'Released under the Apache License 2.0', + copyright: 'Copyright © 2024-present EasyTier | ' + + '' + + 'ICP Record' + + 'Zhejiang ICP No. 2024137671-1', + }, + editLink: { + pattern: 'https://github.com/EasyTier/easytier.github.io/edit/main/:path', + text: 'Edit this page on GitHub', + }, + docFooter: { + prev: 'Previous Page', + next: 'Next Page', + }, + outline: { + label: 'Page Navigation', + }, + lastUpdated: { + text: 'Last Updated', + formatOptions: { + dateStyle: 'short', + timeStyle: 'medium', + }, + }, + langMenuLabel: 'Languages', + returnToTopLabel: 'Back to Top', + sidebarMenuLabel: 'Menu', + darkModeSwitchLabel: 'Theme', + lightModeSwitchTitle: 'Switch to Light Mode', + darkModeSwitchTitle: 'Switch to Dark Mode', + }, +}) + +export const enSearch = { + translations: { + button: { + buttonText: 'Search Docs', + buttonAriaLabel: 'Search Docs', + }, + modal: { + noResultsText: 'No results found', + resetButtonTitle: 'Clear search query', + footer: { + selectText: 'Select', + navigateText: 'Navigate', + closeText: 'Close', + }, + }, + }, +} diff --git a/.vitepress/config/index.ts b/.vitepress/config/index.ts new file mode 100644 index 0000000..0dc649c --- /dev/null +++ b/.vitepress/config/index.ts @@ -0,0 +1,75 @@ +// @ts-expect-error ignore next line +import taskLists from 'markdown-it-task-lists' +import { withMermaid } from 'vitepress-plugin-mermaid' + +import { cn, cnSearch } from './cn' + +import { en } from './en' + +export default withMermaid({ + base: '/', + lastUpdated: true, + head: [ + ['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/easytier.png' }], + ['meta', { name: 'msvalidate.01', content: 'C6CB41F1DA6096106497701D002B19AD' }], + ['meta', { name: 'author', content: 'EasyTier' }], + ['meta', { name: 'keywords', content: 'easytier,SD-WAN,networking' }], + ['meta', { property: 'og:type', content: 'website' }], + ['meta', { property: 'og:title', content: 'EasyTier' }], + ['meta', { property: 'og:description', content: 'EasyTier official' }], + ['meta', { property: 'og:image', content: 'https://easytier.cn/easytier.png' }], + ['link', { rel: 'canonical', href: 'https://easytier.cn' }], + ], + themeConfig: { + siteTitle: 'EasyTier', + logo: '/easytier.png', + search: { + provider: 'local', + options: { + locales: { + root: { ...cnSearch }, + }, + }, + }, + socialLinks: [ + { + icon: 'github', + link: 'https://github.com/EasyTier/Easytier', + ariaLabel: 'Github', + }, + { + icon: 'gmail', + link: 'mailto:sunsijie@buaa.edu.cn', + ariaLabel: 'Email', + }, + { + icon: 'qq', + link: 'https://qm.qq.com/q/wFoTUChqZW', + ariaLabel: 'qq', + }, + { + icon: { + svg: '', + }, + link: '/#sponsor', + }, + ], + }, + locales: { + root: { label: '简体中文', ...cn }, + en: { label: 'English', ...en }, + }, + markdown: { + config: (md) => { + md.use(taskLists) + }, + }, + vue: { + template: { + compilerOptions: { isCustomElement: tag => tag === 'iconify-icon' }, + }, + }, + sitemap: { + hostname: 'https://easytier.cn', + }, +}) diff --git a/.vitepress/data/Twikoo.ts b/.vitepress/data/Twikoo.ts new file mode 100644 index 0000000..06998d3 --- /dev/null +++ b/.vitepress/data/Twikoo.ts @@ -0,0 +1,5 @@ +import type { TwikooData } from '@theojs/lumen' + +export const Twikoo_Data: TwikooData = { + envId: 'https://twikoo.easytier.cn', // 修改成部署的Twikoo地址 +} diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts new file mode 100644 index 0000000..08fc584 --- /dev/null +++ b/.vitepress/theme/index.ts @@ -0,0 +1,24 @@ +import type { EnhanceAppContext } from 'vitepress' + +import { baiduAnalytics, HomeUnderline, trackPageview } from '@theojs/lumen' + +import DefaultTheme from 'vitepress/theme' +import Layout from './layout.vue' + +import '@theojs/lumen/theme' +import '@theojs/lumen/doc-blocks-border' + +export default { + extends: DefaultTheme, + Layout, + + enhanceApp: (ctx: EnhanceAppContext) => { + const { app } = ctx + baiduAnalytics({ baiduId: '0afa8cd5bd78fd0c960f8af5dc6af333' }) + if (typeof window !== 'undefined') { + trackPageview('0afa8cd5bd78fd0c960f8af5dc6af333', window.location.href) + } + + app.component('Home', HomeUnderline) + }, +} as any diff --git a/.vitepress/theme/layout.vue b/.vitepress/theme/layout.vue new file mode 100644 index 0000000..ee1727e --- /dev/null +++ b/.vitepress/theme/layout.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/.vitepress/third_party/lumen b/.vitepress/third_party/lumen new file mode 160000 index 0000000..8f415d7 --- /dev/null +++ b/.vitepress/third_party/lumen @@ -0,0 +1 @@ +Subproject commit 8f415d724452fe08c4b40b1c6e7790a2b683ea15 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..fd1d9bf --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..95aaf4d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,71 @@ +{ + "eslint.experimental.useFlatConfig": true, + "prettier.enable": false, + "editor.formatOnSave": false, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.organizeImports": "never" + }, + "eslint.rules.customizations": [ + { + "rule": "style/*", + "severity": "off" + }, + { + "rule": "style/eol-last", + "severity": "error" + }, + { + "rule": "format/*", + "severity": "off" + }, + { + "rule": "*-indent", + "severity": "off" + }, + { + "rule": "*-spacing", + "severity": "off" + }, + { + "rule": "*-spaces", + "severity": "off" + }, + { + "rule": "*-order", + "severity": "off" + }, + { + "rule": "*-dangle", + "severity": "off" + }, + { + "rule": "*-newline", + "severity": "off" + }, + { + "rule": "*quotes", + "severity": "off" + }, + { + "rule": "*semi", + "severity": "off" + } + ], + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact", + "vue", + "html", + "markdown", + "json", + "jsonc", + "yaml", + "toml" + ], + "cSpell.words": [ + "easytier" + ], +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8cd40f3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright 2023 sunsijie + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c923dd --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +[简体中文](https://easytier.github.io/) | [English](https://easytier.github.io/en) diff --git a/assets/AstralET1.png b/assets/AstralET1.png new file mode 100644 index 0000000..d7e5433 Binary files /dev/null and b/assets/AstralET1.png differ diff --git a/assets/AstralET2.png b/assets/AstralET2.png new file mode 100644 index 0000000..b9112fc Binary files /dev/null and b/assets/AstralET2.png differ diff --git a/assets/AstralET3.png b/assets/AstralET3.png new file mode 100644 index 0000000..a1c1aec Binary files /dev/null and b/assets/AstralET3.png differ diff --git a/assets/AstralET4.png b/assets/AstralET4.png new file mode 100644 index 0000000..afcf922 Binary files /dev/null and b/assets/AstralET4.png differ diff --git a/assets/AstralET5.png b/assets/AstralET5.png new file mode 100644 index 0000000..75a8843 Binary files /dev/null and b/assets/AstralET5.png differ diff --git a/assets/HomoTier_AppGallery.png b/assets/HomoTier_AppGallery.png new file mode 100644 index 0000000..7e4327a Binary files /dev/null and b/assets/HomoTier_AppGallery.png differ diff --git a/assets/alipay.png b/assets/alipay.png new file mode 100644 index 0000000..3661eb5 Binary files /dev/null and b/assets/alipay.png differ diff --git a/assets/cn/aboutp2p1.png b/assets/cn/aboutp2p1.png new file mode 100644 index 0000000..c92b0e9 Binary files /dev/null and b/assets/cn/aboutp2p1.png differ diff --git a/assets/cn/aboutp2p2.png b/assets/cn/aboutp2p2.png new file mode 100644 index 0000000..ff0795d Binary files /dev/null and b/assets/cn/aboutp2p2.png differ diff --git a/assets/cn/aboutp2p3.png b/assets/cn/aboutp2p3.png new file mode 100644 index 0000000..64a796e Binary files /dev/null and b/assets/cn/aboutp2p3.png differ diff --git a/assets/cn/config.png b/assets/cn/config.png new file mode 100644 index 0000000..fce029f Binary files /dev/null and b/assets/cn/config.png differ diff --git a/assets/cn/manual.png b/assets/cn/manual.png new file mode 100644 index 0000000..2901795 Binary files /dev/null and b/assets/cn/manual.png differ diff --git a/assets/cn/portal.png b/assets/cn/portal.png new file mode 100644 index 0000000..ab77318 Binary files /dev/null and b/assets/cn/portal.png differ diff --git a/assets/cn/portal_config.png b/assets/cn/portal_config.png new file mode 100644 index 0000000..08208e0 Binary files /dev/null and b/assets/cn/portal_config.png differ diff --git a/assets/cn/running.png b/assets/cn/running.png new file mode 100644 index 0000000..09e7f91 Binary files /dev/null and b/assets/cn/running.png differ diff --git a/assets/cn/subnet-mapping.png b/assets/cn/subnet-mapping.png new file mode 100644 index 0000000..0969590 Binary files /dev/null and b/assets/cn/subnet-mapping.png differ diff --git a/assets/cn/subnet.png b/assets/cn/subnet.png new file mode 100644 index 0000000..08e3022 Binary files /dev/null and b/assets/cn/subnet.png differ diff --git a/assets/easytier.png b/assets/easytier.png new file mode 100644 index 0000000..26c6d8f Binary files /dev/null and b/assets/easytier.png differ diff --git a/assets/game-step1.png b/assets/game-step1.png new file mode 100644 index 0000000..8269025 Binary files /dev/null and b/assets/game-step1.png differ diff --git a/assets/game-step2.png b/assets/game-step2.png new file mode 100644 index 0000000..2de2548 Binary files /dev/null and b/assets/game-step2.png differ diff --git a/assets/game-step3.png b/assets/game-step3.png new file mode 100644 index 0000000..e4eba54 Binary files /dev/null and b/assets/game-step3.png differ diff --git a/assets/game-step4.png b/assets/game-step4.png new file mode 100644 index 0000000..ecb4116 Binary files /dev/null and b/assets/game-step4.png differ diff --git a/assets/game-step5.png b/assets/game-step5.png new file mode 100644 index 0000000..2ef374f Binary files /dev/null and b/assets/game-step5.png differ diff --git a/assets/game-step6.png b/assets/game-step6.png new file mode 100644 index 0000000..7bd0f05 Binary files /dev/null and b/assets/game-step6.png differ diff --git a/assets/gui-screenshot.png b/assets/gui-screenshot.png new file mode 100644 index 0000000..8870aa9 Binary files /dev/null and b/assets/gui-screenshot.png differ diff --git a/assets/image-1.png b/assets/image-1.png deleted file mode 100644 index c22e584..0000000 Binary files a/assets/image-1.png and /dev/null differ diff --git a/assets/image-2.png b/assets/image-2.png deleted file mode 100644 index daf2d34..0000000 Binary files a/assets/image-2.png and /dev/null differ diff --git a/assets/image-3.png b/assets/image-3.png deleted file mode 100644 index 809eaba..0000000 Binary files a/assets/image-3.png and /dev/null differ diff --git a/assets/image-4.png b/assets/image-4.png index 46166fa..cf1e990 100644 Binary files a/assets/image-4.png and b/assets/image-4.png differ diff --git a/assets/image-5.png b/assets/image-5.png index 1093ea1..240e8bb 100644 Binary files a/assets/image-5.png and b/assets/image-5.png differ diff --git a/assets/image-6.png b/assets/image-6.png deleted file mode 100644 index 381d1fe..0000000 Binary files a/assets/image-6.png and /dev/null differ diff --git a/assets/image-7.png b/assets/image-7.png deleted file mode 100644 index c7604b5..0000000 Binary files a/assets/image-7.png and /dev/null differ diff --git a/assets/image.png b/assets/image.png deleted file mode 100644 index deefbb8..0000000 Binary files a/assets/image.png and /dev/null differ diff --git a/assets/manage-step1.png b/assets/manage-step1.png new file mode 100644 index 0000000..fbfade9 Binary files /dev/null and b/assets/manage-step1.png differ diff --git a/assets/manage-step2.png b/assets/manage-step2.png new file mode 100644 index 0000000..467a426 Binary files /dev/null and b/assets/manage-step2.png differ diff --git a/assets/manage-step3.png b/assets/manage-step3.png new file mode 100644 index 0000000..12acddd Binary files /dev/null and b/assets/manage-step3.png differ diff --git a/assets/manage-step4.png b/assets/manage-step4.png new file mode 100644 index 0000000..600e81f Binary files /dev/null and b/assets/manage-step4.png differ diff --git a/assets/manage-step5.png b/assets/manage-step5.png new file mode 100644 index 0000000..e8472c8 Binary files /dev/null and b/assets/manage-step5.png differ diff --git a/assets/manage-step6.png b/assets/manage-step6.png new file mode 100644 index 0000000..bb52653 Binary files /dev/null and b/assets/manage-step6.png differ diff --git a/assets/manage-step7.png b/assets/manage-step7.png new file mode 100644 index 0000000..52fb36b Binary files /dev/null and b/assets/manage-step7.png differ diff --git a/assets/manage-step8.png b/assets/manage-step8.png new file mode 100644 index 0000000..e7a6061 Binary files /dev/null and b/assets/manage-step8.png differ diff --git a/assets/manage-step9.png b/assets/manage-step9.png new file mode 100644 index 0000000..0269f6e Binary files /dev/null and b/assets/manage-step9.png differ diff --git a/assets/web-device-config.png b/assets/web-device-config.png new file mode 100644 index 0000000..ce5b4cc Binary files /dev/null and b/assets/web-device-config.png differ diff --git a/assets/web-device-list.png b/assets/web-device-list.png new file mode 100644 index 0000000..8abbeee Binary files /dev/null and b/assets/web-device-list.png differ diff --git a/assets/web-device-run-network.png b/assets/web-device-run-network.png new file mode 100644 index 0000000..7ce5c4f Binary files /dev/null and b/assets/web-device-run-network.png differ diff --git a/assets/web-homepage.png b/assets/web-homepage.png new file mode 100644 index 0000000..e8626b3 Binary files /dev/null and b/assets/web-homepage.png differ diff --git a/assets/wechat.png b/assets/wechat.png new file mode 100644 index 0000000..9183f1e Binary files /dev/null and b/assets/wechat.png differ diff --git a/assets/win-service.png b/assets/win-service.png new file mode 100644 index 0000000..c1d45a2 Binary files /dev/null and b/assets/win-service.png differ diff --git a/en/guide/community-and-contribution.md b/en/guide/community-and-contribution.md index 2d3514e..4017ff9 100644 --- a/en/guide/community-and-contribution.md +++ b/en/guide/community-and-contribution.md @@ -1,3 +1,13 @@ + + # Community and Contribution - -We welcome and encourage community contributions! If you want to get involved, please submit a [GitHub PR](https://github.com/KKRainbow/EasyTier/pulls). Detailed contribution guidelines can be found in [CONTRIBUTING.md](https://github.com/KKRainbow/EasyTier/blob/main/CONTRIBUTING.md). \ No newline at end of file + +We welcome and encourage community contributions! If you want to get involved, please submit a [GitHub PR](https://github.com/EasyTier/EasyTier/pulls). + +Detailed contribution guidelines can be found in the [Contributing](https://github.com/EasyTier/EasyTier/blob/main/CONTRIBUTING.md) document. + +## Star History + + diff --git a/en/guide/config/acl.md b/en/guide/config/acl.md new file mode 100644 index 0000000..fc7120a --- /dev/null +++ b/en/guide/config/acl.md @@ -0,0 +1,220 @@ +# Easytier ACL Feature Guide 🛡️ + +## 📖 Table of Contents + +1. [Core Concepts](#-core-concepts) +2. [How It Works](#-how-it-works) +3. [Configuration Details](#-configuration-details) +4. [Common Use Case Examples](#-common-use-case-examples) +5. [Best Practices and Notes](#-best-practices-and-notes) + +--- + +## 🧠 Core Concepts + +Understanding the following key concepts is essential for configuring ACL: + +- **ACL (Access Control List)**: A set of rules used to allow or deny network traffic. Easytier's 2.4.0 update introduced IP-based ACL control, and version 2.4.3 added a zero-trust ACL mechanism based on identity groups, making policies more flexible. +- **Chain**: A collection of rules. Chains have types; for example, `chain_type = 1` represents an inbound chain, processing traffic sent to the local machine. +- **Rule**: Defines traffic matching conditions (such as protocol, port, source/target groups) and the action to take upon a match (allow or deny). +- **Group**: A logical identity tag. A node (server, computer, etc.) can declare that it belongs to one or more groups (e.g., `admin`, `database`). +- **Group Declaration**: A node needs to know in advance all the group names it might communicate with and their corresponding shared secrets. The secret is used to verify whether the group membership claimed by other nodes is valid. +- **Priority**: Rules are matched in descending order of priority value. Once traffic matches a rule, its action is executed, and subsequent matching stops. + +--- + +## 🔧 Configuration Details + +ACL configuration must be added to Easytier's configuration file `config.yaml`. + +### 1. Define Groups and Secrets + +This is the most critical step. Each node needs to declare which groups it belongs to in its configuration and configure the shared secrets for all related groups. + +```yaml +# This section defines the groups this node will join (for generating identity proof) +[acl.acl_v1.group] +members = ["admin", "web-server"] # This node's identity: both an administrator and a web server + +# This section defines all groups "known" to this node and their secrets (for verifying peer identities) +[[acl.acl_v1.group.declares]] +group_name = "admin" +group_secret = "super-secret-admin-key" # Please use a more complex key! + +[[acl.acl_v1.group.declares]] +group_name = "web-server" +group_secret = "web-server-secret-key" + +[[acl.acl_v1.group.declares]] +group_name = "database" +group_secret = "database-secret-key" + +[[acl.acl_v1.group.declares]] +group_name = "guest" +group_secret = "guest-secret-key" +``` + +> **⚠️ Important Notes**: +> - `members`: Defines this node's identity. +> - `declares`: Acts as the node's "address book" and must include definitions for all groups in the network that might communicate. The `declares` section must be completely consistent across all nodes. +> - `group_secret`: The cornerstone of security. Must use a strong, unique secret, and all nodes must share the same secret definitions. + +--- + +### 2. Configure Rule Chains + +Rule chains determine how traffic is handled. + +```yaml +# Define an inbound chain +[[acl.acl_v1.chains]] +name = "my_acl_policy" # Chain name +chain_type = 1 # 0: Unspecified, 1: Inbound chain (processes traffic sent to this machine), 2: Outbound (processes traffic sent from this machine), 3: Forwarding (subnet proxy) +description = "My security policy" +enabled = true # Enable this chain +default_action = 2 # Default action: 1(Allow) 2(Deny) +``` + +--- + +### 3. Configure Rules + +Rules are the core of the policy and are defined within chains. + +```yaml +# List of rules within the chain defined above +[[acl.acl_v1.chains.rules]] +name = "allow_admin_rdp" +description = "Allow administrators to RDP to this machine" +priority = 1000 # Priority, higher number means higher priority (0-65535) +action = 1 # Action: 0(No operation) 1(Allow) 2(Deny) +source_groups = ["admin"] # Rule match: source device must belong to the admin group +protocol = 1 # Protocol: 0(Unspecified) 1(TCP) 2(UDP) 3(ICMP) 4(ICMPv6) 5(Any) +ports = ["3389"] # Local allowed port: 3389 (RDP) +enabled = true # Enable this rule + +[[acl.acl_v1.chains.rules]] +name = "deny_guest_to_db" +description = "Deny guests access to the database" +priority = 900 # Lower priority +action = 2 # Action: 0(No operation) 1(Allow) 2(Deny) +source_groups = ["guest"] # Rule match: source device must belong to the guest group +destination_groups = ["database"] # Match when the target device belongs to the database group +enabled = true # Enable the rule +protocol = 1 # TCP protocol +ports = [] # If empty, matches all ports +source_ips = [] # If empty, matches all source IP ranges; format `10.144.144.2/32` +destination_ips = [] # If empty, matches all destination IP ranges +source_ports = [] # If empty, matches all source ports +rate_limit = 0 # Rate limit (bps), 0 = unlimited +burst_limit = 0 # Maximum burst bandwidth (bps) +stateful = true # Enable connection tracking +``` + +--- + +## 🧪 Common Use Case Examples + +### Use Case 1: Minimal Security Group - "Same Secret Allows Access" + +**Goal**: Implement a private network where devices with the same secret can communicate; devices without the secret cannot join. + +**Configuration**: + +```yaml +[acl.acl_v1.group] +members = ["my-net"] # All devices join the same group + +[[acl.acl_v1.group.declares]] +group_name = "my-net" +group_secret = "my-net-secret-key" # All devices use the same secret + +[[acl.acl_v1.chains]] +name = "default_inbound" +chain_type = 1 +enabled = true +default_action = 2 # Deny by default + +# Core rule: Allow all communication within the group +[[acl.acl_v1.chains.rules]] +name = "allow_whole_group" +description = "Allow all traffic within the group" +priority = 1000 +action = 1 +source_groups = ["my-net"] # Source is a group member +destination_groups = ["my-net"] # Destination is a group member +protocol = 1 +enabled = true +``` + +--- + +### Use Case 2: Three-Tier Network Architecture + +**Goal**: Simulate a classic Web-DB architecture, only allowing web servers to access specific ports on the database. + +**Node Configuration**: + +- Web Server: `members = ["web"]` +- Database Server: `members = ["db"]` +- Admin Machine: `members = ["admin"]` + +**ACL Rules on the Database Server**: + +```yaml +[[acl.acl_v1.chains]] +name = "db_server_policy" +chain_type = 1 +enabled = true +default_action = 2 # Deny all connections by default + +# Rule 1: Allow web servers to access the database port +[[acl.acl_v1.chains.rules]] +name = "allow_web_to_mysql" +description = "Allow the web group to access MySQL" +priority = 100 +action = 1 +source_groups = ["web"] +destination_groups = ["db"] # Target is this machine (db group) +protocol = 1 +ports = ["3306"] # Only open MySQL port +enabled = true + +# Rule 2: Allow admins access to all ports (e.g., for management) +[[acl.acl_v1.chains.rules]] +name = "allow_admin_to_all" +description = "Allow administrators access to all services" +priority = 110 # Higher priority than the previous rule +action = 1 +source_groups = ["admin"] +destination_groups = ["db"] # Target is this machine (db group) +protocol = 1 +enabled = true +``` + +--- + +## ✅ Best Practices and Notes + +1. **Secret Management**: + - **Importance**: `group_secret` is the foundation of security. If leaked, attackers can freely join your network. + - **Recommendation**: Use a password generator to create long and complex secrets, and rotate them periodically. + - **Secure Storage**: Do not commit the configuration file to public code repositories. + +2. **Configuration Consistency**: + - Ensure the `[[acl.acl_v1.group.declares]]` section is completely consistent (same group names and secrets) across all nodes in the network. Otherwise, group verification will fail, and the network will not function correctly. + +3. **Debugging Tips**: + - Start with a relaxed policy (`default_action = 1`) and use logs to confirm network connectivity. + - Gradually add deny rules (`action = 2`) to restrict access. + - Finally, change the `default_action` to deny (`2`) to implement a "whitelist" model. + - Make full use of the `description` field to add clear comments for each rule, facilitating future maintenance. + - The `easytier-cli acl stats` command can be used to view ACL statistics. + +4. **Operation Order**: + - After modifying ACL configuration, the Easytier process usually needs to be restarted to take effect. + - Commands like `easytier-core -d --tcp-whitelist port number` can automatically generate simple port whitelist rules, which can serve as a starting point for learning. + +--- + +We hope this document helps you better understand and use Easytier's ACL features! If you have any questions, welcome to discuss them in the community. 🎉 \ No newline at end of file diff --git a/en/guide/contact.md b/en/guide/contact.md index 232c6e7..14dc2ac 100644 --- a/en/guide/contact.md +++ b/en/guide/contact.md @@ -1,6 +1,6 @@ -# Contact - -- Ask questions or report problems: [GitHub Issues](https://github.com/KKRainbow/EasyTier/issues) -- Discussion and exchange: [GitHub Discussions](https://github.com/KKRainbow/EasyTier/discussions) +# Contact Information + +- Ask questions or report issues: [GitHub Issues](https://github.com/EasyTier/EasyTier/issues) +- Discussion and communication: [GitHub Discussions](https://github.com/EasyTier/EasyTier/discussions) - QQ Group: [949700262](https://qm.qq.com/q/LDxBN5L3kA) -- Telegram: https://t.me/easytier \ No newline at end of file +- Telegram: https://t.me/easytier diff --git a/en/guide/download.md b/en/guide/download.md new file mode 100644 index 0000000..798a950 --- /dev/null +++ b/en/guide/download.md @@ -0,0 +1,212 @@ +--- +home: hello +--- + + + +# Download { #download } + +You can directly go to the [GitHub Release page](https://github.com/EasyTier/EasyTier/releases) to view the download links for all versions, or use the table below to find the version that suits you. + +The command line program package includes four executables: + +- `easytier-core`: The core program of EasyTier +- `easytier-cli`: EasyTier management program, after starting easytier-core, you can use easytier-cli to view virtual network information +- `easytier-web`: Used for self-hosting the EasyTier Web console backend, generally no need to self-host, you can use the official Web console +- `easytier-web-embed`: Same functionality as `easytier-web`, but includes the Web frontend. + +## EasyTier v{{ version }} { #latest } + +- GitHub Acceleration +
+ +
+ +- Filter by Operating System +
+ +
+ +- Filter by Hardware Architecture +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Operating System Hardware Architecture GUI Program CLI Program Notes
{{ pkg.os }} {{ pkg.arch }} + +{{format}} + + + +{{format}} + + +{{ pkg.comment }} +
diff --git a/en/guide/faq.md b/en/guide/faq.md new file mode 100644 index 0000000..d84a111 --- /dev/null +++ b/en/guide/faq.md @@ -0,0 +1,39 @@ +# Frequently Asked Questions {#faq} + +--- + +**Q: Windows 7 cannot create a network, the program crashes or reports an error saying it cannot create a virtual network?** + +**A:** Windows 7 requires SP1 or later versions, and the following patches must be installed: +- [KB3063858](https://www.microsoft.com/en-us/download/details.aspx?id=47409) +- [KB4474419](https://www.catalog.update.microsoft.com/search.aspx?q=KB4474419) + +--- + +**Q: The Linux command-line help is in English, how can I switch to Chinese?** + +**A:** Set the environment variable `LANG=zh_CN`. Use the following command: + +```bash +export LANG=zh_CN +``` + +--- + +**Q: What should I do if TunError is displayed after startup?** + +**A:** Please ensure that the TUN driver is correctly loaded and the `/dev/net/tun` file exists. If running in Docker, ensure privileged mode is enabled. The command to load the TUN driver on Linux is as follows: + +```bash +modprobe tun +mkdir -p /dev/net +sudo mknod /dev/net/tun c 10 200 +``` + +--- + +**Q: What should I do if the error `Address already in use` is reported after startup?** + +**A:** This may be due to port conflicts. Please check whether port 11010 or the port specified by the startup parameter (e.g., `-l tcp:12345`) is occupied by other programs. + +--- diff --git a/en/guide/gui/astral-game.md b/en/guide/gui/astral-game.md new file mode 100644 index 0000000..f859207 --- /dev/null +++ b/en/guide/gui/astral-game.md @@ -0,0 +1,29 @@ +# AstralGame - Game Networking Tool + +[![GitHub](https://img.shields.io/badge/GitHub-AstralET-blue)](https://github.com/ldoubil/astral) +[![AstralGame](https://img.shields.io/badge/wiki-AstralGame-blue)](https://astral.fan/) + +## Preview +![manage-step1](/assets/AstralET1.png) +![manage-step1](/assets/AstralET2.png) +![manage-step1](/assets/AstralET3.png) +![manage-step1](/assets/AstralET4.png) +![manage-step1](/assets/AstralET5.png) + +## Usage Tutorial +- https://astral.fan/quick-start/what-is-astral/ + +## Introduction + +AstralGame is a lightweight game networking tool developed based on **Flutter** and **EasyTier**, designed to provide players with a simple and efficient online gaming experience. + +## Features + +- **Built-in EasyTier**: Directly integrates EasyTier into AstralGame, eliminating the need for additional installations and leaving no background processes. +- **Plug-and-Play**: Launch the app when connecting, close it when done—simple and convenient. +- **Actively Maintained**: Regular updates ensure quick issue resolution and continuous feature optimization (a productivity boost during work breaks). + +## Contact & Support + +- **QQ Group**: [Join Our QQ Community](https://qm.qq.com/q/r4VsExDDt6) +- **GitHub Issues**: [Report Bugs or Suggest Features](https://github.com/ldoubil/astral/issues) \ No newline at end of file diff --git a/en/guide/gui/basic.md b/en/guide/gui/basic.md new file mode 100644 index 0000000..f437ea5 --- /dev/null +++ b/en/guide/gui/basic.md @@ -0,0 +1,11 @@ +# Public Server Networking + +The GUI defaults to using official shared nodes for networking, which is convenient for friends without public IPs. In most cases, P2P tunneling can be successful. If P2P tunneling fails, the bandwidth between nodes may be relatively low. + +The configuration method is shown in the figure. + +![Configuration Interface](/assets/cn/config.png) + +After the configuration is complete, click the Run Network button. The interface after the network runs successfully is shown in the figure. + +![Running](/assets/cn/running.png) diff --git a/en/guide/gui/easytier-game.md b/en/guide/gui/easytier-game.md new file mode 100644 index 0000000..33c4634 --- /dev/null +++ b/en/guide/gui/easytier-game.md @@ -0,0 +1,42 @@ +# [EasyTier Game Launcher](https://github.com/EasyTier/EasytierGame) + +## Introduction + +EasyTier Game Launcher is developed with `nuxt3`, `typescript`, `rust`, and `tauri`. It has a simple interface and comes with the latest EasyTier core. When playing multiplayer games, it provides the most comfortable experience both psychologically and practically. It also supports custom configuration file startup to meet various needs. + +## Download + +GitHub Releases: [https://github.com/EasyTier/EasytierGame/releases](https://github.com/EasyTier/EasytierGame/releases) + +- Only green zip packages are available. Personally, I don't like installers that write to the registry. Just extract and use, keeping the directory clean and tidy. + +![game-step1](/assets/game-step1.png) + +## Tutorial + +- For the first use, enter a "hostname" and click to start the connection. Later, you can create your own server or use servers provided by kind community members. + ![game-step2](/assets/game-step2.png) + +![game-step3](/assets/game-step3.png) + +- There are some special configurations in the advanced options that you can choose from. + ![game-step4](/assets/game-step4.png) + +- If your needs are still not met, you can use a configuration file to start. For details on how to configure, refer to the documentation [Configuration File](/guide/network/config-file.html). + ![game-step5](/assets/game-step5.png) + +- After upgrading the EasyTier core, you can click the update plugin button to update. However, you need to use a VPN. If you cannot update, you can get the update from the community. + ![game-step6](/assets/game-step6.png) + +## Features + +- Developed based on the EasyTier networking tool with a clear and simple interface. +- Built-in "Update" button. When the EasyTier networking tool releases a new version, just click update (requires VPN). +- For the first use, enter a "hostname" and click to start the connection. Later, you can create your own server or use community servers. +- Simple configuration with advanced features, also supports custom configuration file startup. +- **WinIPBroadcast** is enabled by default, no longer afraid of not finding rooms when connecting (e.g., Borderlands 3). +- Tested with **Elden Ring learning version**, **Borderlands 3**, **Deep Rock Galactic**, **Monster Hunter World**, etc., all can be played stably. + +## System Support + +Supports Windows 11, Windows 10, and Windows 7. diff --git a/en/guide/gui/easytier-manager.md b/en/guide/gui/easytier-manager.md new file mode 100644 index 0000000..4675102 --- /dev/null +++ b/en/guide/gui/easytier-manager.md @@ -0,0 +1,73 @@ +# [EasyTier Manager](https://github.com/xlc520/easytier-manager) + +## Download + +GitHub Releases: [https://github.com/xlc520/easytier-manager/releases](https://github.com/xlc520/easytier-manager/releases) + +#### Package Descriptions + +- `exe`: Installer, must be installed before use +- `zip`: Portable, extract and use directly +- `easytier-manager-win_2.0.0.exe`: Universal installer for 64-bit and 32-bit Windows systems +- `easytier-manager-win-x64_2.0.0.exe`: 64-bit Windows system installer +- `easytier-manager-win-ia32_2.0.0.exe`: 32-bit Windows system installer +- `easytier-manager-win7-x64_2.0.0.exe`: 64-bit Windows 7 system installer +- `tar.gz` `deb` `rpm` `AppImage`: For Linux systems (not yet tested) + +## Tutorial + +- **1. [Important] Settings page: Check if the kernel exists, if not, download the kernel, then install it, and check again if the kernel exists** (only needed for first use, subsequent confirmations can run directly) + +![manage-step1](/assets/manage-step1.png) + +![manage-step2](/assets/manage-step2.png) + +- 2. Configuration page: Create new network configurations, providing both direct code editing and form filling methods + +![manage-step3](/assets/manage-step3.png) + +![manage-step4](/assets/manage-step4.png) + +![manage-step5](/assets/manage-step5.png) + +![manage-step6](/assets/manage-step6.png) + +- 3. Workspace (home page): Run specified configurations + +![manage-step7](/assets/manage-step7.png) + +- 4. [Optional] After successful networking, if the connection is fine, you can exit the manager. The core program will run in the background (right-click the tray icon to "Exit") + +- 5. [Optional] On the configuration page, install specified configurations as system services + +![manage-step8](/assets/manage-step8.png) + +![manage-step9](/assets/manage-step9.png) + +## Introduction + +EasyTier Manager integrates Vue3 + Vite5 + Electron33 + Element-Plus. It is a free and open-source network manager based on `element-plus`. It is developed using the latest mainstream technologies such as `vue3`, `vite5`, `TypeScript`, etc. + +## Features + +- **Memory Usage**: After successful networking, you can directly exit the manager without affecting the network, so it won't occupy memory or cause memory leaks due to various issues +- **Multi-Configuration Startup**: Supports running and managing multiple network configurations +- **System Service Installation**: One-click installation as a system service with visual interface, auto-start on boot +- **Visual Configuration Addition**: Provides form-based visual addition of network configurations, simple and convenient +- **Visual Log Viewing**: View logs of current network configurations on the home page +- **One-Click Download and Install**: One-click download and installation of the kernel with built-in accelerated sources, no manual download required +- **Latest Tech Stack**: Developed using cutting-edge frontend technologies like Electron33/Vue3/vite5 +- **TypeScript**: Application-level JavaScript language +- **Internationalization**: Built-in comprehensive internationalization solution + +## Bug Reports & Suggestions + +> Tending towards stability, may not develop new features, only fix vulnerabilities and such + +You can check [TODO](https://github.com/xlc520/easytier-manager/blob/master/TODO.md) to see if it's already recorded to avoid duplication + +[Submit Bug | Feature Request](https://github.com/xlc520/easytier-manager/issues/new/choose) + +## System Support + +Theoretically supports Windows 11, Windows 10, and Windows 7. diff --git a/en/guide/gui/index.md b/en/guide/gui/index.md new file mode 100644 index 0000000..e0e5300 --- /dev/null +++ b/en/guide/gui/index.md @@ -0,0 +1,9 @@ +# Graphical User Interface (GUI) Networking + +The graphical interface program can also be downloaded from the GitHub Release page, with the prefix easytier-gui. + +Note that after installation on MacOS, you need to execute the following command in the terminal, otherwise, it will mistakenly report that the file is damaged. + +```bash +xattr -c /Applications/easytier-gui.app +``` diff --git a/en/guide/gui/manual.md b/en/guide/gui/manual.md new file mode 100644 index 0000000..d32f561 --- /dev/null +++ b/en/guide/gui/manual.md @@ -0,0 +1,7 @@ +# Manual Networking + +EasyTier does not distinguish between client and server, and is completely decentralized. New nodes only need to establish a connection with any node in the virtual network to join the network. The configuration method is shown in the figure below. + +![Manual Networking](/assets/cn/manual.png) + +Note: After entering the node IP, you need to click on the list item to confirm. After confirmation, the node address will be displayed in card format. diff --git a/en/guide/gui/subnet_proxy.md b/en/guide/gui/subnet_proxy.md new file mode 100644 index 0000000..a635ddd --- /dev/null +++ b/en/guide/gui/subnet_proxy.md @@ -0,0 +1,13 @@ +# Subnet Proxy + +By setting up a subnet proxy, you can connect the local area network and the virtual local area network. + +Assuming the devices at home are in the 192.168.1.0/24 subnet, and you want to access any device at home from the company, you can start an EasyTier node at home and add a subnet proxy for 192.168.1.0/24. No additional configuration is needed on the company's devices; simply connect to the home node successfully, and you can access any device at home. + +![Subnet Proxy Configuration](/assets/cn/subnet.png) + +Note: After entering the subnet, you need to click on the list item to confirm. After successful confirmation, the subnet will be displayed in card format. + +Subnet proxy can perform subnet mapping. + +![Subnet Proxy Configuration](/assets/cn/subnet-mapping.png) diff --git a/en/guide/gui/vpn_portal.md b/en/guide/gui/vpn_portal.md new file mode 100644 index 0000000..cca4cab --- /dev/null +++ b/en/guide/gui/vpn_portal.md @@ -0,0 +1,11 @@ +# WireGuard Access + +Each node in EasyTier can act as a WireGuard server, allowing mobile devices such as Android and iOS to easily access devices in the virtual LAN. + +Configuration method as shown in the figure. + +![Wireguard Portal Config](/assets/cn/portal.png) + +Click the "Show WireGuard Portal Configuration" button on the network success page to view the client configuration file. Import this configuration file into a third-party client on your phone to allow the phone to access the virtual LAN. + +![Client Config](/assets/cn/portal_config.png) diff --git a/en/guide/installation.md b/en/guide/installation.md index f5052d2..819760c 100644 --- a/en/guide/installation.md +++ b/en/guide/installation.md @@ -1,19 +1,110 @@ -# Installation {#installation} +# Installation (Command Line Program) {#installation} -1. **Download the precompiled binary file** - Visit the [GitHub Release page](https://github.com/KKRainbow/EasyTier/releases) to download the binary file suitable for your operating system. Release includes both command-line programs and GUI programs in the compressed package. +This section only introduces installation methods. Please read the [Quick Networking](/en/guide/network/quick-networking) documentation to understand parameter meanings and usage methods. -2. **Install via crates.io** +## Installation Methods + +1. **Manual Download of Command Line Program** + + Visit the [⬇️Download Page](./download) to download the EasyTier command line program suitable for your operating system and hardware architecture. After downloading, it's a ZIP compressed package that can be used directly after extraction. ::: code-group - ```sh [cargo] - cargo install easytier + + ```bash [Linux / MacOS / FreeBSD] + ./easytier-core --version ``` + + ```powershell [Windows] + .\easytier-core.exe --version + ``` + ::: -3. **Install from source code** - ::: code-group + *** + +2. **DockerHub** + + [DockerHub Image Address](https://hub.docker.com/r/easytier/easytier) + + ```sh [docker] + # docker.io image + docker pull easytier/easytier:latest + docker run -d --privileged --network host easytier/easytier:latest + + # Domestic users can use DaoCloud image + docker pull m.daocloud.io/docker.io/easytier/easytier:latest + docker run -d --privileged --network host m.daocloud.io/docker.io/easytier/easytier:latest + ``` + + Please continue reading the [Quick Networking](/en/guide/network/quick-networking) documentation to understand parameter meanings and usage methods. + + *** + +3. **Install via Docker Compose** + + ::: details docker-compose.yml + + ```yaml [docker-compose.yml] + services: + watchtower: # Used to automatically update easytier image, delete this part if not needed + image: containrrr/watchtower + container_name: watchtower + restart: unless-stopped + environment: + - TZ=Asia/Shanghai + - WATCHTOWER_NO_STARTUP_MESSAGE + volumes: + - /var/run/docker.sock:/var/run/docker.sock + command: --interval 3600 --cleanup --label-enable + easytier: + image: easytier/easytier:latest # Domestic users can use m.daocloud.io/docker.io/easytier/easytier:latest + hostname: easytier + container_name: easytier + labels: + com.centurylinklabs.watchtower.enable: 'true' + restart: unless-stopped + network_mode: host + cap_add: + - NET_ADMIN + - NET_RAW + environment: + - TZ=Asia/Shanghai + devices: + - /dev/net/tun:/dev/net/tun + volumes: + - /etc/easytier:/root + - /etc/machine-id:/etc/machine-id:ro # Map host machine code + command: -d --network-name --network-secret -p tcp://public.easytier.cn:11010 + ``` + + ::: + + *** + +4. **One-Click Installation Script (Linux Only)** + + Note: The one-click script depends on `unzip`, please download and install it in advance. + + ```bash + wget -O /tmp/easytier.sh "https://raw.githubusercontent.com/EasyTier/EasyTier/main/script/install.sh" && sudo bash /tmp/easytier.sh install --gh-proxy https://ghfast.top/ + ``` + + After the script executes successfully, EasyTier's binary programs will be installed in the `/opt/easytier` directory, and the configuration file is located at `/opt/easytier/config/default.conf`. + + The configuration file can be generated through the [Configuration File Generator](https://easytier.cn/web/index.html#/config_generator). + + EasyTier will be registered as a system service and can be managed with the following commands: + + ```bash + systemctl start easytier@default + ``` + + *** + +5. **Install from Source** + ```sh [cargo] - cargo install --git https://github.com/KKRainbow/EasyTier.git + cargo install --git https://github.com/EasyTier/EasyTier.git easytier ``` - ::: \ No newline at end of file + + Source installation requires Rust environment and LLVM installation. diff --git a/en/guide/installation_gui.md b/en/guide/installation_gui.md new file mode 100644 index 0000000..e7c0784 --- /dev/null +++ b/en/guide/installation_gui.md @@ -0,0 +1,42 @@ +# Installation (Graphical Interface) {#installation_gui} + +## EasyTier GUI + +Visit the [⬇️ Download Page](./download) to download the graphical interface program suitable for your operating system and hardware architecture, and install it directly. + +After successful installation, you can refer to the [Public Server Group Network](/guide/gui/basic) documentation to understand how to use the graphical interface tools. + +Please note that EasyTier GUI relies on WebView, and the following common issues may occur: + +1. On lower versions of Windows, WebView download may fail due to domestic network environment issues, making it impossible to download WebView components. Please manually install [WebView2](https://developer.microsoft.com/en-US/microsoft-edge/webview2/) or [Edge](https://www.microsoft.com/en-us/edge) browser. + +2. On lower versions of Android, styles may be lost, and display may be chaotic. Please manually update the WebView component in the app store. + +## Third-party Graphical Interfaces + +### [EasyTier Game (Windows)](/guide/gui/easytier-game) + +EasyTierGame is a game networking launcher developed using nuxt3, typescript, rust, and tauri. It features a simple interface and includes the latest version of the EasyTier core. When playing games, it provides the most comfortable experience both psychologically and in usage, while supporting custom configuration file launches to meet various needs. + +--- + +### [EasyTier Manager (Windows)](/guide/gui/easytier-manager) + +EasyTier Manager is a desktop application used to manage the EasyTier core. It provides visualized operations for adding, modifying, and deleting EasyTier configuration files. + +- Supports one-click start and stop of group networks through the interface. +- Supports modification of all existing core parameters, with all new and modified operations supporting both interface-based forms and text editor operations. +- Supports viewing current running logs through the interface. +- Supports one-click download of any version of the core (Note: Many parameters and features of older versions are not supported). + +--- + +### [Astral Game (Windows / Android / Linux)](/guide/gui/astral-game) + +AstralGame is a cross-platform network application based on EasyTier, providing simple and easy-to-use P2P network connections and VPN services. Built with Flutter, its modern interface allows users to easily create and manage virtual networks. + +--- + +### [luci-app-easytier (OpenWrt)](https://github.com/EasyTier/luci-app-easytier) + +EasyTier's OpenWrt plugin provides methods for installing and configuring the EasyTier core on OpenWrt routers. The plugin supports completing the installation, configuration, and management of the EasyTier core within OpenWrt's LuCI interface. Users can conveniently configure the core, view running logs, restart the core, and perform other operations through the LuCI interface. diff --git a/en/guide/introduction.md b/en/guide/introduction.md index 739bb15..94a65ce 100644 --- a/en/guide/introduction.md +++ b/en/guide/introduction.md @@ -1,20 +1,38 @@ # Introduction -EasyTier is a simple, safe and decentralized VPN networking solution implemented with the Rust language and Tokio framework. - -![alt text](/assets/image-5.png) -![alt text](/assets/image-4.png) - -## Features - -- **Decentralized**: No need to rely on centralized services, nodes are equal and independent. -- **Safe**: Use WireGuard protocol to encrypt data. -- **High Performance**: Full-link zero-copy, with performance comparable to mainstream networking software. -- **Cross-platform**: Supports MacOS/Linux/Windows, will support IOS and Android in the future. The executable file is statically linked, making deployment simple. -- **Networking without public IP**: Supports networking using shared public nodes, refer to [Configuration Guide](/guide/network/without-public-ip.md) -- **NAT traversal**: Supports UDP-based NAT traversal, able to establish stable connections even in complex network environments. -- **Subnet Proxy (Point-to-Network)**: Nodes can expose accessible network segments as proxies to the VPN subnet, allowing other nodes to access these subnets through the node. -- **Smart Routing**: Selects links based on traffic to reduce latency and increase throughput. -- **TCP Support**: Provides reliable data transmission through concurrent TCP links when UDP is limited, optimizing performance. -- **High Availability**: Supports multi-path and switches to healthy paths when high packet loss or network errors are detected. -- **IPv6 Support**: Supports networking using IPv6. \ No newline at end of file +EasyTier is a simple, secure, decentralized tool for intranet penetration and remote networking, suitable for various scenarios such as remote work, remote access, and game acceleration. It requires no public IP and no complex configuration, enabling secure interconnection between devices in different locations with ease. + +The software can be used via command line or graphical interface. It is ready to use after download, with no additional dependencies. + +- [🛠️ CLI Installation Page](./installation) provides methods for installing the command-line tool. +- [🖥️ GUI Installation Page](./installation_gui) provides methods for installing the graphical interface tool. +- [⬇️ Download Page](./download) provides the latest EasyTier download links. + +## Applicable Scenarios + +- **Remote Work**: Make computers at the company, home, and remote locations communicate as if they are on the same local network. +- **Remote Access**: Securely access home NAS, servers, or other devices anytime, anywhere. +- **Game Acceleration**: Build a virtual local area network to enjoy multiplayer games. +- **IoT Networking**: Securely interconnect devices distributed across different locations. + +## Core Features + +- **Decentralized**: No reliance on central servers; all nodes are equal and independent, capable of forwarding and networking. +- **Secure Encryption**: Supports WireGuard and AES-GCM encryption to ensure data security. +- **Cross-Platform**: Supports MacOS, Linux, Windows, FreeBSD, Android, and will support iOS in the future. +- **Networking Without Public IP**: Enables networking using shared public nodes, see [Configuration Guide](/guide/network/networking-without-public-ip). +- **NAT Traversal**: Supports UDP NAT traversal for stable connections in complex network environments. +- **Intelligent Routing**: Automatically selects the best link to reduce latency and increase throughput. +- **High Availability**: Supports multipath and automatically switches to healthy links to improve stability. + +## Advanced Features + +- **KCP / QUIC Proxy**: Converts TCP traffic to KCP / QUIC protocol, improving transmission latency and stability in high UDP packet loss environments. +- **Non-Privileged Mode**: Supports running under non-privileged users, avoiding the need for root permissions (only as an accessed endpoint). +- **WireGuard Access**: Supports WireGuard client access to the EasyTier network. + +## Graphical Interface (GUI) + +EasyTier provides a simple and user-friendly graphical interface, suitable for beginners to get started quickly. + +EasyTier GUI Screenshot diff --git a/en/guide/license.md b/en/guide/license.md index 4b4d29e..91b9c35 100644 --- a/en/guide/license.md +++ b/en/guide/license.md @@ -1,3 +1,3 @@ # License - -EasyTier is released under the [Apache License 2.0](https://github.com/KKRainbow/EasyTier/blob/main/LICENSE). \ No newline at end of file + +EasyTier is released under the [LGPL 3.0](https://github.com/EasyTier/EasyTier/blob/main/LICENSE). diff --git a/en/guide/network/config-file.md b/en/guide/network/config-file.md new file mode 100644 index 0000000..85e1609 --- /dev/null +++ b/en/guide/network/config-file.md @@ -0,0 +1,27 @@ +# Configuration File + +Supports using the -c parameter to specify the configuration file path. + +```sh +easytier-core -c ./config.yaml +``` + +::: warning Note +Note: Parameters in the configuration file can be overridden by command line parameters. For example, if `--hostname abc` is specified in the configuration file, but `--hostname xyz` is used in the command line, then the hostname parameter `xyz` from the command line will be used. +::: + +Running with parameters can generate a configuration file with the corresponding parameters. The configuration file will be printed in the command line, and you can manually copy and save it as a toml file. + +Running `easytier-core` directly without parameters will generate the minimal configuration file. + +## Multiple Configuration Files Startup + +You can specify multiple configuration files through the `-c` parameter. EasyTier will load multiple configuration files in one process and start multiple virtual networks. + +```sh +easytier-core -c ./config1.yaml -c ./config2.yaml +``` + +## Configuration File Generator + +The official website provides a configuration file generator, which you can access via Configuration File Generator to generate configuration files. diff --git a/en/guide/network/configurations.md b/en/guide/network/configurations.md new file mode 100644 index 0000000..95584c3 --- /dev/null +++ b/en/guide/network/configurations.md @@ -0,0 +1,97 @@ +# Complete Configuration Options + +You can use `easytier-core --help` to view all configuration options. + +## Basic Settings + +### Configuration Server + +| Parameter | Description | +| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-w, --config-server` | Configuration server address. Allowed formats: | +| | - Full URL: `--config-server udp://127.0.0.1:22020/admin` | +| | - Username only: `--config-server admin`, will use the official server | +| | [env: ET_CONFIG_SERVER=] | +| `--machine-id` | Web configuration server identifies machines through machine id, used for configuration recovery after disconnection and reconnection, must be unique and fixed. Default obtained from system. [env: ET_MACHINE_ID=] | +| `-c, --config-file` | Configuration file path, note: options configured in command line will override options in configuration file [env: ET_CONFIG_FILE=] | + +### Network Settings + +| Parameter | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--network-name` | Network name used to identify this VPN network [env: ET_NETWORK_NAME=] | +| `--network-secret` | Network secret, used to verify that this node belongs to the VPN network [env: ET_NETWORK_SECRET=] | +| `-i, --ipv4` | IPv4 address of this VPN node. If empty, this node will only forward packets and will not create a TUN device [env: ET_IPV4=] | +| `-d, --dhcp` | Automatically determine and set IP address by Easytier, default starts from 10.0.0.1. Warning: When using DHCP, if IP conflicts occur in the network, IP will be automatically changed. [env: ET_DHCP=] | +| `-p, --peers` | Peer nodes to connect to initially [env: ET_PEERS=] | +| `-e, --external-node` | Use public shared nodes to discover peer nodes [env: ET_EXTERNAL_NODE=] | +| `-n, --proxy-networks` | Export local network to other peer nodes in VPN, e.g.: `10.0.0.0/24`. Supports mapping to other CIDR, e.g.: `10.0.0.0/24->192.168.0.0/24` [env: ET_PROXY_NETWORKS=] | + +### RPC Settings + +| Parameter | Description | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `-r, --rpc-portal` | RPC portal address for management. Supports the following formats: | +| | - `0` means random port | +| | - `12345` means listen on localhost:12345 | +| | - `0.0.0.0:12345` means listen on all interfaces:12345 | +| | Default is `0`, first try `15888` | +| | [env: ET_RPC_PORTAL=] | +| `--rpc-portal-whitelist` | RPC portal whitelist, only allow these addresses to access RPC portal, e.g.: `127.0.0.1/32,127.0.0.0/8,::1/128` [env: ET_RPC_PORTAL_WHITELIST=] | + +### Listener Settings + +| Parameter | Description | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-l, --listeners` | Listeners for accepting connections, supports the following formats: | +| | - Port number: `<11010>`, means tcp/udp will listen on port 11010, ws/wss will listen on ports 11010 and 11011, wg will listen on port 11011. | +| | - URL: ``, where tcp can be tcp, udp, ring, wg, ws, wss protocols. | +| | - Protocol and port pair: ``, e.g. wg:11011, means use WireGuard protocol to listen on port 11011. | +| | [env: ET_LISTENERS=] | +| `--mapped-listeners` | Manually specify the public address of the listener, other nodes can use this address to connect to this node. E.g.: `tcp://123.123.123.123:11223`, can specify multiple. [env: ET_MAPPED_LISTENERS=] | +| `--no-listener` | Don't listen on any port, only connect to peer nodes [env: ET_NO_LISTENER=] | + +### Other Settings + +| Parameter | Description | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `--hostname` | Hostname used to identify this device [env: ET_HOSTNAME=] | +| `-m, --instance-name` | Instance name, used to identify this VPN node on the same machine [env: ET_INSTANCE_NAME=] | +| `--vpn-portal` | Define the URL of the VPN portal, allowing other VPN clients to connect. E.g.: `wg://0.0.0.0:11010/10.14.14.0/24` [env: ET_VPN_PORTAL=] | +| `--default-protocol` | Default protocol used when connecting to peer nodes [env: ET_DEFAULT_PROTOCOL=] | +| `-u, --disable-encryption` | Disable encryption for peer node communication, default is false, must be the same as peer nodes [env: ET_DISABLE_ENCRYPTION=] | +| `--multi-thread` | Use multi-threaded runtime, default is single-threaded [env: ET_MULTI_THREAD=] | +| `--disable-ipv6` | Don't use IPv6 [env: ET_DISABLE_IPV6=] | +| `--dev-name` | Optional TUN interface name [env: ET_DEV_NAME=] | +| `--mtu` | MTU of TUN device, default is 1380 when not encrypted, 1360 when encrypted [env: ET_MTU=] | +| `--latency-first` | Latency priority mode, will try to use the lowest latency path to forward traffic, default uses shortest path [env: ET_LATENCY_FIRST=] | +| `--exit-nodes` | Exit nodes for forwarding all traffic, virtual IPv4 addresses, priority determined by list order [env: ET_EXIT_NODES=] | +| `--enable-exit-node` | Allow this node to become an exit node [env: ET_ENABLE_EXIT_NODE=] | +| `--proxy-forward-by-system` | Forward subnet proxy packets through system kernel, disable built-in NAT [env: ET_PROXY_FORWARD_BY_SYSTEM=] | +| `--no-tun` | Don't create TUN device, can use subnet proxy to access nodes [env: ET_NO_TUN=] | +| `--use-smoltcp` | Enable smoltcp stack for subnet proxy and KCP proxy [env: ET_USE_SMOLTCP=] | +| `--manual-routes` | Manually assign route CIDR, will disable subnet proxy and wireguard routes propagated from peer nodes. E.g.: `192.168.0.0/16` [env: ET_MANUAL_ROUTES=] | +| `--relay-network-whitelist` | Only forward traffic from whitelisted networks, supports wildcard strings. Multiple network names can be separated by English spaces. [env: ET_RELAY_NETWORK_WHITELIST=] | +| `--disable-p2p` | Disable P2P communication, only forward packets through nodes specified by `--peers` [env: ET_DISABLE_P2P=] | +| `--disable-udp-hole-punching` | Disable UDP hole punching function [env: ET_DISABLE_UDP_HOLE_PUNCHING=] | +| `--relay-all-peer-rpc` | Forward RPC packets from all peer nodes, even if peer nodes are not in the relay network whitelist. [env: ET_RELAY_ALL_PEER_RPC=] | +| `--socks5` | Enable socks5 server, allowing socks5 clients to access virtual network. Format: ``, e.g.: `1080` [env: ET_SOCKS5=] | +| `--compression` | Compression algorithm to use, supports `none`, `zstd`. Default is `none` [env: ET_COMPRESSION=] | +| `--bind-device` | Bind the connector's socket to a physical device to avoid routing issues. [env: ET_BIND_DEVICE=] | +| `--enable-kcp-proxy` | Use KCP proxy for TCP streams, improving latency and throughput on UDP packet loss networks. [env: ET_ENABLE_KCP_PROXY=] | +| `--disable-kcp-input` | Don't allow other nodes to use KCP proxy TCP streams to this node. [env: ET_DISABLE_KCP_INPUT=] | +| `--enable-quic-proxy` | Use QUIC proxy for TCP streams, improving latency and throughput on UDP packet loss networks. [env: ET_ENABLE_QUIC_PROXY=] | +| `--disable-quic-input` | Don't allow other nodes to use QUIC proxy TCP streams to this node. [env: ET_DISABLE_QUIC_INPUT=] | +| `--port-forward` | Forward local ports to remote ports in virtual network. E.g.: `udp://0.0.0.0:12345/10.126.126.1:23456` [env: ET_PORT_FORWARD=] | +| `--accept-dns` | If true, enable Magic DNS. With Magic DNS, you can use domain names to access other nodes, e.g.: `.et.net` [env: ET_ACCEPT_DNS=] | +| `--private-mode` | If true, don't allow nodes using different network names and passwords from this network to handshake or relay through this node [env: ET_PRIVATE_MODE=] | +| `--foreign-relay-bps-limit` | Limit bandwidth for relayed traffic [env: ET_FOREIGN_RELAY_BPS_LIMIT=] | +| `--console-log-level` | Console log level [env: ET_CONSOLE_LOG_LEVEL=] | +| `--file-log-level` | File log level [env: ET_FILE_LOG_LEVEL=] | +| `--file-log-dir` | Directory to store log files [env: ET_FILE_LOG_DIR=] | + +--- + +For more configuration options, please refer to the output of `easytier-core --help`. + +--- diff --git a/en/guide/network/decentralized-networking.md b/en/guide/network/decentralized-networking.md new file mode 100644 index 0000000..972ad37 --- /dev/null +++ b/en/guide/network/decentralized-networking.md @@ -0,0 +1,91 @@ +# Decentralized Networking + +Most networking software is centralized, where all devices must connect to a central server to form a network. + +EasyTier is decentralized, with no distinction between server and client. As long as one device can communicate with any node in the virtual network, it can join the virtual network. + +## Two-Node Networking {#two-nodes} + +Assume the network topology of two nodes is as follows: + +```mermaid +flowchart LR +subgraph Node A [Physical NIC IP: 22.1.1.1] + nodeA[EasyTier
Virtual IP: 10.144.144.1] +end +subgraph Node B [Physical NIC IP: 33.1.1.1] + nodeB[EasyTier
Virtual IP: 10.144.144.2] +end +nodeA <-----> nodeB +``` + +### Steps + +1. Run the following command on Node A: + + ```sh + sudo easytier-core -i 10.144.144.1 + ``` + + - `-i` specifies the virtual network IP address. + + After startup, this node will listen on the following ports by default: + + | Protocol | Default Port | + | ------------- | ------------ | + | TCP | 11010 (TCP) | + | UDP | 11010 (UDP) | + | WebSocket | 11011 (TCP) | + | WebSocket SSL | 11012 (TCP) | + | WireGuard | 11013 (UDP) | + + You can specify listening ports through the `-l` parameter, for example: + + | Parameter Example | Description | + | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | + | `-l 12345` | Change the base port to 12345, then listening ports are: TCP: 12345, UDP: 12345, WebSocket: 12346, WebSocket SSL: 12347, WireGuard: 12348 | + | `-l tcp:11010 -l udp:11011` | Change TCP port to 11010, UDP port to 11011, only listen on these two ports. Supported protocols are `tcp`, `udp`, `ws`, `wss`, `wg` | + | `--no-listener` | Disable port listening, which will affect the establishment of non-hole-punching connections. | + +2. Run the following command on Node B: + + ```sh + sudo easytier-core -d -p udp://22.1.1.1:11010 + ``` + + - `-d` represents DHCP mode, automatically assign virtual IP. + - `-p` specifies the public address and port of Node A. + +## Three-Node Networking + +Based on two-node networking, the third node C can join the virtual network by connecting to Node A or Node B. + +Assume networking by connecting to Node A, the network topology is as follows: + +```mermaid +flowchart LR +subgraph Node A [Physical NIC IP: 22.1.1.1] + nodeA[EasyTier
Virtual IP: 10.144.144.1] +end +subgraph Node C [Newly Added] + nodeC[EasyTier
Virtual IP: 10.144.144.3] +end +subgraph Node B [Physical NIC IP: 33.1.1.1] + nodeB[EasyTier
Virtual IP: 10.144.144.2] +end +nodeA <-----> nodeB +nodeC <-----> nodeA +``` + +### Steps + +1. Run the following command on Node C: + + ```sh + sudo easytier-core -d -p udp://22.1.1.1:11010 + ``` + + - `-d` represents DHCP mode, automatically assign virtual IP. + - `-p` specifies the public address and port of Node A. + +Subsequently, Node C can communicate with Node A and Node B through the virtual network. diff --git a/en/guide/network/host-public-server.md b/en/guide/network/host-public-server.md new file mode 100644 index 0000000..f9919a7 --- /dev/null +++ b/en/guide/network/host-public-server.md @@ -0,0 +1,33 @@ +# Setting Up a Shared Node + +Users can use their own public nodes to set up a public shared node for networking without a public IP, making it easier for other users without a public IP to network. Simply start EasyTier without any parameters, and the node can be used as a public server (no root privileges required): + +``` +easytier-core +``` + +Additionally, EasyTier supports shared node clusters. Each virtual network (created with the same network name and key) can act as a shared node cluster, and nodes from other networks can connect to any node in the shared node cluster, discovering each other without a public IP. Running a self-built public server cluster is the same as running a virtual network, but you can skip configuring the IPv4 address. + +If you wish to contribute a public server to the EasyTier community, you can contact the administrator, and we will inform you how to add your node to the community shared node list. Of course, this requires your node to have a certain level of bandwidth and stability. + +## Disable Forwarding + +By default, each EasyTier node allows forwarding services for other virtual networks, even if the node has specified a network name (`--network-name`) and network key (`--network-secret`), and has joined a virtual network. + +To change this behavior, you can use the `--relay-network-whitelist` parameter to specify a whitelist of network names that can be forwarded (a space-separated list of wildcards, such as `"ab* abc"`). When this parameter's list is empty, it will not provide forwarding services for any other networks. + +EasyTier can avoid forwarding network packets for other virtual networks and only help them establish P2P links by setting the whitelist to empty and configuring it to only forward RPC traffic. The reference command is: + +``` +easytier-core --relay-network-whitelist --relay-all-peer-rpc +``` + +## Private Mode + +If you want EasyTier to only provide services in your virtual network and don't want nodes from other virtual networks to connect to your node, you can start EasyTier with the `--private-mode true` parameter. + +``` +sudo easytier-core --private-mode true --network-name my-network --network-secret my-secret +``` + +This will only allow nodes with network name `my-network` and key `my-secret` to connect to this EasyTier node. diff --git a/en/guide/network/install-as-a-macos-service.md b/en/guide/network/install-as-a-macos-service.md new file mode 100644 index 0000000..d774b17 --- /dev/null +++ b/en/guide/network/install-as-a-macos-service.md @@ -0,0 +1,33 @@ +# Install as a macOS Service + +Download and install [serviceman](https://webinstall.dev/serviceman). + +Open Terminal and run the following commands to register the service: + +```bash +# Register the easytier service using a configuration file +sudo serviceman add -name easytier -system \ +--workdir /var/log/easytier \ +-groupname wheel -username root \ +-cap-net-bind \ +-- easytier-core -c ~/.config/easytier.toml + +# Register the easytier service without using a configuration file +sudo serviceman add -name easytier -system \ +--workdir /var/log/easytier \ +-groupname wheel -username root \ +-cap-net-bind \ +-- easytier-core --ipv4 x.x.x.x --network-name xxx --network-secret yyy --peers tcp://peer_host:11010 +``` + +Start the easytier service: + +```bash +sudo serviceman start easytier +``` + +Stop the easytier service: + +```bash +sudo serviceman stop easytier +``` diff --git a/en/guide/network/install-as-a-systemd-service.md b/en/guide/network/install-as-a-systemd-service.md new file mode 100644 index 0000000..e7c9bd0 --- /dev/null +++ b/en/guide/network/install-as-a-systemd-service.md @@ -0,0 +1,34 @@ +# Install the Service as a Linux Systemd Service + +On Linux distributions that support systemd, you can configure the service to start with the system by following these steps: + +1. Create a new service file `/etc/systemd/system/easytier.service` and modify the command line parameters after `ExecStart` as needed. + +```shell +[Unit] +Description=EasyTier Service +After=network.target syslog.target +Wants=network.target + +[Service] +Type=simple +ExecStart=/root/easytier-core --ipv4 x.x.x.x --network-name xxx --network-secret yyy --peers tcp://peer_host:11010 + +[Install] +WantedBy=multi-user.target +``` + +2. After saving the file, run the following command in the terminal to enable the service: + +```sh +systemctl enable easytier.service +``` + +3. You can start and stop the service using the following commands: + +```sh +systemctl start easytier.service +systemctl stop easytier.service +``` + +Please note that using the `systemctl` command instead of the `service` command is a more modern approach and is recommended on systems that support systemd. diff --git a/en/guide/network/install-as-a-windows-service.md b/en/guide/network/install-as-a-windows-service.md new file mode 100644 index 0000000..b868020 --- /dev/null +++ b/en/guide/network/install-as-a-windows-service.md @@ -0,0 +1,73 @@ +# Install as a Windows Service + +> Thanks to BeiChen℃ for providing the tutorial, and dawn-lc for providing the one-click install/uninstall script + +On Windows systems, installing certain applications as services allows them to run automatically in the background without manual intervention, greatly improving the stability and convenience of the application. + +This tutorial will use NSSM (Non-Sucking Service Manager) to install the EasyTier application as a Windows service as an example, and provide a detailed explanation of the entire operation process. + +## 1. Preparation + +**Download EasyTier Application**: + +Download the latest version of the `Windows` operating system `command line program` compressed package. + +After downloading, extract the compressed package to a local directory, such as `D:\EasyTier`. + +The current directory should contain at least the following files: + +- `easytier-core.exe` (core program) +- `easytier-cli.exe` (command line tool) +- `Packet.dll` (runtime library) +- `wintun.dll` (runtime library) + +**Download NSSM**: + +Open your browser and visit the NSSM official website [https://nssm.cc/](https://nssm.cc/download). + +On the official website page, find the version suitable for your system (usually the latest version), click the download link to download it locally. + +After downloading, find the version corresponding to your device architecture (such as: `win64`), and extract the `nssm.exe` from it to the local directory where `EasyTier` is located. + +**Download Install/Uninstall Script**: + +Start PowerShell in the current directory and execute the following commands: + +`iwr "https://github.com/EasyTier/EasyTier/raw/refs/heads/main/script/install.cmd" -OutFile "install.cmd"` + +`iwr "https://github.com/EasyTier/EasyTier/raw/refs/heads/main/script/uninstall.cmd" -OutFile "uninstall.cmd"` + +## 2. Preparation Work + +1. Ensure the current directory contains the following files: + + - `easytier-core.exe` (core program) + - `easytier-cli.exe` (command line tool) + - `nssm.exe` (service management tool) + - `Packet.dll` (runtime library) + - `wintun.dll` (runtime library) + - `install.cmd` (install script) + - `uninstall.cmd` (uninstall script) + +2. Place the entire folder in a fixed location. + +## 3. Install Service + +1. Run `install.cmd` +2. Follow the prompts to enter configuration information. +3. After installation is complete, the service will start automatically. + +## 4. Uninstall Service + +1. Run `uninstall.cmd` +2. The script will automatically stop and delete the service. + +## 5. Notes + +1. Do not move the program file location after installation + +## 6. Common Questions + +**Q: How to modify service configuration?** + +A: First uninstall the service, then reinstall it diff --git a/en/guide/network/kcp-proxy.md b/en/guide/network/kcp-proxy.md new file mode 100644 index 0000000..423745e --- /dev/null +++ b/en/guide/network/kcp-proxy.md @@ -0,0 +1,126 @@ +# KCP Proxy + +EasyTier typically uses the UDP protocol to transmit IP packets within the virtual network. However, some ISPs restrict UDP, which can lead to high packet loss and affect TCP performance within the virtual network. + +To address this issue, EasyTier provides a KCP proxy feature that can proxy TCP connections in the virtual network and convert them to KCP for transmission. Thanks to KCP's more aggressive retransmission mechanism, it effectively reduces packet loss and improves TCP transmission speeds within the virtual network. + +## Network Topology + +Assume the network topology is as follows: + +```mermaid +graph LR + A[Application Client] -->|TCP| B(EasyTier
Node A) + B -->|KCP over UDP| C(EasyTier
Node B) + C -->|TCP| D[Application Server] + + classDef endpoint fill:#1e90ff,stroke:#ffffff,color:#ffffff + classDef easy fill:#4682b4,stroke:#ffffff,color:#ffffff + classDef transport stroke:#ffa500,stroke-width:2px + + class A,D endpoint + class B,C easy + linkStyle 1 stroke:#ffa500,stroke-width:2px,stroke-dasharray:5 5 + + style B stroke-width:2px + style C stroke-width:2px +``` + +## Using KCP Proxy + +### Enable KCP Proxy + +To proxy TCP traffic on Node A to the KCP protocol, simply start EasyTier on Node A with the `--enable-kcp-proxy` parameter. + +```sh +sudo easytier-core --enable-kcp-proxy +``` + +- `--enable-kcp-proxy` enables the KCP proxy feature. + +The KCP proxy ensures version compatibility by automatically reverting to the TCP protocol if it detects that the counterpart node does not support the KCP proxy. + +### Switch to User-Space Network Stack + +By default, the KCP proxy uses the kernel's network stack, which may not work correctly due to system firewall settings. You can try using the `--use-smoltcp` parameter to switch to the user-space network stack. + +```sh +sudo easytier-core --enable-kcp-proxy --use-smoltcp +``` + +- `--use-smoltcp` switches to the user-space network stack. + +### Disable KCP Input + +If you do not want traffic destined for a specific node to use the KCP protocol, start EasyTier on the target node with the `--disable-kcp-input` parameter. + +For example, if you do not want Node B to receive KCP traffic, start EasyTier on Node B with the following command: + +```sh +sudo easytier-core --disable-kcp-input +``` + +- `--disable-kcp-input` disables KCP inbound traffic. + +In this case, even if Node A has enabled the KCP proxy, the traffic from Node A to Node B will continue to use the TCP protocol. + +## Subnet-to-Subnet KCP Support + +If Node A is a router, and the subnet under A needs to access other EasyTier nodes or other proxied subnets, it can also use the KCP proxy. However, Node A must use the user-space network stack by specifying the `--use-smoltcp` parameter. + +```sh +sudo easytier-core --enable-kcp-proxy --use-smoltcp +``` + +Otherwise, the TCP protocol will still be used. + +## Checking KCP Proxy Status + +You can check the status of KCP proxy connections using the EasyTier CLI tool. + +```bash +$ easytier-cli proxy + +┌────────────────────┬───────────────────┬─────────────────────────┬───────────┬────────────────┐ +│ src │ dst │ start_time │ state │ transport_type │ +├────────────────────┼───────────────────┼─────────────────────────┼───────────┼────────────────┤ +│ 10.126.126.7:51838 │ 10.147.223.128:22 │ 2025-02-07 10:39:08 UTC │ Connected │ Tcp │ +├────────────────────┼───────────────────┼─────────────────────────┼───────────┼────────────────┤ +│ 0.0.0.0:0 │ 10.147.223.1:80 │ 2025-02-07 10:41:28 UTC │ Connected │ Kcp │ +├────────────────────┼───────────────────┼─────────────────────────┼───────────┼────────────────┤ +│ 0.0.0.0:0 │ 10.147.223.1:80 │ 2025-02-07 10:41:18 UTC │ Connected │ Kcp │ +└────────────────────┴───────────────────┴─────────────────────────┴───────────┴────────────────┘ +``` + +## QUIC Proxy + +EasyTier v2.3.2 introduced support for QUIC proxy, which works similarly to KCP proxy, but QUIC's BBR algorithm can achieve higher bandwidth in high packet loss environments (while KCP proxy can significantly reduce latency, but has a lower bandwidth ceiling). + +QUIC proxy can be enabled on the connection initiator side using the `--enable-quic-proxy` parameter. + +```sh +sudo easytier-core --enable-quic-proxy +``` + +QUIC proxy on the receiving end can be disabled using the `--disable-quic-input` parameter. + +```sh +sudo easytier-core --disable-quic-input +``` + +Both the sender and receiver can check the QUIC proxy connection status using the `easytier-cli proxy` command. + +```bash +$ easytier-cli proxy +┌────────────────────┬───────────────────┬─────────────────────────┬───────────┬────────────────┐ +│ src │ dst │ start_time │ state │ transport_type │ +├────────────────────┼───────────────────┼─────────────────────────┼───────────┼────────────────┤ +│ 10.126.126.7:51838 │ 10.147.223.128:22 │ 2025-02-07 10:39:08 UTC │ Connected │ Quic │ +└────────────────────┴───────────────────┴─────────────────────────┴───────────┴────────────────┘ +``` + +::: tip Note +QUIC and KCP proxies can be enabled simultaneously, but KCP proxy takes precedence over QUIC proxy. + +When both are enabled, QUIC proxy will only take effect after the destination end closes KCP input. +::: diff --git a/en/guide/network/magic-dns.md b/en/guide/network/magic-dns.md new file mode 100644 index 0000000..6c4c036 --- /dev/null +++ b/en/guide/network/magic-dns.md @@ -0,0 +1,17 @@ +# Magic DNS + +EasyTier supports a Magic DNS feature similar to Tailscale, allowing users to access other nodes via domain names without remembering virtual IP addresses. Simply add the `--accept-dns` parameter during startup to enable the Magic DNS feature. + +Magic DNS uses `100.100.100.101` as the default DNS server address. You can `ping` this address to test whether Magic DNS is successfully enabled. + +If Magic DNS is successfully enabled, and the hostname of Node A is `node-a`, other nodes can access Node A via `node-a.et.net`. + +```sh +ping node-a.et.net +``` + +Hostnames support Chinese characters. + +::: tip Note +Currently, Magic DNS only supports automatic configuration of system DNS on Windows and macOS. On Linux, you need to manually configure the DNS server to `100.100.100.101` for normal use. +::: diff --git a/en/guide/network/multi-node.md b/en/guide/network/multi-node.md deleted file mode 100644 index bf281d3..0000000 --- a/en/guide/network/multi-node.md +++ /dev/null @@ -1,11 +0,0 @@ -# Multi-node Networking - -Based on the two-node networking example just now, if more nodes need to join the virtual network, you can use the following command. - -``` -sudo easytier-core --ipv4 10.144.144.2 --peers udp://22.1.1.1:11010 -``` - -The `--peers` parameter can fill in the listening address of any node already in the virtual network. - ---- \ No newline at end of file diff --git a/en/guide/network/network-to-network.md b/en/guide/network/network-to-network.md new file mode 100644 index 0000000..c21afa9 --- /dev/null +++ b/en/guide/network/network-to-network.md @@ -0,0 +1,49 @@ +# Network to Network + +The network topology of network to network is shown in the figure + +```mermaid +flowchart LR + +subgraph Node A +nodeA[EasyTier
10.144.144.1] +end + +subgraph Node B +nodeB[EasyTier
10.144.144.2] +end + +id1[[10.1.1.0/24]] + +id2[[192.168.1.0/24]] + +id2 <-.Subnet Proxy.-> nodeA <--> nodeB <-.Subnet Proxy.-> id1 + +id2 -.No EasyTier needed to access the other subnet.-> id1 + +``` + +After the network to network configuration is successful, devices in the 192.168.1.0/24 subnet can communicate with devices in the 10.1.1.0/24 subnet without installing EasyTier. + +## Linux Network to Network Configuration + +To achieve network to network, Node A needs to be the gateway for the 192.168.1.0/24 subnet. The startup and configuration parameters for the two EasyTier nodes are as follows: + +Node A + +```bash +# Start EasyTier and proxy the 192.168.1.0/24 subnet, using a public server to help network +easytier-core -i 10.144.144.1 -n 192.168.1.0/24 -p tcp://public.easytier.top:11010 --network-name n2n_test + +# Allow the gateway to forward traffic and configure the firewall to allow traffic forwarding +sysctl -w net.ipv4.ip_forward=1 +iptables -A FORWARD -s 192.168.1.0/24 -j ACCEPT +iptables -A FORWARD -d 192.168.1.0/24 -j ACCEPT +``` + +Node B + +```bash +# Start EasyTier and proxy the 10.1.1.0/24 subnet, using a public server to help network +easytier-core -i 10.144.144.2 -n 10.1.1.0/24 -p tcp://public.easytier.top:11010 --network-name n2n_test +``` diff --git a/en/guide/network/networking-without-public-ip.md b/en/guide/network/networking-without-public-ip.md new file mode 100644 index 0000000..867b971 --- /dev/null +++ b/en/guide/network/networking-without-public-ip.md @@ -0,0 +1,29 @@ +# Networking Without Public IP + +EasyTier supports networking using shared public nodes. Currently, the following shared public node has been deployed: + +`tcp://public.easytier.top:11010` + +When using shared nodes, each node entering the network needs to provide the same `--network-name` and `--network-secret` parameters as the unique identifier for the network. + +For example, with two nodes: + +Node A executes: + +```sh +sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -p tcp://public.easytier.top:11010 +``` + +Node B executes: + +```sh +sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -p tcp://public.easytier.top:11010 +``` + +After successful execution, Node A can access Node B via the virtual IP `10.144.144.2`. + +`--ipv4 x.x.x.x` can be replaced with `-d` to enable DHCP functionality, allowing EasyTier to automatically allocate the IP address of this node based on other existing virtual IPs within the virtual network. + +Nodes can connect to multiple public servers. If one public server fails, nodes can still communicate using other active public servers. Simply specify multiple `-p` parameters, such as `-p tcp://1.1.1.1:11010 -p udp://1.1.1.2:11011`. Note that each node in the virtual network must specify the same list of public servers; otherwise, networking may not function properly. + +--- diff --git a/en/guide/network/no-root.md b/en/guide/network/no-root.md new file mode 100644 index 0000000..a255632 --- /dev/null +++ b/en/guide/network/no-root.md @@ -0,0 +1,7 @@ +# No TUN Mode (No Root Permission Required) + +Since creating a TUN device requires ROOT permissions, EasyTier provides a method that does not rely on TUN for environments where Root permissions cannot be obtained. Simply add the `--no-tun` parameter when starting EasyTier. + +When using the no TUN mode for networking, nodes can be accessed via virtual IP (TCP, UDP, and ICMP are all supported), and can also act as subnet proxies (using the -n parameter). However, they cannot actively initiate access to other nodes. + +To actively access other nodes in no TUN mode, you can use EasyTier's [SOCKS5 server feature](/guide/network/socks5). diff --git a/en/guide/network/oneclick-install-as-service.md b/en/guide/network/oneclick-install-as-service.md new file mode 100644 index 0000000..b273eb7 --- /dev/null +++ b/en/guide/network/oneclick-install-as-service.md @@ -0,0 +1,79 @@ +# One-Click Register Service + +EasyTier Cli provides a service registration command that can register EasyTier as a system service with one click on most systems. After registration, EasyTier will automatically start when the system boots and run in the background. + +Using this command requires `easytier-core` and `easytier-cli` to be in the same directory. After entering that directory, run the following command: + +::: code-group + +```sh [Linux] +# Assuming EasyTier's startup parameters are -w abc +sudo ./easytier-cli service install -w abc +``` + +```powershell [Windows] +# Assuming EasyTier's startup parameters are -w abc +.\easytier-cli.exe service install -w abc +``` + +::: + +The part after `install` will be used as startup parameters for `easytier-core`. + +After the service is successfully installed, you can use the following commands to manage the service: + +- Start service: + + ::: code-group + + ```sh [Linux] + sudo ./easytier-cli service start + ``` + + ```powershell [Windows] + .\easytier-cli.exe service start + ``` + + ::: + +- Stop service: + + ::: code-group + + ```sh [Linux] + sudo ./easytier-cli service stop + ``` + + ```powershell [Windows] + .\easytier-cli.exe service stop + ``` + + ::: + +- Check status: + + ::: code-group + + ```sh [Linux] + sudo ./easytier-cli service status + ``` + + ```powershell [Windows] + .\easytier-cli.exe service status + ``` + + ::: + +- Uninstall service: + + ::: code-group + + ```sh [Linux] + sudo ./easytier-cli service uninstall + ``` + + ```powershell [Windows] + .\easytier-cli.exe service uninstall + ``` + + ::: diff --git a/en/guide/network/other.md b/en/guide/network/other.md deleted file mode 100644 index 8372e3c..0000000 --- a/en/guide/network/other.md +++ /dev/null @@ -1,3 +0,0 @@ -# 其他 - -可使用 ``easytier-core --help`` 查看全部配置项 \ No newline at end of file diff --git a/en/guide/network/p2p-optimize.md b/en/guide/network/p2p-optimize.md new file mode 100644 index 0000000..ed463d2 --- /dev/null +++ b/en/guide/network/p2p-optimize.md @@ -0,0 +1,28 @@ +# P2P Optimization + +If you want EasyTier to establish P2P connections with other nodes more easily, you can optimize it in the following ways. + +## IPv6 + +EasyTier supports P2P communication between nodes via IPv6. By default, EasyTier will randomly listen on an IPv6 UDP port. + +In some cases, specifying the listening IPv6 address and port may be more beneficial for P2P communication between nodes. +You can use the `-l` option to configure the IPv6 listener. For example: + +```sh +easytier-core -l 'tcp://[::]:12345' -l 'udp://[::]:12345' +``` + +## Specify Public IP and Port + +In some cases, the node has a public IP and port, but EasyTier cannot correctly identify them (e.g., NAT host). You can use the `--mapped-listeners` option to configure the public IP and port. For example: + +```sh +easytier-core --mapped-listeners tcp://8.8.8.8:12345 -l tcp://0.0.0.0:11010 +``` + +This EasyTier instance listens on the local 11010 TCP port, and this port is mapped to the public 12345 port. Other nodes will try to connect to the public 12345 port. + +## Disable Internet Assistance Tools + +Some internet assistance tools may affect the results of STUN tests, causing EasyTier to fail to identify the NAT type or to identify the wrong public IP and port. You can try disabling these tools. diff --git a/en/guide/network/point-to-networking.md b/en/guide/network/point-to-networking.md new file mode 100644 index 0000000..268d117 --- /dev/null +++ b/en/guide/network/point-to-networking.md @@ -0,0 +1,123 @@ +# Subnet Proxy (Point-to-Network) + +Assume the network topology is as follows, and node B wants to share its accessible subnet 10.1.1.0/24 with other nodes. + +```mermaid +flowchart LR + +subgraph Node A IP 22.1.1.1 +nodeA[EasyTier
10.144.144.1] +end + +subgraph Node B +nodeB[EasyTier
10.144.144.2] +end + +id1[[10.1.1.0/24]] + +nodeA <--> nodeB <-.-> id1 + +``` + +The startup parameters for node B's easytier are as follows (add the -n parameter) + +```sh +sudo easytier-core --ipv4 10.144.144.2 -n 10.1.1.0/24 +``` + +The subnet proxy information will be automatically synchronized to each node in the virtual network, and each node will automatically configure the corresponding routes. Node A can check if the subnet proxy is effective with the following command. + +1. Check if the routing information has been synchronized. The proxy_cidrs column shows the proxied subnets. + + ```sh + easytier-cli route + ``` + + | ipv4 | hostname | proxy_cidrs | next_hop_ipv4 | next_hop_hostname | next_hop_lat | cost | + | :----------- | :------- | :---------- | :------------ | :---------------- | :----------- | :--- | + | 10.144.144.1 | abc-dec | 10.1.1.0/24 | DIRECT | | 3.25 | 1 | + +2. Test if node A can access the nodes under the proxied subnet + + ```sh + ping 10.1.1.2 + ``` + +::: warning Note +The -n parameter for subnet proxy can be specified multiple times to proxy multiple subnets; you can also set the mask to 32 to proxy a single IP address. + +```sh +easytier-core -n 10.1.1.0/24 -n 10.2.0.0/16 -n 10.3.3.3/32 +``` + +::: + +## Firewall + +Since proxy traffic needs to use the system's network stack, the subnet proxy requires the firewall on the virtual network card to be disabled. + +- On Windows, you can add `easytier-core.exe` to the firewall exception list or directly disable the firewall. + +- On Linux, you can use `iptables` or `ufw` to allow INPUT and OUTPUT traffic on the virtual network card. + +- On OpenWrt, you can allow virtual network card traffic in the LUCI interface. + +If you cannot disable the firewall, you can try using a user-space network stack for the subnet proxy, which can avoid the need to configure the firewall. Simply add the `--use-smoltcp` parameter when starting EasyTier. + +::: warning Note + +The user-space protocol stack will be inferior to the kernel protocol stack in terms of performance, congestion control, etc. + +Currently, the `--use-smoltcp` parameter only affects the TCP protocol. UDP and ICMP will use the user-space protocol stack regardless of whether this parameter is used. + +::: + +## Manually Specifying Routes + +By default, when a node in the virtual network configures a subnet proxy, the subnet proxy segment will be synchronized to all nodes in the virtual network, and a route will be automatically generated to forward packets destined for these segments to the virtual network. + +This can simplify networking in most cases, but in some scenarios, users may not want EasyTier to automatically configure routes on the nodes. Users can manually configure the traffic to be forwarded to the virtual network using the `--manual-routes` parameter. + +When using `--manual-routes`, only the segments configured with this parameter will enter the virtual network. If the list after this parameter is empty, EasyTier will not handle any traffic for non-virtual network segments. For example: + +```sh +sudo easytier-core --ipv4 10.144.144.2 -n 10.1.1.0/24 --manual-routes 10.1.1.0/24 +``` + +`--manual-routes` can be specified multiple times to configure multiple segments, with the same format as the `-n` parameter. + +Then only traffic from the 10.1.1.0/24 segment will be handled by the virtual network on this node, and traffic from other segments will not enter the virtual network. + +## Network Segment Mapping + +Assume the following scenario: Both node A and node B have subnets of `192.168.1.0/24` in their internal networks (same network segment but different physical networks). If nodes A and B want to proxy this network segment, they need to map this network segment to different virtual network segments. + +The following commands map `192.168.1.0/24` to `10.1.1.0/24` on node A, and `192.168.1.0/24` to `10.2.2.0/24` on node B. + +```sh +# Run on node A +sudo easytier-core --ipv4 10.144.144.1 -n '192.168.1.0/24->10.1.1.0/24' + +# Run on node B +sudo easytier-core --ipv4 10.144.144.2 -n '192.168.1.0/24->10.2.2.0/24' +``` + +Other nodes in the virtual network can access the `192.168.1.X` proxied by node A by accessing `10.1.1.X`; and access the `192.168.1.X` proxied by node B by accessing `10.2.2.X`. + +::: warning Note +The mapped network segment size must be the same as the original network segment size, otherwise EasyTier will fail to start. +::: + +## Disabling Built-in NAT + +By default, the built-in NAT is enabled in the subnet proxy, which handles packet forwarding in user space. This allows non-gateway devices to act as subnet entry points while circumventing the limitations on packet forwarding across different operating systems. + +If Easytier is operating on a gateway device, users can use the `--proxy-forward-by-system` parameter to delegate the forwarding of subnet proxy packets to the system kernel, in which case the built-in NAT will be disabled. + +It is important to note that when this option is enabled, the packet forwarding of the subnet proxy will rely entirely on the operating system. Therefore, please ensure that the operating system's firewall, forwarding rules, routing rules, and other configurations are correctly set up. + +## Network to Network + +The mutual access between the subnet under node A and the subnet under node B is called network to network. For network to network configuration, please refer to the [Network to Network](network-to-network) chapter. + +--- diff --git a/en/guide/network/point-to.md b/en/guide/network/point-to.md deleted file mode 100644 index 2837230..0000000 --- a/en/guide/network/point-to.md +++ /dev/null @@ -1,43 +0,0 @@ -# Subnet Proxy (Point-to-Network) - -Assuming the network topology is as follows, Node B wants to share its accessible subnet 10.1.1.0/24 with other nodes. - -```mermaid -flowchart LR - -subgraph Node A IP 22.1.1.1 -nodea[EasyTier\n10.144.144.1] -end - -subgraph Node B -nodeb[EasyTier\n10.144.144.2] -end - -id1[[10.1.1.0/24]] - -nodea <--> nodeb <-.-> id1 - -``` - -Then the startup parameters for Node B's easytier are (new -n parameter) - -```sh -sudo easytier-core --ipv4 10.144.144.2 -n 10.1.1.0/24 -``` - -Subnet proxy information will automatically sync to each node in the virtual network, and each node will automatically configure the corresponding route. Node A can check whether the subnet proxy is effective through the following command. - -1. Check whether the routing information has beensynchronized, the proxy_cidrs column shows the proxied subnets. - - ```sh - easytier-cli route - ``` - ![alt text](/assets/image-3.png) - -2. Test whether Node A can access nodes under the proxied subnet - - ```sh - ping 10.1.1.2 - ``` - ---- \ No newline at end of file diff --git a/en/guide/network/quick-networking.md b/en/guide/network/quick-networking.md new file mode 100644 index 0000000..258936e --- /dev/null +++ b/en/guide/network/quick-networking.md @@ -0,0 +1,227 @@ +# Quick Networking + +## Using Shared Nodes for Networking + +When you don't have a public IP, you can use the free shared nodes provided by the EasyTier community for quick networking. Nodes will automatically attempt NAT traversal and establish P2P connections. When P2P fails, data will be relayed through shared nodes. + +You can build a simple dual-node virtual network through the following steps: + +```mermaid +flowchart LR + S[Public Shared Node] + +subgraph Virtual Network abc + A[Node A] + B[Node B] +end + +A -->|Connect| S +B -->|Connect| S +A <-.->|P2P Direct| B +``` + +### Step Example + +Assuming there are two nodes A and B: + +#### 1. Run on Node A + +::: code-group + +```sh [Linux] +# Run with administrator privileges +./easytier-core -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 +``` + +```powershell [Windows] +# Run with administrator privileges +.\easytier-core.exe -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 +``` + +::: + +- `-d` Automatically assign virtual IP, default allocation is `10.126.126.0/24` network segment, you can use `-i 10.11.11.0/24` to specify other virtual IPs. +- `--network-name` Specify the virtual network name (supports Chinese). Note: If it conflicts with other users' network names, it may cause networking failure. +- `--network-secret` Specify the password for the virtual network, used to protect network security. +- `-p` Specify the node address, here it's the official shared node, you can also use [other public nodes](https://easytier.gd.nkbpal.cn/status/easytier) + +#### 2. Run on Node B + +::: code-group + +```sh [Linux] +# Run with administrator privileges +./easytier-core -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 +``` + +```powershell [Windows] +# Run with administrator privileges +.\easytier-core.exe -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 +``` + +::: + +- `-d` Indicates DHCP mode, automatically assign virtual IP +- `-p` Must specify the same shared node as Node A + +#### 3. Test Networking + +Test connectivity with Node A on Node B: + +```sh +ping 10.126.126.1 +ping 10.126.126.2 +``` + +::: warning Note +If you cannot ping through, it may be that the firewall is blocking incoming traffic. Please turn off the firewall or add allow rules. +::: + +#### 4. Join More Nodes + +You can continue running the same command on other nodes to join the same virtual network. + +## View Virtual Network Status + +After EasyTier starts, you can use easytier-cli to manage and view status. + +- View node information in the virtual network: + +```sh +easytier-cli peer +``` + +--- + +| ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | +| :----------- | :------- | :--- | :----- | :-------- | :------- | :------- | :----------- | :------- | :-------- | +| 10.144.144.1 | abc-dec | 1 | 3.452 | 0 | 17.33kB | 20.42kB | udp | FullCone | 390879727 | + +- View virtual network routing information: + +```sh +easytier-cli route +``` + +--- + +| ipv4 | hostname | proxy_cidrs | next_hop_ipv4 | next_hop_hostname | next_hop_lat | cost | +| :----------- | :------- | :---------- | :------------ | :---------------- | :----------- | :--- | +| 10.144.144.1 | abc-dec | | DIRECT | | 3.646 | 1 | + +- View local node information: + +```sh +easytier-cli node +``` + +--- + +::: details Output Example + +``` +┌───────────────┬──────────────────────┐ +│ Virtual IP │ 10.144.144.1 │ +├───────────────┼──────────────────────┤ +│ Hostname │ archlinux-base │ +├───────────────┼──────────────────────┤ +│ Proxy CIDRs │ 10.147.223.0/24 │ +├───────────────┼──────────────────────┤ +│ Peer ID │ 2616333191 │ +├───────────────┼──────────────────────┤ +│ Public IP │ 75.52.125.26 │ +├───────────────┼──────────────────────┤ +│ UDP Stun Type │ FullCone │ +├───────────────┼──────────────────────┤ +│ Listener 1 │ tcp://0.0.0.0:11010 │ +├───────────────┼──────────────────────┤ +│ Listener 2 │ udp://0.0.0.0:11010 │ +├───────────────┼──────────────────────┤ +│ Listener 3 │ wg://0.0.0.0:11011 │ +├───────────────┼──────────────────────┤ +│ Listener 4 │ ws://0.0.0.0:11011/ │ +├───────────────┼──────────────────────┤ +│ Listener 5 │ wss://0.0.0.0:11012/ │ +├───────────────┼──────────────────────┤ +│ Listener 6 │ udp://[::]:37039 │ +└───────────────┴──────────────────────┘ +``` + +::: + +--- + +## Using Multiple Shared Nodes for Networking + +To improve availability, you can connect to multiple shared nodes simultaneously by specifying multiple `-p` parameters: + +```sh +-p tcp://1.1.1.1:11010 -p udp://1.1.1.2:11011 +``` + +It's recommended that all nodes specify the same shared node list. + +### Networking Principle Diagram + +The following diagram shows the networking mode under multiple shared node clusters: + +```mermaid +flowchart LR +subgraph Shared Nodes + nodeA[Shared Node A
Network Name: Public] + nodeB[Shared Node B
Network Name: Public] +end +subgraph Node C + nodeC[Node C
Network Name: abc] +end +subgraph Node D + nodeD[Node D
Network Name: abc] +end +nodeA <--> nodeB +nodeC <--> Shared Nodes +nodeD <--> Shared Nodes +``` + +Even if network partitioning occurs, where C can only connect to A and D can only connect to B, C and D can still communicate: + +```mermaid +flowchart LR +subgraph Node C + nodeC[Node C
Network Name: abc] +end +subgraph Shared Nodes + nodeA[Shared Node A
Network Name: Public] + nodeB[Shared Node B
Network Name: Public] +end +subgraph Node D + nodeD[Node D
Network Name: abc] +end +nodeA <--> nodeB +nodeC <--> nodeA +nodeB <--> nodeD +``` + +--- + +## Joining/Building Multiple Virtual Networks Simultaneously + +EasyTier supports running multiple processes on the same device, with each process joining different virtual networks. Note: + +- Virtual IP network segments of different virtual networks cannot overlap, otherwise routing conflicts will occur; +- When starting multiple instances, different listening ports must be specified, otherwise port conflicts will occur. + +Example: + +```sh +# Run with administrator privileges +./easytier-core --network-name net1 -p tcp://public.easytier.cn:11010 -l 11010 +./easytier-core --network-name net2 -p tcp://public.easytier.cn:11010 -l 21010 +``` + +- `-l` Specify the listening port. + +--- + +## Setting Up Shared Nodes + +If you want to set up your own shared nodes, you can refer to the [Hosting Public Server](host-public-server) documentation. diff --git a/en/guide/network/socks5.md b/en/guide/network/socks5.md new file mode 100644 index 0000000..b5083c6 --- /dev/null +++ b/en/guide/network/socks5.md @@ -0,0 +1,5 @@ +# SOCKS5 + +EasyTier supports creating SOCKS5 servers. Other programs on the node can access the virtual network and other proxy subnets within the virtual network by setting the proxy to EasyTier's SOCKS5 service. + +The parameter to enable the SOCKS5 service is `--socks5 12333`. Adding this parameter to the easytier-core startup command will allow the local 12333 port to serve SOCKS5 clients. Currently, the SOCKS5 server does not require username and password authentication and can be used directly. diff --git a/en/guide/network/two-node.md b/en/guide/network/two-node.md deleted file mode 100644 index a8ea337..0000000 --- a/en/guide/network/two-node.md +++ /dev/null @@ -1,50 +0,0 @@ -# Two-node Networking - -Assuming the network topology of the two nodes is as follows - -```mermaid -flowchart LR - -subgraph Node A IP 22.1.1.1 -nodea[EasyTier\n10.144.144.1] -end - -subgraph Node B -nodeb[EasyTier\n10.144.144.2] -end - -nodea <-----> nodeb - -``` - -1. Execute on Node A: - ```sh - sudo easytier-core --ipv4 10.144.144.1 - ``` - Successful execution of the command will print the following. - - ![alt text](/assets/image-2.png) - -2. Execute on Node B - ```sh - sudo easytier-core --ipv4 10.144.144.2 --peers udp://22.1.1.1:11010 - ``` - -3. Test Connectivity - - The two nodes should connect successfully and be able to communicate within the virtual subnet - ```sh - ping 10.144.144.2 - ``` - - Use easytier-cli to view node information in the subnet - ```sh - easytier-cli peer - ``` - ![alt text](/assets/image.png) - ```sh - easytier-cli route - ``` - ![alt text](/assets/image-1.png) - ---- \ No newline at end of file diff --git a/en/guide/network/use-easytier-with-wireguard-client.md b/en/guide/network/use-easytier-with-wireguard-client.md new file mode 100644 index 0000000..2251cc1 --- /dev/null +++ b/en/guide/network/use-easytier-with-wireguard-client.md @@ -0,0 +1,78 @@ +# Connect Using WireGuard Client + +EasyTier can be used as a WireGuard server, allowing any device with a WireGuard client installed to access the EasyTier network. For platforms currently not supported by EasyTier (such as iOS), this method can be used to connect to the EasyTier network. + +## Network Topology + +Assume the network topology is as follows, where node A and node B use the [two-node networking](decentralized-networking#two-nodes) method, and node B proxies the `10.1.1.0/24` subnet through [subnet proxy](point-to-networking). + +```mermaid +flowchart LR +ios[[iPhone
with WireGuard installed]] + +subgraph Node A [Public IP: 22.1.1.1] + nodea[EasyTier
Virtual IP: 10.144.144.1] +end + +subgraph Node B + nodeb[EasyTier
Virtual IP: 10.144.144.2] +end + +id1[[Subnet
10.1.1.0/24]] + +ios <-.-> nodea <--> nodeb <-.-> id1 +``` + +We need the iPhone to access the EasyTier network through node A, and the configuration can be as follows. + +## Configuration Steps + +### 1. Configure Node A + +In the `easytier-core` command on node A, add the `--vpn-portal` parameter to specify the port WireGuard listens on and the subnet used by the WireGuard network. + +```sh +# The following parameters mean: listen on 0.0.0.0:11013 port, WireGuard uses the 10.14.14.0/24 subnet +sudo easytier-core --ipv4 10.144.144.1 --vpn-portal wg://0.0.0.0:11013/10.14.14.0/24 +``` + +### 2. Get WireGuard Client Configuration + +After `easytier-core` starts successfully, use `easytier-cli` to get the WireGuard client configuration. + +```sh +$> easytier-cli vpn-portal +portal_name: wireguard + +client_config: +[Interface] +PrivateKey = 9VDvlaIC9XHUvRuE06hD2CEDrtGF+0lDthgr9SZfIho= +Address = 10.14.14.0/24 # should assign an ip from this cidr manually + +[Peer] +PublicKey = zhrZQg4QdPZs8CajT3r4fmzcNsWpBL9ImQCUsnlXyGM= +AllowedIPs = 192.168.80.0/20,10.147.223.0/24,10.144.144.0/24 +Endpoint = 0.0.0.0:11013 # should be the public ip of the easytier server + +connected_clients: +[] +``` + +## Using Client Configuration + +Before using the Client Config, you need to modify the following fields to actual values: + +- **Interface Address**: Modify to the client's IP, such as `10.14.14.1/24`. If connecting multiple WireGuard clients, ensure each client has a different IP. +- **Peer Endpoint**: Modify to the public IP and port of EasyTier node A `22.1.1.11:11013`. + +Import the configuration file into the WireGuard client to access the EasyTier network. + +::: tip Note +If you need to support multiple clients, you can specify a larger subnet in the `--vpn-portal` parameter of `easytier-core`, such as `10.14.0.0/16`. +::: + +## Notes + +All traffic from WireGuard clients accessing the EasyTier virtual network will go through EasyTier node A. You need to ensure that node A's network connection is stable, and generally node A needs to have a public IP for convenient use. + +--- diff --git a/en/guide/network/use-wireguard-client.md b/en/guide/network/use-wireguard-client.md deleted file mode 100644 index d779b73..0000000 --- a/en/guide/network/use-wireguard-client.md +++ /dev/null @@ -1,55 +0,0 @@ -# Use EasyTier with WireGuard Client - -EasyTier can be used as a WireGuard server to allow any device with WireGuard client installed to access the EasyTier network. For platforms currently unsupported by EasyTier (such as iOS, Android, etc.), this method can be used to connect to the EasyTier network. - -Assuming the network topology is as follows: - -```mermaid -flowchart LR - -ios[[iPhone \n WireGuard Installed]] - -subgraph Node A IP 22.1.1.1 -nodea[EasyTier\n10.144.144.1] -end - -subgraph Node B -nodeb[EasyTier\n10.144.144.2] -end - -id1[[10.1.1.0/24]] - -ios <-.-> nodea <--> nodeb <-.-> id1 -``` - -To enable an iPhone to access the EasyTier network through Node A, the following configuration can be applied: - -Include the --vpn-portal parameter in the easytier-core command on Node A to specify the port that the WireGuard service listens on and the subnet used by the WireGuard network. - -``` -# The following parameters mean: listen on port 0.0.0.0:11013, and use the 10.14.14.0/24 subnet for WireGuard -sudo easytier-core --ipv4 10.144.144.1 --vpn-portal wg://0.0.0.0:11013/10.14.14.0/24 -``` - -After successfully starting easytier-core, use easytier-cli to obtain the WireGuard client configuration. - -``` -$> easytier-cli vpn-portal -portal_name: wireguard - -client_config: -[Interface] -PrivateKey = 9VDvlaIC9XHUvRuE06hD2CEDrtGF+0lDthgr9SZfIho= -Address = 10.14.14.0/24 # should assign an ip from this cidr manually - -[Peer] -PublicKey = zhrZQg4QdPZs8CajT3r4fmzcNsWpBL9ImQCUsnlXyGM= -AllowedIPs = 192.168.80.0/20,10.147.223.0/24,10.144.144.0/24 -Endpoint = 0.0.0.0:11013 # should be the public ip of the vpn server - -connected_clients: -[] - -``` - -Before using the Client Config, you need to modify the Interface Address and Peer Endpoint to the client's IP and the IP of the EasyTier node, respectively. Import the configuration file into the WireGuard client to access the EasyTier network. \ No newline at end of file diff --git a/en/guide/network/web-console.md b/en/guide/network/web-console.md new file mode 100644 index 0000000..1102fe8 --- /dev/null +++ b/en/guide/network/web-console.md @@ -0,0 +1,58 @@ +# Using the Web Console + +EasyTier supports using the [Web Console](https://easytier.cn/web#/) to manage EasyTier nodes, including viewing node status, configuring node parameters, viewing node logs, and more. + +## Register an Account + +To use the Web Console for the first time, you need to register an account. [Registration link](https://easytier.cn/web#/auth/register). + +## Running EasyTier Node + +If you want the EasyTier node to be managed by the Web Console, you need to specify the `--config-server` or `-w` parameter when starting, for example: + +```sh +sudo ./easytier-core -w +``` + +> Please replace `` with the username you registered on the Web Console. + +If the terminal shows messages like "Connection successful" or "Connected to server", it means Easytier Core has successfully connected to the Web Console server. + +::: tip Note +The Web backend identifies devices and persists configurations through machine unique codes. By default, EasyTier automatically obtains the machine unique code from the system. If the machine code acquisition fails, it may cause configuration loss after restart. It is recommended to use the `--machine-id` parameter to specify the machine code, for example: + +```sh +sudo ./easytier-core -w --machine-id abc123 +``` + +Please ensure the machine code is unique and unchanged across all devices. **It is strongly recommended to manually specify the machine code in Docker environments.** +::: + +::: danger Note +Only one EasyTier process on a machine can be managed by the Web Console. Having multiple processes may cause unexpected issues. +::: + +::: tip Note + +You can specify the hostname displayed on the console using the `--hostname ` parameter. + +::: + +## Using the Web Console + +Log in to the [Web Console](https://easytier.cn/web#/) using the username and password you just registered. After logging in successfully, you will see the node list. + +Select the device you need to configure on the webpage. + +![alt text](/assets/web-homepage.png) + +After opening the device, click the green connect button. + +![alt text](/assets/web-device-list.png) +![alt text](/assets/web-device-config.png) + +Configure + +![alt text](/assets/web-device-run-network.png) + +The subsequent configuration steps are the same as configuring a program with a GUI. diff --git a/en/guide/network/without-public-ip.md b/en/guide/network/without-public-ip.md deleted file mode 100644 index c332e1f..0000000 --- a/en/guide/network/without-public-ip.md +++ /dev/null @@ -1,21 +0,0 @@ -# Networking without Public IP - -EasyTier supports networking using shared public nodes. The currently deployed shared public node is - -``tcp://easytier.public.kkrainbow.top:11010`` - -When using shared nodes, each node entering the network needs to provide the same ``--network-name`` and ``--network-secret`` parameters as the unique identifier of the network. - -Taking two nodes as an example, Node A executes: - -```sh -sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -e tcp://easytier.public.kkrainbow.top:11010 -``` - -Node B executes - -```sh -sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -e tcp://easytier.public.kkrainbow.top:11010 -``` - -After the command is successfully executed, Node A can access Node B through the virtual IP 10.144.144.2. diff --git a/en/guide/networking.md b/en/guide/networking.md index d98ad8c..6648080 100644 --- a/en/guide/networking.md +++ b/en/guide/networking.md @@ -1,6 +1,7 @@ # Networking -::: tip 注意 -The following text only describes the use of the command-line tool; the GUI program can be configured by referring to the following concepts. + +::: warning Note +The following only describes the use of command-line tools. For graphical interface programs, you can refer to the concepts below for configuration, or refer to [Graphical Interface GUI Networking](/guide/gui/index). ::: -Make sure EasyTier is installed according to the [Installation Guide](/guide/installation), and both easytier-core and easytier-cli commands are available. \ No newline at end of file +Make sure you have installed EasyTier according to the [Installation Guide](/guide/installation), and that both the easytier-core and easytier-cli commands are available. diff --git a/en/guide/perf.md b/en/guide/perf.md new file mode 100644 index 0000000..2c176eb --- /dev/null +++ b/en/guide/perf.md @@ -0,0 +1,149 @@ +# Performance Testing + +Software and versions involved in the test (to avoid bias and for fair treatment, use "X" as a placeholder): + +| Software | Version | Link | +| ----------------- | ----------------- | ------------------------------------ | +| EasyTier | 1.2.1 | https://github.com/EasyTier/EasyTier | +| Networking Tool A | July 2024 Version | | + +To be tested: + +- WireGuard +- TailScale +- ZeroTier + +## X86 + +| | | +| ------------- | -------------------------------------------- | +| Machine Model | Alibaba Cloud ecs.ic5.2xlarge | +| vCPU | 8 vCPU | +| RAM | 8G | +| CPU Model | Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz | +| OS | Ubuntu 22.04 64-bit | + +## Test Results + +| Software | Test Item | Performance ( No -R / With -R ) Gbit/s | +| :---------------: | :---------------: | :------------------------------------: | +| LoopBack Device | | 28.3 / 28.3 | +| EasyTier | UDP No Encryption | 1.43 / 1.46 | +| EasyTier | UDP AES-128-GCM | 1.36 / 1.37 | +| EasyTier | TCP No Encryption | 1.31 / 1.41 | +| EasyTier | TCP AES-128-GCM | 1.42 / 1.41 | +| | | | +| Networking Tool A | UDP No Encryption | 1.10 / 1.11 | +| Networking Tool A | UDP AES-128-GCM | 0.93 / 0.98 | + +## Reproduction Method + +### Basic Preparation + +The test is based on Linux network namespace functionality and can be performed using Ubuntu virtual machines, physical machines, Docker containers, etc. + +Initialization commands (execute with root privileges) + +```bash +apt update +apt install iperf3 iptables -y + +ip netns add red +ip netns add green +ip link add br0 type bridge +ip link set br0 up +ip addr add 192.168.0.1/16 dev br0 + +ip link add vethcab0 type veth peer name red0 +ip link set vethcab0 master br0 +ip link set red0 netns red +ip netns exec red ip link set lo up +ip netns exec red ip link set red0 up +ip netns exec red ip addr add 192.168.0.2/16 dev red0 +ip netns exec red ip route add default via 192.168.0.1 +ip link set vethcab0 up + +ip link add vethcab1 type veth peer name green0 +ip link set vethcab1 master br0 +ip link set green0 netns green +ip netns exec green ip link set lo up +ip netns exec green ip link set green0 up +ip netns exec green ip addr add 192.168.0.3/16 dev green0 +ip netns exec green ip route add default via 192.168.0.1 +ip link set vethcab1 up + +sysctl net.ipv4.ip_forward=1 +sysctl net.bridge.bridge-nf-call-iptables=0 +sysctl net.bridge.bridge-nf-call-ip6tables=0 +sysctl net.ipv6.conf.lo.disable_ipv6=0 + +# Note: EasyTier does not rely on public network services, so iptables forwarding can be omitted +iptables -t nat -A POSTROUTING -j MASQUERADE +iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o eth0 -j MASQUERADE +iptables -A FORWARD -i eht0 -j ACCEPT +iptables --policy FORWARD ACCEPT + +nohup ip netns exec red iperf3 -s & +``` + +Additionally, ensure that the programs to be tested are in the PATH environment variable. + +The following iperf3 command does not include -R; in actual tests, data with -R will be measured. + +### LoopBack + +```bash +ip netns exec green iperf3 -c 192.168.0.2 +``` + +### EasyTier + +#### UDP No Encryption: + +```bash +ip netns exec red easytier-core -i 10.126.126.2 --multi-thread -u +ip netns exec green easytier-core -i 10.126.126.3 -p udp://192.168.0.2:11010 --multi-thread -u +ip netns exec green iperf3 -c 10.126.126.2 +``` + +#### UDP Encryption: + +```bash +ip netns exec red easytier-core -i 10.126.126.2 --multi-thread +ip netns exec green easytier-core -i 10.126.126.3 -p udp://192.168.0.2:11010 --multi-thread +ip netns exec green iperf3 -c 10.126.126.2 +``` + +#### TCP No Encryption + +```bash +ip netns exec red easytier-core -i 10.126.126.2 --multi-thread -u +ip netns exec green easytier-core -i 10.126.126.3 -p tcp://192.168.0.2:11010 --multi-thread -u +ip netns exec green iperf3 -c 10.126.126.2 +``` + +#### TCP Encryption + +```bash +ip netns exec red easytier-core -i 10.126.126.2 --multi-thread +ip netns exec green easytier-core -i 10.126.126.3 -p tcp://192.168.0.2:11010 --multi-thread +ip netns exec green iperf3 -c 10.126.126.2 +``` + +### Networking Tool A + +#### UDP No Encryption + +```bash +ip netns exec red xxx -k iperf -s 8.134.146.7:29872 --ip 10.26.0.2 +ip netns exec green xxx -k iperf -s 8.134.146.7:29872 --ip 10.26.0.3 +ip netns exec green iperf3 -c 10.26.0.2 +``` + +#### UDP Encryption + +```bash +ip netns exec red xxx -k iperf -s 8.134.146.7:29872 -w 1234 --ip 10.26.0.2 +ip netns exec green xxx -k iperf -s 8.134.146.7:29872 -w 1234 --ip 10.26.0.3 +ip netns exec green iperf3 -c 10.26.0.2 +``` diff --git a/en/guide/roadmap.md b/en/guide/roadmap.md index 5f50504..bf529b0 100644 --- a/en/guide/roadmap.md +++ b/en/guide/roadmap.md @@ -1,6 +1,8 @@ # Roadmap -- [ ] Improve documentation and user guides. -- [ ] Support features such as encryption, TCP hole punching,etc. -- [ ] Support Android, IOS and other mobile platforms. -- [ ] Support Web configuration management. \ No newline at end of file +- [ ] Support for optimizing P2P transmission using KCP / FEC. +- [ ] Support for UPnP. +- [ ] Support for IOS. +- [ ] Support for TCP hole punching. +- [x] Support for Web configuration management. +- [x] Improve documentation and user guides. diff --git a/en/index.md b/en/index.md new file mode 100644 index 0000000..0607dd4 --- /dev/null +++ b/en/index.md @@ -0,0 +1,97 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: EasyTier + text: Powered by Rust and Tokio + tagline: ✨ A simple, secure, decentralized networking solution + image: + light: '/gui-config-light.png' + dark: '/gui-config-dark.png' + alt: 'Easytier GUI configuration interface' + actions: + - theme: brand + text: Get Started + link: /en/guide/introduction + - theme: alt + text: Download + link: /en/guide/download + - theme: alt + text: Web Console + link: https://easytier.cn/web + - theme: sponsor + text: 💚 Sponsor + link: /#sponsor + +features: + - title: Decentralized + details: Nodes are equal and independent, no centralized services required.
No distinction between client/server. + link: /en/guide/network/decentralized-networking + - title: Easy to Use + details: Web, client, command line multiple operation methods
Supports one-click networking + link: /en/guide/network/web-console + - title: Cross-Platform + details: Supports Win / MacOS / Linux / FreeBSD / Android
Compatible with X86 / ARM / MIPS architectures + link: /en/guide/download + - title: Secure + details: AES-GCM or WireGuard encryption
Prevents man-in-the-middle attacks + link: / + - title: Efficient NAT Traversal + details: Supports UDP, IPv6 traversal
Can penetrate NAT4-NAT4 networks + link: / + - title: Subnet Proxy + details: Nodes can share subnets for other nodes to access. + link: /en/guide/network/point-to-networking + - title: Intelligent Routing + details: Latency priority, automatic route selection
Provides the best network experience + link: /en/guide/network/configurations + - title: High Performance + details: Zero-copy throughout the entire link
Supports TCP / UDP / WSS / WG and other protocols + link: /en/guide/perf + - title: UDP Loss Resistance + details: KCP / QUIC proxy
Optimizes latency and bandwidth in high packet loss environments + link: /en/guide/network/kcp-proxy +--- + +## Related Links + +- QQ Group: [949700262](https://qm.qq.com/q/wFoTUChqZW) +- Telegram: https://t.me/easytier +- Discord:https://discord.gg/yRCkdu8brx + +## Acknowledgments + + + +## Sponsor {#sponsor} + +If you find EasyTier helpful, please consider sponsoring us. + +Software development and maintenance require a lot of time and effort, and your sponsorship will help us better maintain and improve EasyTier. + +
+
+ WeChat +
+
+ Alipay +
+
+ + diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..5d5a0e2 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,10 @@ +// @ts-check +import antfu from '@antfu/eslint-config' + +export default antfu({ + formatters: true, + rules: { + 'style/eol-last': ['error', 'always'], + }, + ignores: [], +}) diff --git a/guide/aboutp2p.md b/guide/aboutp2p.md new file mode 100644 index 0000000..4370e12 --- /dev/null +++ b/guide/aboutp2p.md @@ -0,0 +1,220 @@ +# 什么是 P2P ? + +P2P( Peer-to-Peer), 也称点对点或对等网络,在 Easytier 中指直连(直接连接)。 + +传统网络架构:客户端-服务端,在 Easytier 中指中转/中继(relay)。 + +对等网络架构:只有节点,节点可以是服务端也可以是客户端。 + +相较于传统网络架构,P2P 的优势是什么: + +1.成本低:无需购买额外的服务器,大幅度节约联机成本。 + +2.延迟低:不过经过服务器转发数据,有效降低联机延迟。 + +3.带宽高:网络带宽取决于节点的上限。 + +# P2P 和 NAT 有什么关系,具体有什么影响? + +NAT 类型决定了你是否可以和其他用户建立 P2P 连接,建立 P2P 连接之后,通常可以有效的降低网络延迟,网络带宽较高。 + +当然即使无法建立 P2P 连接,您依然可以通过 Easytier 的 中转/中继/relay 功能进行联机,通常网络延迟较高,网络带宽较低。 + +## 建立 P2P 连接难度表 + +| NAT 类型 | 开放型互联网 | 对称型防火墙 | 完全圆锥型 NAT | 受限圆锥型 NAT | 端口受限圆锥型 NAT | 对称型递增 NAT | 对称型 NAT | +| :--------------------: | :----------: | :----------: | :------------: | :------------: | :----------------: | :------------: | :--------: | +| **开放型互联网** | 容易 | 容易 | 容易 | 容易 | 容易 | 容易 | 容易 | +| **对称型防火墙** | 容易 | 简单 | 简单 | 简单 | 简单 | 简单 | 简单 | +| **完全圆锥型 NAT** | 容易 | 简单 | 简单 | 简单 | 简单 | 简单 | 简单 | +| **受限圆锥型 NAT** | 容易 | 简单 | 容易 | 中等 | 中等 | 中等 | 中等 | +| **端口受限圆锥型 NAT** | 容易 | 简单 | 容易 | 中等 | 中等 | 中等 | 中等 | +| **对称型递增 NAT** | 容易 | 简单 | 容易 | 中等 | 中等 | 困难 | 困难 | +| **对称型 NAT** | 容易 | 简单 | 容易 | 中等 | 中等 | 困难 | 极难 | + +说明:NAT 类型仅决定了 P2P 的难度,无法保证 P2P 连接的网络质量,此外无论是哪种类型都无法 100% 保证可以建立 P2P 连接! + +注:家用宽带的绝大多数 NAT 类型为:端口受限圆锥型 NAT、对称型递增 NAT、对称型 NAT 和对称型防火墙(仅IPv6)。 + +​ 移动网络(手机卡/移动数据)的绝大多数 NAT 类型为:对称型 NAT 和对称型防火墙(仅IPv6)。 + +# 什么是 NAT 和 NAPT ? + +NAT(Network Address Translation)网络地址转换,主要用于实现位于内部网络的主机访问外部网络的功能。当局域网内的主机需要访问外部网络时,通过 NAT 技术可以将其私网地址转为公网地址,并且多个私网用户可以共用一个公网地址,这样既可保证网络互通,又节省了公网地址。 + +NAPT(Network Address Port Translation)也称为 NAT-PT 或 PAT,网络地址端口转换,允许多个私网地址映射到同一个公网地址的不同端口;通常是企业,家庭上网的默认方式。 + + +# 什么是 NAT 类型?NAT 类型有哪些,它们有什么区别? +NAT 的类型决定了外部主机如何与内部主机建立连接,以下为所有的 NAT 类型极其特点: + +**Open Internet(开放型互联网/公网/直接映射+端点无关过滤)** + +该类型不使用 NAT,地址为公网 IP,可以直接被其他用户连接,例如: +IPv4 +120.120.120.120:25565 ← 111.111.111.111:x(x代表任意端口) +120.120.120.120:25565 ← 222.222.222.222:x(x代表任意端口) +120.120.120.120:25565 ← 333.333.333.333:x(x代表任意端口) +120.120.120.120:25565 ← 444.444.444.444:x(x代表任意端口) + +IPv6 +[240e:3fd8:256a:3367::1]:25565 ← [240e::1]:x(x代表任意端口) +[240e:3fd8:256a:3367::1]:25565 ← [240e::2]:x(x代表任意端口) +[240e:3fd8:256a:3367::1]:25565 ← [240e::3]:x(x代表任意端口) +[240e:3fd8:256a:3367::1]:25565 ← [240e::4]:x(x代表任意端口) + +该类型常见于防火墙放行的公网 IP 或者无防火墙的公网IP。 + +**Symmetric Firewall(对称型防火墙/直接映射+地址和端口相关过滤)** + +该类型和 Open Internet 相同,但其所在的设备有防火墙对入站进行过滤,此时其他用户无法直接连接该端口,例如: +IPv4 +120.120.120.120:25565 ↚ 111.111.111.111:x(x代表任意端口) +120.120.120.120:25565 ↚ 222.222.222.222:x(x代表任意端口) +120.120.120.120:25565 ↚ 333.333.333.333:x(x代表任意端口) +120.120.120.120:25565 ↚ 444.444.444.444:x(x代表任意端口) + +IPv6 +[240e:3fd8:256a:3367::1]:25565 ↚ [240e::1]:x(x代表任意端口) +[240e:3fd8:256a:3367::1]:25565 ↚ [240e::2]:x(x代表任意端口) +[240e:3fd8:256a:3367::1]:25565 ↚ [240e::3]:x(x代表任意端口) +[240e:3fd8:256a:3367::1]:25565 ↚ [240e::4]:x(x代表任意端口) + +该类型常见于有防火墙的公网 IP,但可以通过打洞进行连接,能否直连取决于防火墙的策略。 + +**No Pat NAT(Basic NAT/基础 NAT/端点无关映射+端点无关过滤)** + +该类型只进行地址转换,端口保持一致,例如: +IPv4 +192.168.1.1:25565 ← 120.120.120.120:25565 ← 111.111.111.111:x(x代表任意端口) +192.168.1.1:25565 ← 120.120.120.120:25565 ← 222.222.222.222:x(x代表任意端口) +192.168.1.1:25565 ← 120.120.120.120:25565 ← 333.333.333.333:x(x代表任意端口) +192.168.1.1:25565 ← 120.120.120.120:25565 ← 444.444.444.444:x(x代表任意端口) + +IPv6 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:25565 ← [240e::1]:x(x代表任意端口) +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:25565 ← [240e::2]:x(x代表任意端口) +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:25565 ← [240e::3]:x(x代表任意端口) +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:25565 ← [240e::4]:x(x代表任意端口) + +**Full Cone NAT(完全圆锥型 NAT/端点无关映射+端点无关过滤/NAT1)** + +该类型会将地址和端口都进行转换,例如: +IPv4 +192.168.1.1:25565 ← 120.120.120.120:35565 ← 111.111.111.111:x(x代表任意端口) +192.168.1.1:25565 ← 120.120.120.120:35565 ← 222.222.222.222:x(x代表任意端口) +192.168.1.1:25565 ← 120.120.120.120:35565 ← 333.333.333.333:x(x代表任意端口) +192.168.1.1:25565 ← 120.120.120.120:35565 ← 444.444.444.444:x(x代表任意端口) + +IPv6 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ← [240e::1]:x(x代表任意端口) +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ← [240e::2]:x(x代表任意端口) +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ← [240e::3]:x(x代表任意端口) +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ← [240e::4]:x(x代表任意端口) + +该类型的策略是:我在内网中开放了25565这个端口,任何用户都可以通过转换后的公网端口35565连接进来。 + +**Restricted Cone NAT(受限圆锥型 NAT/端点无关映射+地址有关过滤/NAT2)** + +该类型在 Full Cone NAT 基础上限制了其他用户的 IP 地址,例如: +IPv4 +192.168.1.1:25565 ← 120.120.120.120:35565 ← 111.111.111.111:x(x代表任意端口) +192.168.1.1:25565 ← 120.120.120.120:35565 ↚ 222.222.222.222:x(x代表任意端口) +192.168.1.1:25565 ← 120.120.120.120:35565 ↚ 333.333.333.333:x(x代表任意端口) +192.168.1.1:25565 ← 120.120.120.120:35565 ↚ 444.444.444.444:x(x代表任意端口) + +IPv6 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ← [240e::1]:x(x代表任意端口) +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ↚ [240e::2]:x(x代表任意端口) +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ↚ [240e::3]:x(x代表任意端口) +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ↚ [240e::4]:x(x代表任意端口) + +该类型的策略是:我在内网中开放了25565这个端口,只有我指定的 IP 地址 才可以通过转换后的公网端口35565连接进来。 +其中(111.111.111.111和240e::1)为我指定的 IP 地址。 + +**Port Restricted Cone NAT(端口受限圆锥型 NAT/端点无关映射+地址和端口有关过滤/NAT3)** + +该类型在 Restricted Cone NAT 基础上限制了其他用户的端口号,例如: +IPv4 +192.168.1.1:25565 ← 120.120.120.120:35565 ← 111.111.111.111:11010 +192.168.1.1:25565 ← 120.120.120.120:35565 ↚ 111.111.111.111:22020 +192.168.1.1:25565 ← 120.120.120.120:35565 ↚ 222.222.222.222:x(x代表任意端口) +192.168.1.1:25565 ← 120.120.120.120:35565 ↚ 333.333.333.333:x(x代表任意端口) + +IPv6 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ← [240e::1]:11010 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ↚ [240e::1]:22020 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ↚ [240e::2]:x(x代表任意端口) +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ↚ [240e::3]:x(x代表任意端口) + +该类型的策略是:我在内网中开放了25565这个端口,只有我指定的 IP 地址+端口号 才可以通过转换后的公网端口35565连接进来。 +其中(111.111.111.111:11010和[240e::1]:11010)为我指定的 IP 地址+端口号。 + +**Symmetric Easy Increase NAT(对称型递增 NAT/地址和端口相关映射+地址和端口有关过滤/NAT4E)** + +该类型在 Port Restricted Cone NAT 基础上限制了映射行为,但映射的端口是有规律的,例如: +IPv4 +192.168.1.1:25565 ← 120.120.120.120:35565 ← 111.111.111.111:11010 +192.168.1.1:25565 ← 120.120.120.120:35566 ← 222.222.222.222:22020 +192.168.1.1:25565 ← 120.120.120.120:35567 ↚ 111.111.111.111:33030 +192.168.1.1:25565 ← 120.120.120.120:35568 ↚ 333.333.333.333:x(x代表任意端口) + +IPv6 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35565 ← [240e::1]:11010 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35567 ← [240e::2]:22020 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35569 ↚ [240e::1]:33030 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:35571 ↚ [240e::3]:x(x代表任意端口) + +其中(111.111.111.111:11010、222.222.222.222:11010、[240e::1]:11010、[240e::2]:22020)为我指定的 IP 地址+端口号。 + +**Symmetric NAT(对称型 NAT/地址和端口相关映射+地址和端口有关过滤/NAT4)** + +该类型在 Symmetric Easy Increase NAT 基础上限制了映射的端口,该端口随机,例如: +IPv4 +192.168.1.1:25565 ← 120.120.120.120:66534 ← 111.111.111.111:11010 +192.168.1.1:25565 ← 120.120.120.120:32768 ← 222.222.222.222:22020 +192.168.1.1:25565 ← 120.120.120.120:26984 ↚ 111.111.111.111:33030 +192.168.1.1:25565 ← 120.120.120.120:16489 ↚ 333.333.333.333:x(x代表任意端口) + +IPv6 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:55645 ← [240e::1]:11010 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:32478 ← [240e::2]:22020 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:43269 ↚ [240e::1]:33030 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:11443 ↚ [240e::3]:x(x代表任意端口) + +其中(111.111.111.111:11010、222.222.222.222:11010、[240e::1]:11010、[240e::2]:22020)为我指定的 IP 地址+端口号。 + +**Blocked(阻止型)** + +该类型的防火墙非常严格,任何用户都无法连接,例如: +IPv4 +192.168.1.1:25565 ← 120.120.120.120:66534 ↚ 任意 IP +任意端口 + +IPv6 +[fd00::1]:25565 ← [240e:3fd8:256a:3367::1]:55645 ↚ 任意 IP +任意端口 + +# 什么是打洞? + +**首先我们要理解有状态防火墙:** + +我们都知道防火墙可以入站和出站,但连接和“方向”是协议设计者想象的产物。 + +在实际操作中,每个连接最终都是双向的;所有数据包都会来回传输。 + +那防火墙如何知道什么是入站,什么是出站呢? + +这就是状态部分的作用,有状态防火墙的核心是维护一个“状态表”(State Table),记录所有经过的连接的状态信息。当数据包到达时,防火墙不仅检查数据包的头部信息(如源/目的IP、端口、协议),还会检查这个数据包是否属于一个已经建立的、受信任的连接。 + +绝大多数情况下,NAT 设备都会包含一个有状态防火墙,例如上面提到的**对称型防火墙**,对于绝大多数的防火墙的规则来说,当 A 向 B 的 IP+指定端口发送数据后,即使 B 的防火墙丢弃了该数据包,但只要 B 通过该 IP+端口发送数据回应 A 的请求,那么 B 的数据就可以通过 A 的防火墙,如下图所示: + +![A向B发送数据](/assets/cn/aboutp2p1.png) + +![B向A发送回应](/assets/cn/aboutp2p2.png) + +![通过防火墙后](/assets/cn/aboutp2p3.png) + +我们可以看到 A 和 B 之间在防火墙之间建立了一个连接,而防火墙上面出现了一个“洞”,这就是打洞的由来。 + +# 为什么我无法建立 P2P 连接? + +通过对 NAT 和打洞的了解,在仅有 NAT 的情况下,建立 P2P 的几率几乎为100%,但我们都知道几乎所有设备都有防火墙,只要运营商将其防火墙的规则设置严格一些,那我们就无法建立 P2P 连接。 diff --git a/guide/aboutstateofInternet.md b/guide/aboutstateofInternet.md new file mode 100644 index 0000000..ce37a76 --- /dev/null +++ b/guide/aboutstateofInternet.md @@ -0,0 +1,85 @@ +## 目前的网络环境 + +运营商目前会对跨省、跨网的流量做出以下策略: + +1.限制上传带宽:例如我所在的地区会将上行带宽限速至300kb/s,这点非常影响 MC 用户、串流用户和 NAS 用户。 + +2.丢包:例如我所在的地区会固定发4丢1,也就是丢包25%,非常影响游戏用户。 + +3.增加延迟:例如我所在的地区会延迟翻倍,上海到北京应该是20ms,但现在是40ms,非常影响游戏用户。 + +::: danger 警告 +以上策略会极大的影响 Easytier 建立的 P2P 连接质量!!! +::: + +### 其他一些常见的策略: + +1.分时间段限制:会出现以上3中提到的策略,比如17:30-23:30可能会出现以上3种情况,也可能只出现其中1种情况。 + +2.限制连接数:运营商会限制已建立的连接数量,比如限制到 1024 个连接,在设备比较多的情况下会无法建立新连接从而导致断网。 + +3.限制 P2P 连接:运营商会限制打洞行为,例如即使有一方为 NAT1 也无法和 NAT4 建立 P2P 连接。 + +4.限制流量:例如限制每日上传的流量为 50GB。 + +5.根据协议进行限制:例如 UDP 协议的流量会严格限制,TCP 策略会稍好一些,这点非常影响目前 Easytier 建立 P2P 之后的连接质量。 + +### 关于跨国流量 + +由于一些特殊原因,跨国流量长期存在以上问题,甚至会更加严重。 + +::: warning 注意 +以上仅为我所在的地区运营商策略,不通地区的策略会有出入,很大出有些地区不限制,有些地区会限制的很严格。 +::: + +::: danger 警告 +某些地区即使你在非限制时段,同城,同运营商的情况下,如果您的上传流量超出运营商的限制,依然会对您的宽带做出如上限制,这是运营商得底线!!! +::: + +## 那么影响了什么呢? + +### 游戏用户: + +拿我的世界(MC)举例,MC 的整合包对带宽要求较高,本来高延迟和丢包体验已经很差,限速的话甚至会无法联机,比如经典的连接中断错误。 + +对带宽没有要求的游戏体验也会很差。 + +### NAS 用户: + +虽然 NAS 用户对延迟不敏感,但是限速会极大的影响使用体验,传输文件时往往要花费非常长的时间来进行传输,并且丢包问题会导致传输的文件损坏。 + +### 串流用户: + +一般串流用户对画质和帧率有着较高的要求,这就需要较大的带宽,往往会较为严重的造成卡顿、掉线等问题。 + +## 什么是跨省、跨网? + +跨省指的是流量在不同的省份之间进行传输,例如河南省——河北省。 + +跨网指的是流量在不同的运营商之间进行传输,例如中国电信——中国联通。 + +## 为什么协议会影响 P2P 的连接质量? + +我们都知道 UDP 很快,但由于 UDP 是无连接的,运营商很讨厌这个协议,相对而言 TCP 就很“听话”了,所以运营商往往会对 UDP 协议进行严格限制,由于目前 Easytier 只有 UDP 打洞,所以体验会非常糟糕。 + +## 有什么解决方法吗? + +1.使用 TCP 协议进行传输,但实际情况是 TCP 也会限制,只是没有 UDP 那么夸张。 + +2.通过服务器进行中转,在 Easytier 中选择禁用 P2P 或选择仅中继/仅中转/强制中继/强制中转这类选项。 + +::: warning 注意 +公共服务器带宽较低,如果您需要传输大量数据,需自行购买大带宽服务器,如 NAS 用户,串流用户,MC 整合包用户,否则体验会非常差。 +::: + +3.找运营商花钱办理不限速套餐,这类套餐往往价格很高。 + +4.投诉或起诉运营商,例如工信部,有能力的可以更上一层楼。 + +5.跨国用户需要办理运营商的特殊套餐或通过其他方式进行组网/联机。 + +## 为什么网络环境会变成这样? + +说白了还是钱的问题,某些大厂,典型的如视频类软件、网盘类软件甚至一些其他的软件会通过“花小钱办大事”或者“偷取”用户的上传带宽来避免购买运营商的服务器以节约成本,运营商当然不愿意了,本来就是商业宽带补贴家用宽带,这样一搞运营商会亏钱,所以才造成了今天这个局面。 + +详情可以参考这篇文章:[关于跨网爆炸那些事](https://blog.sunflyer.cn/archives/1208) diff --git a/guide/community-and-contribution.md b/guide/community-and-contribution.md index 30014de..301374e 100644 --- a/guide/community-and-contribution.md +++ b/guide/community-and-contribution.md @@ -1,5 +1,13 @@ + + # 社区和贡献 -我们欢迎并鼓励社区贡献!如果你想参与进来,请提交 [GitHub PR](https://github.com/KKRainbow/EasyTier/pulls)。 +我们欢迎并鼓励社区贡献!如果你想参与进来,请提交 [GitHub PR](https://github.com/EasyTier/EasyTier/pulls)。 + +详细的贡献指南可以在 [Contributing](https://github.com/EasyTier/EasyTier/blob/main/CONTRIBUTING.md) 中找到。 + +## Star 历史 -详细的贡献指南可以在 [Contributing](https://github.com/KKRainbow/EasyTier/blob/main/CONTRIBUTING.md) 中找到。 \ No newline at end of file + diff --git a/guide/config/acl.md b/guide/config/acl.md new file mode 100644 index 0000000..31673fe --- /dev/null +++ b/guide/config/acl.md @@ -0,0 +1,220 @@ +# Easytier ACL 功能指南 🛡️ + +## 📖 目录 + +1. [核心概念](#-核心概念) +2. [工作原理](#-工作原理) +3. [配置详解](#-配置详解) +4. [常用场景示例](#-常用场景示例) +5. [最佳实践与注意事项](#-最佳实践与注意事项) + +--- + +## 🧠 核心概念 + +理解以下几个关键概念是配置 ACL 的基础: + +- **ACL(访问控制列表)**:一组规则的集合,用于允许或拒绝网络流量。Easytier 自 2.4.0 版本起支持基于 IP 的 ACL,2.4.3 版本新增基于身份组(Group)的零信任 ACL 机制,策略配置更加灵活。 +- **链(Chain)**:规则的集合。链有不同的类型,例如 `chain_type = 1` 表示入站链,用于处理发送到本机的流量。 +- **规则(Rule)**:定义流量匹配条件(如协议、端口、源/目标组)及匹配后的执行动作(允许或拒绝)。 +- **组(Group)**:一个逻辑上的身份标签。节点(服务器、电脑等)可以声明自己属于一个或多个组(如 `admin`、`database`)。 +- **组声明(Group Declaration)**:节点需预先知道所有可能通信的组名及其对应的共享密钥,密钥用于验证其他节点声明的组成员身份是否有效。 +- **优先级(Priority)**:规则按优先级数值从高到低匹配。一旦流量匹配某条规则,将执行其动作并停止后续匹配。 + +--- + +## 🔧 配置详解 + +ACL 配置需添加到 Easytier 的配置文件 `config.yaml` 中。 + +### 1. 定义组与密钥 + +这是最关键的一步。每个节点需在配置中声明其所属的组,并配置所有相关组的共享密钥。 + +```yaml +# 本节定义本节点要加入的组(用于生成身份证明) +[acl.acl_v1.group] +members = ["admin", "web-server"] # 本节点身份:既是管理员,也是Web服务器 + +# 本节定义本节点“已知”的所有组及其密钥(用于验证对方身份) +[[acl.acl_v1.group.declares]] +group_name = "admin" +group_secret = "super-secret-admin-key" # 请使用更复杂的密钥! + +[[acl.acl_v1.group.declares]] +group_name = "web-server" +group_secret = "web-server-secret-key" + +[[acl.acl_v1.group.declares]] +group_name = "database" +group_secret = "database-secret-key" + +[[acl.acl_v1.group.declares]] +group_name = "guest" +group_secret = "guest-secret-key" +``` + +> **⚠️ 重要提示**: +> - `members`:定义本节点的身份。 +> - `declares`:相当于节点的“通讯录”,必须包含网络中所有可能通信的组的定义。所有节点的 `declares` 必须完全一致。 +> - `group_secret`:是安全的核心,必须使用高强度、独一无二的密钥,且所有节点共享相同的密钥定义。 + +--- + +### 2. 配置规则链 (Chain) + +规则链决定了如何处理流量。 + +```yaml +# 定义一个入站链 +[[acl.acl_v1.chains]] +name = "my_acl_policy" # 链名称 +chain_type = 1 # 0:未指定,1:入站链(处理发给本机的流量),2:出站(处理本机发出的流量),3:转发(子网代理) +description = "我的安全策略" +enabled = true # 启用此链 +default_action = 2 # 默认动作:1(允许) 2(拒绝) +``` + +--- + +### 3. 配置规则 (Rules) + +规则是策略的核心,定义在链内部。 + +```yaml +# 上面定义的链中的规则列表 +[[acl.acl_v1.chains.rules]] +name = "allow_admin_rdp" +description = "允许管理员通过RDP连接本机" +priority = 1000 # 优先级,数字越大优先级越高(0-65535) +action = 1 # 动作:0(无操作) 1(允许) 2(拒绝) +source_groups = ["admin"] # 规则匹配:源设备必须属于 admin 组 +protocol = 1 # 协议:0(未指定) 1(TCP) 2(UDP) 3(ICMP) 4(ICMPv6) 5(任何) +ports = ["3389"] # 本机允许端口:3389(RDP) +enabled = true # 启用此规则 + +[[acl.acl_v1.chains.rules]] +name = "deny_guest_to_db" +description = "拒绝访客访问数据库" +priority = 900 # 优先级较低 +action = 2 # 动作:0(无操作) 1(允许) 2(拒绝) +source_groups = ["guest"] # 规则匹配:源设备必须属于 guest 组 +destination_groups = ["database"] # 目标设备属于 database 组时匹配 +enabled = true # 启用规则 +protocol = 1 # TCP协议 +ports = [] # 留空,则匹配所有端口 +source_ips = [] # 留空,则匹配所有源IP范围;格式 `10.144.144.2/32` +destination_ips = [] # 留空,则匹配所有目标IP范围 +source_ports = [] # 留空,则匹配所有源端口 +rate_limit = 0 # 速率限制(bps),0 = 无限制 +burst_limit = 0 # 突发的最高带宽(bps) +stateful = true # 启用连接跟踪 +``` + +--- + +## 🧪 常用场景示例 + +### 场景 1:极简安全组 - “密钥相同即放行” + +**目标**:实现一个私有网络,只要设备拥有相同的密钥,就能互通;没有密钥的设备无法接入。 + +**配置**: + +```yaml +[acl.acl_v1.group] +members = ["my-net"] # 所有设备都加入同一个组 + +[[acl.acl_v1.group.declares]] +group_name = "my-net" +group_secret = "my-net-secret-key" # 所有设备使用相同密钥 + +[[acl.acl_v1.chains]] +name = "default_inbound" +chain_type = 1 +enabled = true +default_action = 2 # 默认拒绝 + +# 核心规则:允许组内所有通信 +[[acl.acl_v1.chains.rules]] +name = "allow_whole_group" +description = "允许组内所有流量" +priority = 1000 +action = 1 +source_groups = ["my-net"] # 源是组内成员 +destination_groups = ["my-net"] # 目标是组内成员 +protocol = 1 +enabled = true +``` + +--- + +### 场景 2:三层网络架构 + +**目标**:模拟经典 Web-DB 架构,只允许 Web 服务器访问数据库的特定端口。 + +**节点配置**: + +- Web 服务器:`members = ["web"]` +- 数据库服务器:`members = ["db"]` +- 管理员机器:`members = ["admin"]` + +**数据库服务器上的 ACL 规则**: + +```yaml +[[acl.acl_v1.chains]] +name = "db_server_policy" +chain_type = 1 +enabled = true +default_action = 2 # 默认拒绝所有连接 + +# 规则 1: 允许Web服务器访问数据库端口 +[[acl.acl_v1.chains.rules]] +name = "allow_web_to_mysql" +description = "允许Web组访问MySQL" +priority = 100 +action = 1 +source_groups = ["web"] +destination_groups = ["db"] # 目标是本机(db组) +protocol = 1 +ports = ["3306"] # 只开放MySQL端口 +enabled = true + +# 规则 2: 允许管理员访问所有端口(例如用于管理) +[[acl.acl_v1.chains.rules]] +name = "allow_admin_to_all" +description = "允许管理员访问所有服务" +priority = 110 # 优先级比上一条更高 +action = 1 +source_groups = ["admin"] +destination_groups = ["db"] # 目标是本机(db组) +protocol = 1 +enabled = true +``` + +--- + +## ✅ 最佳实践与注意事项 + +1. **密钥管理**: + - **重要性**:`group_secret` 是安全的基石,一旦泄露,攻击者可随意加入您的网络。 + - **建议**:使用密码生成器创建长且复杂的密钥,并定期更换。 + - **安全存储**:不要将配置文件提交到公共代码仓库。 + +2. **配置一致性**: + - 确保网络中所有节点的 `[[acl.acl_v1.group.declares]]` 部分完全一致(相同的组名和密钥),否则会导致组验证失败,网络不通。 + +3. **调试技巧**: + - 从宽松策略(`default_action = 1`)开始,搭配日志确认网络连通性。 + - 逐步添加拒绝规则(`action = 2`)来限制访问。 + - 最后,改为 `default_action = 2` ,形成“白名单”模式。 + - 充分利用 `description` 字段,为每个规则添加清晰注释,便于后期维护。 + - `easytier-cli acl stats` 命令可查看 ACL 统计信息。 + +4. **操作顺序**: + - 修改 ACL 配置后,通常需要重启 Easytier 进程才能生效。 + - 使用 `easytier-core -d --tcp-whitelist 端口号` 等命令可以自动生成简单的端口白名单规则,可作为学习起点。 + +--- + +希望这份文档能帮助您更好地理解和使用 Easytier 的 ACL 功能!如有任何问题,欢迎在社区讨论。🎉 \ No newline at end of file diff --git a/guide/contact.md b/guide/contact.md index b52cdde..bcb524b 100644 --- a/guide/contact.md +++ b/guide/contact.md @@ -1,6 +1,6 @@ # 联系方式 -- 提问或报告问题:[GitHub Issues](https://github.com/KKRainbow/EasyTier/issues) -- 讨论和交流:[GitHub Discussions](https://github.com/KKRainbow/EasyTier/discussions) +- 提问或报告问题:[GitHub Issues](https://github.com/EasyTier/EasyTier/issues) +- 讨论和交流:[GitHub Discussions](https://github.com/EasyTier/EasyTier/discussions) - QQ 群: [949700262](https://qm.qq.com/q/LDxBN5L3kA) -- Telegram:https://t.me/easytier \ No newline at end of file +- Telegram:https://t.me/easytier diff --git a/guide/download.md b/guide/download.md new file mode 100644 index 0000000..5a9e767 --- /dev/null +++ b/guide/download.md @@ -0,0 +1,212 @@ +--- +home: hello +--- + + + +# 下载 { #download } + +您可以直接前往 [GitHub Release 页面](https://github.com/EasyTier/EasyTier/releases) 查看所有版本的下载链接,或者使用下面的表格查找适合您的版本。 + +命令行程序的压缩包中包含四个可执行程序: + +- `easytier-core`:EasyTier 的核心程序 +- `easytier-cli`:EasyTier 管理程序,启动 easytier-core 后,可以使用 easytier-cli 查看虚拟网信息 +- `easytier-web`: 用于自建 EasyTier 的 Web 控制台后端,一般情况下无需自建,使用官方提供的 Web 控制台即可 +- `easytier-web-embed`: 与 `easytier-web` 功能相同,但内置了 Web 前端。 + +## EasyTier v{{ version }} { #latest } + +- Github 加速 +
+ +
+ +- 根据操作系统筛选 +
+ +
+ +- 根据硬件架构筛选 +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
操作系统 硬件架构 图形界面程序 GUI 命令行程序 CLI 注意事项
{{ pkg.os }} {{ pkg.arch }} + +{{format}} + + + +{{format}} + + +{{ pkg.comment }} +
diff --git a/guide/faq.md b/guide/faq.md new file mode 100644 index 0000000..63c7ab8 --- /dev/null +++ b/guide/faq.md @@ -0,0 +1,39 @@ +# 常见问题 {#faq} + +--- + +**Q:Windows 7 无法创建网络,程序崩溃或者报错无法创建虚拟网?** + +**A:** Windows 7 需为 SP1 及以上版本,并安装以下补丁: +- [KB3063858](https://www.microsoft.com/en-us/download/details.aspx?id=47409) +- [KB4474419](https://www.catalog.update.microsoft.com/search.aspx?q=KB4474419) + +--- + +**Q:Linux 命令行帮助是英文,如何切换为中文?** + +**A:** 设置环境变量 `LANG=zh_CN`,命令如下: + +```bash +export LANG=zh_CN +``` + +--- + +**Q:启动后提示 TunError 怎么办?** + +**A:** 请确认 TUN 驱动已正确加载,并且 `/dev/net/tun` 文件存在。如果在 Docker 中运行,请确保开启特权模式。Linux 下加载 TUN 驱动的命令如下: + +```bash +modprobe tun +mkdir -p /dev/net +sudo mknod /dev/net/tun c 10 200 +``` + +--- + +**Q:启动后报错 `Address already in use`?** + +**A:** 可能是端口冲突。请检查 11010 端口或启动参数指定的端口(如 `-l tcp:12345`)是否被其他程序占用。 + +--- diff --git a/guide/gui/astral-game.md b/guide/gui/astral-game.md new file mode 100644 index 0000000..9117950 --- /dev/null +++ b/guide/gui/astral-game.md @@ -0,0 +1,29 @@ +# AstralGame 游戏联机工具 +[![GitHub](https://img.shields.io/badge/GitHub-AstralET-blue)](https://github.com/ldoubil/astral) +[![AstralGame](https://img.shields.io/badge/wiki-AstralGame-blue)](https://astral.fan/) + +## 预览 +![manage-step1](/assets/AstralET1.png) +![manage-step1](/assets/AstralET2.png) +![manage-step1](/assets/AstralET3.png) +![manage-step1](/assets/AstralET4.png) +![manage-step1](/assets/AstralET5.png) + +## 使用教程 +- https://astral.fan/quick-start/what-is-astral/ + + +## 介绍 + +AstralGame 是一款基于 ​**Flutter** 和 ​**EasyTier** 开发的轻量级游戏联机工具,旨在为玩家提供简单、高效的联机体验。 + +## 特性 + +- ​**内置 EasyTier**:将 EasyTier 直接编译到 AstralGame 中,无需额外安装,也不会保留任何后台进程。 +- ​**即开即用**:联机时启动应用,结束后关闭即可,操作简单便捷。 +- ​**活跃维护**:作者积极更新,随时修复问题并优化功能(上班摸鱼成果)。 + +## 联系我们 & 功能建议 + +- ​**QQ 群**: [点击加入 QQ 群](https://qm.qq.com/q/r4VsExDDt6) +- ​**GitHub Issues**: [提交问题或建议](https://github.com/ldoubil/astral/issues) diff --git a/guide/gui/basic.md b/guide/gui/basic.md new file mode 100644 index 0000000..9c3b401 --- /dev/null +++ b/guide/gui/basic.md @@ -0,0 +1,11 @@ +# 公共服务器组网 + +GUI 默认使用官方共享节点组网,方便没有公网 IP 的朋友组网。大部分情况可以打洞 P2P 成功,若无法 P2P 成功,节点间带宽可能会比较低。 + +配置方法如图所示。 + +![配置界面](/assets/cn/config.png) + +配置完成后点击运行网络按钮即可,运行网络成功后的界面如图 + +![running](/assets/cn/running.png) diff --git a/guide/gui/easytier-game.md b/guide/gui/easytier-game.md new file mode 100644 index 0000000..aad2113 --- /dev/null +++ b/guide/gui/easytier-game.md @@ -0,0 +1,44 @@ +# [EasyTier 游戏联机启动器](https://github.com/EasyTier/EasytierGame) + +## 简介 + +EasyTierGame 游戏联机启动器,由`nuxt3` `typescript` `rust` `tauri` 开发 +具有简易的界面,附带最新版 easytier 内核,联机游玩的时候无论是心理上和使用上都能给予您最舒服的体验,同时支持自定义配置文件启动,满足各种需求 + +## 下载 + +Github +Releases: [https://github.com/EasyTier/EasytierGame/releases](https://github.com/EasyTier/EasytierGame/releases) + +- 只有绿色zip包,个人不喜欢安装包各种写注册表,解压即用就行,目录清爽干净 + +![game-step1](/assets/game-step1.png) + +## 使用教程 + +- 第一次使用,输入一个“主机名”点击启动联机即可,后续可以自建服务器或者使用爱心群友提供的服务器 + ![game-step2](/assets/game-step2.png) + +![game-step3](/assets/game-step3.png) + +- 高级选项里有一些特殊配置,可以自行选择 + ![game-step4](/assets/game-step4.png) + +- 如果还是无法满足您的需求,可以使用配置文件进行启动,具体如何配置,可以查看文档[配置文件](/guide/network/config-file.html) + ![game-step5](/assets/game-step5.png) + +- easytier内核升级后,可以点击更新插件按钮就可以进行更新,但是需要出国,如果无法更新,可以在群里获取 + ![game-step6](/assets/game-step6.png) + +## 特性 + +- 基于easytier组网工具开发,界面清晰简单 +- 自带“更新”按钮,在easytier组网工具发布新版本时,点击更新即可(需要出国工具) +- 第一次使用,输入一个“主机名”点击启动联机即可,后续可以自建服务器或者使用群友的服务器 +- 配置简单,也含有高级功能,同时也支持自定义配置文件启动 +- 默认开启 **WinIPBroadcast** 不再怕联机找不到房间(例如:无主之地3) +- 已经测试 **艾尔登法环学习版**,**无主之地3**,**深岩银河**,**怪物猎人世界** 等都可稳定联机游玩 + +## 系统支持 + +支持Windows 11 、Windows 10 、 Windows 7 diff --git a/guide/gui/easytier-manager.md b/guide/gui/easytier-manager.md new file mode 100644 index 0000000..6935f1c --- /dev/null +++ b/guide/gui/easytier-manager.md @@ -0,0 +1,75 @@ +# [EasyTier 管理器](https://github.com/xlc520/easytier-manager) + +## 下载 + +Github +Releases: [https://github.com/xlc520/easytier-manager/releases](https://github.com/xlc520/easytier-manager/releases) + +#### 各个包说明 + +- `exe`:安装程序,安装后才可使用 +- `zip`:免安装,解压即可使用 +- `easytier-manager-win_2.0.0.exe`:64、32位 Windows 系统通用安装包 +- `easytier-manager-win-x64_2.0.0.exe`:64位 Windows 系统安装包 +- `easytier-manager-win-ia32_2.0.0.exe`:32位 Windows 系统安装包 +- `easytier-manager-win7-x64_2.0.0.exe`: 64位 Windows 7 系统安装包 +- `tar.gz` `deb` `rpm` `AppImage`:Linux系统上使用(尚未测试) + +## 使用教程 + +- **1.【重要】设置页 检测内核是否存在,若不存在则下载内核,然后安装,再次检测内核是否存在**(仅首次使用需要,后续确认存在即可直接运行) + +![manage-step1](/assets/manage-step1.png) + +![manage-step2](/assets/manage-step2.png) + +- 2.配置页新建组网配置,提供直接编辑代码的方式,和表单填写 + +![manage-step3](/assets/manage-step3.png) + +![manage-step4](/assets/manage-step4.png) + +![manage-step5](/assets/manage-step5.png) + +![manage-step6](/assets/manage-step6.png) + +- 3.工作台(首页)运行指定配置 + +![manage-step7](/assets/manage-step7.png) + +- 4.[可选] 组网成功后,连接没有问题可退出管理器,核心程序会在后台运行(托盘图标右键`退出`) + +- 5.[可选] 在配置页面,安装指定配置为系统服务 + +![manage-step8](/assets/manage-step8.png) + +![manage-step9](/assets/manage-step9.png) + +## 介绍 + +EasyTier 管理器 整合Vue3 + Vite5 + Electron33 + Element-Plus, 是一个基于 `element-plus` +免费开源的组网管理器。使用了最新的`vue3`,`vite5`,`TypeScript` 等主流技术开发。 + +## 特性 + +- **内存占用**:组网成功后,可以直接退出管理器,不会影响组网,所以不会占用内存,不会因各种问题内存泄漏 +- **多配置启动**:支持多个组网配置运行、管理 +- **系统服务安装**:界面化一键安装为系统服务,开机自动启动 +- **可视化添加配置**:提供表单可视化添加组网配置,简单方便 +- **可视化日志查看**:首页可查看当前组网配置的日志 +- **一键下载安装**:一键下载安装内核,内置加速源,无需手动下载,下载完一键安装 +- **最新技术栈**:使用 Electron33/Vue3/vite5 等前端前沿技术开发 +- **TypeScript**: 应用程序级 JavaScript 的语言 +- **国际化**:内置完善的国际化方案 + +## Bug 反馈 & 建议 + +> 趋于稳定可能不会在开发新功能,只会修复漏洞之类的 + +可在 [TODO](https://github.com/xlc520/easytier-manager/blob/master/TODO.md) 查看是否已有记录,以免重复 + +[BUG 提交 | 需求建议](https://github.com/xlc520/easytier-manager/issues/new/choose) + +## 系统支持 + +理论支持Windows 11 、Windows 10 、 Windows 7 diff --git a/guide/gui/index.md b/guide/gui/index.md new file mode 100644 index 0000000..b268517 --- /dev/null +++ b/guide/gui/index.md @@ -0,0 +1,9 @@ +# 图形界面 GUI 组网 + +图形界面程序同样可在 GitHub Release 页面下载,对应的前缀为 easytier-gui。 + +需要注意,MacOS 在安装完毕后,需要在命令行执行如下命令,否则会误报文件已损坏。 + +```bash +xattr -c /Applications/easytier-gui.app +``` diff --git a/guide/gui/manual.md b/guide/gui/manual.md new file mode 100644 index 0000000..12c47d4 --- /dev/null +++ b/guide/gui/manual.md @@ -0,0 +1,7 @@ +# 手动组网 + +EasyTier 不区分客户端服务端,且完全去中心化,新增节点只需与虚拟网络中的任意节点建链即可加入组网。配置方法如下图所示。 + +![手动组网](/assets/cn/manual.png) + +需要注意节点 IP 输入后需要点击列表项确认,确认后节点地址以卡片形式显示。 diff --git a/guide/gui/subnet_proxy.md b/guide/gui/subnet_proxy.md new file mode 100644 index 0000000..fa0aa95 --- /dev/null +++ b/guide/gui/subnet_proxy.md @@ -0,0 +1,13 @@ +# 子网代理 + +通过设置子网代理,连通本地局域网和虚拟局域网。 + +假设家中的设备在子网 192.168.1.0/24 网段中,想在公司访问家中的任意设备,则可在家中启动一个 EasyTier 节点,新增子网代理 192.168.1.0/24。公司的设备中无需任何额外配置,只需与家中节点成功组网,即可访问家中任意设备。 + +![子网代理配置](/assets/cn/subnet.png) + +需要注意网段输入后需要点击列表项确认,确认成功后网段转为卡片形式显示。 + +子网代理可以做网段映射 + +![子网代理配置](/assets/cn/subnet-mapping.png) diff --git a/guide/gui/vpn_portal.md b/guide/gui/vpn_portal.md new file mode 100644 index 0000000..44e5b2d --- /dev/null +++ b/guide/gui/vpn_portal.md @@ -0,0 +1,11 @@ +# WireGuard 接入 + +EasyTier 中每个节点可以作为 WireGuard 的服务端,让安卓、IOS 等移动设备,轻松访问虚拟局域网中的设备。 + +配置方法如图。 + +![Wireguard Portal Config](/assets/cn/portal.png) + +点击组网成功页面的 《显示 WireGuard 门户配置》 按钮,就可以查看客户端的配置文件,将该配置文件导入手机的第三方客户端,即可让手机访问虚拟局域网 + +![Client Config](/assets/cn/portal_config.png) diff --git a/guide/installation.md b/guide/installation.md index 19df950..ab86ab1 100644 --- a/guide/installation.md +++ b/guide/installation.md @@ -1,19 +1,110 @@ -# 安装 {#installation} +# 安装 (命令行程序) {#installation} -1. **下载预编译的二进制文件** - 访问 [GitHub Release 页面](https://github.com/KKRainbow/EasyTier/releases) 下载适用于您操作系统的二进制文件。Release 压缩包中同时包含命令行程序和图形界面程序。 +本章节组仅介绍安装方式,阅读 [快速组网](/guide/network/quick-networking) 文档以了解参数含义和使用方法。 -2. **通过 crates.io 安装** +## 安装方式 + +1. **手动下载命令行程序** + + 访问 [⬇️下载页面](./download) 下载适用于您操作系统和硬件架构的 EasyTier 命令行程序。下载后为 ZIP 压缩包,解压后既可直接使用。 ::: code-group - ```sh [cargo] - cargo install easytier + + ```bash [Linux / MacOS / FreeBSD] + ./easytier-core --version ``` + + ```powershell [Windows] + .\easytier-core.exe --version + ``` + ::: -3. **通过源码安装** - ::: code-group + *** + +2. **DockerHub** + + [DockerHub 镜像地址](https://hub.docker.com/r/easytier/easytier) + + ```sh [docker] + # docker.io 镜像 + docker pull easytier/easytier:latest + docker run -d --privileged --network host easytier/easytier:latest + + # 国内用户可以使用 DaoCloud 镜像 + docker pull m.daocloud.io/docker.io/easytier/easytier:latest + docker run -d --privileged --network host m.daocloud.io/docker.io/easytier/easytier:latest + ``` + + 请继续阅读 [快速组网](/guide/network/quick-networking) 文档以了解参数含义和使用方法。 + + *** + +3. **通过Docker Compose安装** + + ::: details docker-compose.yml + + ```yaml [docker-compose.yml] + services: + watchtower: # 用于自动更新easytier镜像,若不需要请删除这部分 + image: containrrr/watchtower + container_name: watchtower + restart: unless-stopped + environment: + - TZ=Asia/Shanghai + - WATCHTOWER_NO_STARTUP_MESSAGE + volumes: + - /var/run/docker.sock:/var/run/docker.sock + command: --interval 3600 --cleanup --label-enable + easytier: + image: easytier/easytier:latest # 国内用户可以使用 m.daocloud.io/docker.io/easytier/easytier:latest + hostname: easytier + container_name: easytier + labels: + com.centurylinklabs.watchtower.enable: 'true' + restart: unless-stopped + network_mode: host + cap_add: + - NET_ADMIN + - NET_RAW + environment: + - TZ=Asia/Shanghai + devices: + - /dev/net/tun:/dev/net/tun + volumes: + - /etc/easytier:/root + - /etc/machine-id:/etc/machine-id:ro # 映射宿主机机器码 + command: -d --network-name <用户> --network-secret <密码> -p tcp://public.easytier.cn:11010 + ``` + + ::: + + *** + +4. **一键安装脚本(仅 Linux)** + + 注意:一键脚本依赖 `unzip`,请提前下载并安装。 + + ```bash + wget -O /tmp/easytier.sh "https://raw.githubusercontent.com/EasyTier/EasyTier/main/script/install.sh" && sudo bash /tmp/easytier.sh install --gh-proxy https://ghfast.top/ + ``` + + 脚本执行成功后,EasyTier 的二进程程序会安装到 `/opt/easytier` 目录下,配置文件位于 `/opt/easytier/config/default.conf`。 + + 配置文件可通过 [配置文件生成器](https://easytier.cn/web/index.html#/config_generator) 生成。 + + EasyTier 会被注册为系统服务,可以通过以下命令管理: + + ```bash + systemctl start easytier@default + ``` + + *** + +5. **通过源码安装** + ```sh [cargo] - cargo install --git https://github.com/KKRainbow/EasyTier.git + cargo install --git https://github.com/EasyTier/EasyTier.git easytier ``` - ::: \ No newline at end of file + + 源码安装需要 Rust 环境,并且安装 LLVM。 diff --git a/guide/installation_gui.md b/guide/installation_gui.md new file mode 100644 index 0000000..746b13d --- /dev/null +++ b/guide/installation_gui.md @@ -0,0 +1,78 @@ +# 安装 (图形界面) {#installation_gui} + +## EasyTier GUI + +访问 [⬇️下载页面](./download) 下载适用于您操作系统和硬件架构的图形界面程序,并安装即可。 + +安装成功后可阅读 [公共服务器组网](/guide/gui/basic) 文档以了解图形界面工具的使用方法。 + +需要注意,EasyTier GUI 依赖 WebView,可能有以下常见问题: + +1. 在低版本 Windows 上 WebView 下载失败,由于国内网络环境问题,可能无法下载 WebView 组件。请手动安装 [WebView2](https://developer.microsoft.com/zh-CN/microsoft-edge/webview2/) 或 [Edge](https://www.microsoft.com/zh-cn/edge) 浏览器。 + +2. 在低版本 Android 上样式丢失,显示混乱。请在应用商店中手动更新 WebView 组件。 + +## 第三方图形界面 + +### [EasyTier Game ( Windows )](/guide/gui/easytier-game) + + EasyTierGame 游戏联机启动器,由 nuxt3 typescript rust tauri 开发 具有简易的界面,附带最新版 easytier 内核,联机游玩的时候无论是心理上和使用上都能给予您最舒服的体验,同时支持自定义配置文件启动,满足各种需求 + +--- + +### [EasyTier Manager ( Windows )](/guide/gui/easytier-manager) + + EasyTier 管理器是用来管理 EasyTier 内核的一个桌面应用,用于可视化的新增、修改、删除 EasyTier 的配置文件。 + + - 支持界面化的一键启动、停止组网 + - 支持修改所有内核已有参数,所有新增、修改操作都支持界面化表单操作和文本编辑器操作 + - 支持界面化查看当前运行日志 + - 支持一键下载任意版本的内核 ( 请注意,旧版本很多参数特性不支持 ) + +--- + +### [Astral Game ( Windows / Android / Linux )](/guide/gui/astral-game) + + AstralGame 是一个基于 EasyTier 的跨平台网络应用,提供简单易用的 P2P 网络连接和 VPN 服务。通过 Flutter 构建的现代化界面,让用户能够轻松创建和管理虚拟网络。 + +--- + +### [luci-app-easytier ( OpenWrt )](https://github.com/EasyTier/luci-app-easytier) + + EasyTier 的 OpenWrt 插件,提供了在 OpenWrt 路由器上安装和配置 EasyTier 内核的方法。 + + 插件支持在 OpenWrt 的 LuCI 界面内完成 EasyTier 内核的安装、配置和管理。用户可以方便地通过 LuCI 界面进行配置内核、查看运行日志、重启内核等操作。 + +--- + +### [HomoTier ( HarmonyOS 5.0 )](https://appgallery.huawei.com/app/detail?id=top.frankhan.et4hm&channelId=SHARE) + + HomoTier 是一个基于 EasyTier 进行二次开发的鸿蒙原生应用,使用 ArkTS 实现现代化UI,提供 VPN 服务以及基本的配置管理,并依托于鸿蒙分布式设计能够跨设备共享配置。 + +
+ + + +
+ +--- + +### [EasyTier鸿蒙版 ( HarmonyOS 5-最新 )](https://appgallery.huawei.com/link/invite-test-wap?taskId=5279964495502566d0f704390bdba314&invitationCode=AcmyBG3xwJr) + + EasyTier鸿蒙版 是一个基于 EasyTier 进行二次开发的鸿蒙原生应用,使用 ArkTS 实现现代化UI,提供 VPN 服务以及配置管理,且能够快捷导入社区共享节点,并支持碰一碰等鸿蒙新特性。 + + 需要注意的是,在将来可能会放弃HarmonyOS 5(API18-)兼容,以便减少兼容代码,且尽可能保持HarmonyOS 6(API20+)及以上的兼容性。 + +
+ + + +
+ +--- + +### [Terracotta | 陶瓦联机 ( Windows / Android / Linux )](https://github.com/burningtnt/Terracotta) + + Terracotta | 陶瓦联机时为 Minecraft 玩家提供开箱即用的联机功能。陶瓦联机基于 EasyTier 开发,针对 MC 做出了大量优化,尽量降低了操作门槛。 + + 目前已接入 HMCL 启动器,可在最新版本体验程序功能,也可下载本程序体验。 diff --git a/guide/introduction.md b/guide/introduction.md index a38a7a1..b9f9084 100644 --- a/guide/introduction.md +++ b/guide/introduction.md @@ -1,20 +1,40 @@ -# 简介 - -一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。 - -![alt text](/assets/image-6.png) -![alt text](/assets/image-7.png) - -## 特点 - -- **去中心化**:无需依赖中心化服务,节点平等且独立。 -- **安全**:支持利用 WireGuard 加密通信,也支持 AES-GCM 加密保护中转流量。 -- **高性能**:全链路零拷贝,性能与主流组网软件相当。 -- **跨平台**:支持 MacOS/Linux/Windows,未来将支持 IOS 和 Android。可执行文件静态链接,部署简单。 -- **无公网 IP 组网**:支持利用共享的公网节点组网,可参考 [配置指南](/guide/network/without-public-ip) -- **NAT 穿透**:支持基于 UDP 的 NAT 穿透,即使在复杂的网络环境下也能建立稳定的连接。 -- **子网代理(点对网)**:节点可以将可访问的网段作为代理暴露给 VPN 子网,允许其他节点通过该节点访问这些子网。 -- **智能路由**:根据流量智能选择链路,减少延迟,提高吞吐量。 -- **TCP 支持**:在 UDP 受限的情况下,通过并发 TCP 链接提供可靠的数据传输,优化性能。 -- **高可用性**:支持多路径和在检测到高丢包率或网络错误时切换到健康路径。 -- **IPV6 支持**:支持利用 IPV6 组网。 \ No newline at end of file +# EasyTier 简介 + +EasyTier 是一款简单、安全、去中心化的内网穿透和异地组网工具,适合远程办公、异地访问、游戏加速等多种场景。无需公网 IP,无需复杂配置,轻松实现不同地点设备间的安全互联。 + +软件即可通过命令行使用,也可以通过图形界面操作。下载后可直接使用,无其他任何依赖。 + +- [🛠️安装 CLI 页面](./installation) 可查看安装命令行工具的方法。 +- [🖥️安装 GUI 页面](./installation_gui) 可查看安装图形界面工具的方法。 +- [⬇️下载页面](./download) 可获取最新版本 EasyTier 的下载链接。 + + +## 适用场景 + +- **远程办公**:让公司、家中和外地的电脑像在同一局域网一样互通。 +- **异地访问**:随时随地安全访问家中 NAS、服务器或其他设备。 +- **游戏加速**:组建虚拟局域网,畅玩联机游戏。 +- **物联网组网**:让分布在不同地点的设备安全互联。 + +## 核心特点 + +- **去中心化**:无需依赖中心服务器,所有节点平等独立,都可以参与转发和组网。 +- **安全加密**:支持 WireGuard 和 AES-GCM 加密,保障数据安全。 +- **跨平台**:支持 MacOS、Linux、Windows、FreeBSD、Android,未来将支持 iOS。 +- **无公网 IP 组网**:可通过共享公网节点组网,详见[配置指南](/guide/network/networking-without-public-ip)。 +- **NAT 穿透**:支持 UDP NAT 穿透,复杂网络环境下也能稳定连接。 +- **智能路由**:自动选择最佳链路,降低延迟,提高吞吐量。 +- **高可用性**:多路径支持,自动切换健康链路,提升稳定性。 + +## 高级特性 + +- **KCP / QUIC 代理**: 支持将 TCP 流量转为 KCP / QUIC 协议,提升高 UDP 丢包环境下的传输延迟和稳定性。 +- **非特权模式**:支持在非特权用户下运行,避免需要 root 权限。(仅可作为被访问端) +- **WireGuard 接入**: 支持 WireGuard 客户端接入 EasyTIer 网络。 + + +## 图形界面(GUI) + +EasyTier 提供简单易用的图形界面,适合新手快速上手。 + +EasyTier GUI Screenshot diff --git a/guide/license.md b/guide/license.md index e312763..df4af79 100644 --- a/guide/license.md +++ b/guide/license.md @@ -1,3 +1,3 @@ # 许可证 -EasyTier 根据 [Apache License 2.0](https://github.com/KKRainbow/EasyTier/blob/main/LICENSE) 许可证发布。 \ No newline at end of file +EasyTier 基于 [LGPL 3.0](https://github.com/EasyTier/EasyTier/blob/main/LICENSE) 许可发布。 diff --git a/guide/network/config-file.md b/guide/network/config-file.md new file mode 100644 index 0000000..457b537 --- /dev/null +++ b/guide/network/config-file.md @@ -0,0 +1,28 @@ +# 配置文件 + +支持使用 -c 参数指定配置文件路径。 + +```sh +easytier-core -c ./config.yaml +``` + +::: warning 注意 +注意:配置文件中的参数可以被命令行覆盖,比如配置文件中指定了 `--hostname abc`,但在命令行中使用 `--hostname xyz`,则会使用命令行中的主机名参数 `xyz`。 +::: + +使用参数运行可以获得对应参数的配置文件。配置文件会打印在命令行中,可以手动复制对应配置保存为toml文件即可。 + +在不使用参数的情况下直接运行 `easytier-core` 可以获得最小配置文件。 + +## 多配置文件启动 + +可以通过 `-c` 参数指定多个配置文件,EasyTier 会在一个进程中加载多个配置文件并启动多个虚拟网络。 + +```sh +easytier-core -c ./config1.yaml -c ./config2.yaml +``` + + +## 配置文件生成工具 + +官网提供了配置文件生成工具,可以通过访问 配置文件生成工具 来生成配置文件。 diff --git a/guide/network/configurations.md b/guide/network/configurations.md new file mode 100644 index 0000000..f74f189 --- /dev/null +++ b/guide/network/configurations.md @@ -0,0 +1,97 @@ +# 完整配置选项 + +可使用 `easytier-core --help` 查看全部配置项。 + +## 基本设置 + +### 配置服务器 + +| 参数 | 说明 | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `-w, --config-server` | 配置服务器地址。允许格式: | +| | - 完整URL:`--config-server udp://127.0.0.1:22020/admin` | +| | - 仅用户名:`--config-server admin`,将使用官方的服务器 | +| | [env: ET_CONFIG_SERVER=] | +| `--machine-id` | Web 配置服务器通过 machine id 来识别机器,用于断线重连后的配置恢复,需要保证唯一且固定不变。默认从系统获得。 [env: ET_MACHINE_ID=] | +| `-c, --config-file` | 配置文件路径,注意:命令行中的配置的选项会覆盖配置文件中的选项 [env: ET_CONFIG_FILE=] | + +### 网络设置 + +| 参数 | 说明 | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `--network-name` | 用于标识此VPN网络的网络名称 [env: ET_NETWORK_NAME=] | +| `--network-secret` | 网络密钥,用于验证此节点属于VPN网络 [env: ET_NETWORK_SECRET=] | +| `-i, --ipv4` | 此VPN节点的IPv4地址。如果为空,则此节点将仅转发数据包,不会创建TUN设备 [env: ET_IPV4=] | +| `-d, --dhcp` | 由Easytier自动确定并设置IP地址,默认从10.0.0.1开始。警告:在使用DHCP时,如果网络中出现IP冲突,IP将自动更改。 [env: ET_DHCP=] | +| `-p, --peers` | 最初要连接的对等节点 [env: ET_PEERS=] | +| `-e, --external-node` | 使用公共共享节点来发现对等节点 [env: ET_EXTERNAL_NODE=] | +| `-n, --proxy-networks` | 将本地网络导出到VPN中的其他对等节点,例如:`10.0.0.0/24`。支持映射到其他CIDR,例如:`10.0.0.0/24->192.168.0.0/24` [env: ET_PROXY_NETWORKS=] | + +### RPC 设置 + +| 参数 | 说明 | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------ | +| `-r, --rpc-portal` | 用于管理的RPC门户地址。支持以下格式: | +| | - `0` 表示随机端口 | +| | - `12345` 表示在localhost的12345上监听 | +| | - `0.0.0.0:12345` 表示在所有接口的12345上监听 | +| | 默认是 `0`,首先尝试 `15888` | +| | [env: ET_RPC_PORTAL=] | +| `--rpc-portal-whitelist` | RPC门户白名单,仅允许这些地址访问RPC门户,例如:`127.0.0.1/32,127.0.0.0/8,::1/128` [env: ET_RPC_PORTAL_WHITELIST=] | + +### 监听器设置 + +| 参数 | 说明 | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `-l, --listeners` | 监听器用于接受连接,支持以下格式: | +| | - 端口号:`<11010>`,意味着tcp/udp将在11010端口监听,ws/wss将在11010和11011端口监听,wg将在11011端口监听。 | +| | - URL:``,其中tcp可以是tcp、udp、ring、wg、ws、wss协议。 | +| | - 协议和端口对:``,例如wg:11011,表示使用WireGuard协议在11011端口监听。 | +| | [env: ET_LISTENERS=] | +| `--mapped-listeners` | 手动指定监听器的公网地址,其他节点可以使用该地址连接到本节点。例如:`tcp://123.123.123.123:11223`,可以指定多个。 [env: ET_MAPPED_LISTENERS=] | +| `--no-listener` | 不监听任何端口,只连接到对等节点 [env: ET_NO_LISTENER=] | + +### 其他设置 + +| 参数 | 说明 | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| `--hostname` | 用于标识此设备的主机名 [env: ET_HOSTNAME=] | +| `-m, --instance-name` | 实例名称,用于在同一台机器上标识此VPN节点 [env: ET_INSTANCE_NAME=] | +| `--vpn-portal` | 定义VPN门户的URL,允许其他VPN客户端连接。例如:`wg://0.0.0.0:11010/10.14.14.0/24` [env: ET_VPN_PORTAL=] | +| `--default-protocol` | 连接到对等节点时使用的默认协议 [env: ET_DEFAULT_PROTOCOL=] | +| `-u, --disable-encryption` | 禁用对等节点通信的加密,默认为false,必须与对等节点相同 [env: ET_DISABLE_ENCRYPTION=] | +| `--multi-thread` | 使用多线程运行时,默认为单线程 [env: ET_MULTI_THREAD=] | +| `--disable-ipv6` | 不使用IPv6 [env: ET_DISABLE_IPV6=] | +| `--dev-name` | 可选的TUN接口名称 [env: ET_DEV_NAME=] | +| `--mtu` | TUN设备的MTU,默认为非加密时为1380,加密时为1360 [env: ET_MTU=] | +| `--latency-first` | 延迟优先模式,将尝试使用最低延迟路径转发流量,默认使用最短路径 [env: ET_LATENCY_FIRST=] | +| `--exit-nodes` | 转发所有流量的出口节点,虚拟IPv4地址,优先级由列表顺序决定 [env: ET_EXIT_NODES=] | +| `--enable-exit-node` | 允许此节点成为出口节点 [env: ET_ENABLE_EXIT_NODE=] | +| `--proxy-forward-by-system` | 通过系统内核转发子网代理数据包,禁用内置NAT [env: ET_PROXY_FORWARD_BY_SYSTEM=] | +| `--no-tun` | 不创建TUN设备,可以使用子网代理访问节点 [env: ET_NO_TUN=] | +| `--use-smoltcp` | 为子网代理和 KCP 代理启用smoltcp堆栈 [env: ET_USE_SMOLTCP=] | +| `--manual-routes` | 手动分配路由CIDR,将禁用子网代理和从对等节点传播的wireguard路由。例如:`192.168.0.0/16` [env: ET_MANUAL_ROUTES=] | +| `--relay-network-whitelist` | 仅转发白名单网络的流量,支持通配符字符串。多个网络名称间可以使用英文空格间隔。 [env: ET_RELAY_NETWORK_WHITELIST=] | +| `--disable-p2p` | 禁用P2P通信,只通过`--peers`指定的节点转发数据包 [env: ET_DISABLE_P2P=] | +| `--disable-udp-hole-punching` | 禁用UDP打洞功能 [env: ET_DISABLE_UDP_HOLE_PUNCHING=] | +| `--relay-all-peer-rpc` | 转发所有对等节点的RPC数据包,即使对等节点不在转发网络白名单中。 [env: ET_RELAY_ALL_PEER_RPC=] | +| `--socks5` | 启用 socks5 服务器,允许 socks5 客户端访问虚拟网络。格式: `<端口>`,例如:`1080` [env: ET_SOCKS5=] | +| `--compression` | 要使用的压缩算法,支持 `none`、`zstd`。默认为 `none` [env: ET_COMPRESSION=] | +| `--bind-device` | 将连接器的套接字绑定到物理设备以避免路由问题。 [env: ET_BIND_DEVICE=] | +| `--enable-kcp-proxy` | 使用 KCP 代理 TCP 流,提高在 UDP 丢包网络上的延迟和吞吐量。 [env: ET_ENABLE_KCP_PROXY=] | +| `--disable-kcp-input` | 不允许其他节点使用 KCP 代理 TCP 流到此节点。 [env: ET_DISABLE_KCP_INPUT=] | +| `--enable-quic-proxy` | 使用 QUIC 代理 TCP 流,提高在 UDP 丢包网络上的延迟和吞吐量。 [env: ET_ENABLE_QUIC_PROXY=] | +| `--disable-quic-input` | 不允许其他节点使用 QUIC 代理 TCP 流到此节点。 [env: ET_DISABLE_QUIC_INPUT=] | +| `--port-forward` | 将本地端口转发到虚拟网络中的远程端口。例如:`udp://0.0.0.0:12345/10.126.126.1:23456` [env: ET_PORT_FORWARD=] | +| `--accept-dns` | 如果为true,则启用魔法DNS。使用魔法DNS,您可以使用域名访问其他节点,例如:`.et.net` [env: ET_ACCEPT_DNS=] | +| `--private-mode` | 如果为true,则不允许使用了与本网络不相同的网络名称和密码的节点通过本节点进行握手或中转 [env: ET_PRIVATE_MODE=] | +| `--foreign-relay-bps-limit` | 限制转发流量的带宽 [env: ET_FOREIGN_RELAY_BPS_LIMIT=] | +| `--console-log-level` | 控制台日志级别 [env: ET_CONSOLE_LOG_LEVEL=] | +| `--file-log-level` | 文件日志级别 [env: ET_FILE_LOG_LEVEL=] | +| `--file-log-dir` | 存储日志文件的目录 [env: ET_FILE_LOG_DIR=] | + +--- + +更多配置项请参考 `easytier-core --help` 输出。 + +--- diff --git a/guide/network/decentralized-networking.md b/guide/network/decentralized-networking.md new file mode 100644 index 0000000..b28afbb --- /dev/null +++ b/guide/network/decentralized-networking.md @@ -0,0 +1,92 @@ +# 去中心组网 + +绝大多数组网软件是中心化的,所有设备必须与中心服务器建连才可组网。 + +EasyTier 是去中心化的,不区分服务器和客户端,只要一个设备可以与虚拟网中任意一个节点通信,就可以加入虚拟网。 + +## 双节点组网 {#two-nodes} + +假设双节点的网络拓扑如下: + +```mermaid +flowchart LR +subgraph 节点 A [物理网卡IP: 22.1.1.1] + nodeA[EasyTier
虚拟 IP: 10.144.144.1] +end +subgraph 节点 B [物理网卡IP: 33.1.1.1] + nodeB[EasyTier
虚拟 IP: 10.144.144.2] +end +nodeA <-----> nodeB +``` + +### 步骤 + +1. 在节点 A 上运行以下命令: + + ```sh + sudo easytier-core -i 10.144.144.1 + ``` + + - `-i` 指定虚拟网的 IP 地址。 + + 该节点启动后会默认监听以下端口: + + | 协议 | 默认端口 | + | ------------- | ----------- | + | TCP | 11010 (TCP) | + | UDP | 11010 (UDP) | + | WebSocket | 11011 (TCP) | + | WebSocket SSL | 11012 (TCP) | + | WireGuard | 11013 (UDP) | + + 可以通过 `-l` 参数指定监听端口,例如: + + | 参数示例 | 说明 | + | --------------------------- | -------------------------------------------------------------------------------------------------------------------------- | + | `-l 12345` | 将端口的基准端口改为 12345,则监听端口为:TCP: 12345, UDP: 12345, WebSocket: 12346, WebSocket SSL: 12347, WireGuard: 12348 | + | `-l tcp:11010 -l udp:11011` | 将 TCP 端口改为 11010,UDP 端口改为 11011,仅监听这两个端口。支持的协议有 `tcp`、`udp`、`ws`、`wss`、`wg` | + | `--no-listener` | 禁止监听端口,会影响非打洞连接的建立。 | + +2. 在节点 B 上运行以下命令: + + ```sh + sudo easytier-core -d -p udp://22.1.1.1:11010 + ``` + + - `-d` 表示 DHCP 模式,自动分配虚拟 IP。 + - `-p` 指定节点 A 的公网地址和端口。 + + +## 三节点组网 + +基于双节点组网,第三个节点 C 可以连接到节点 A 或节点 B 加入虚拟网。 + +假设通过连接到节点 A 组网,网络拓扑如下: + +```mermaid +flowchart LR +subgraph 节点 A [物理网卡IP: 22.1.1.1] + nodeA[EasyTier
虚拟 IP: 10.144.144.1] +end +subgraph 节点 C [新加入] + nodeC[EasyTier
虚拟 IP: 10.144.144.3] +end +subgraph 节点 B [物理网卡IP: 33.1.1.1] + nodeB[EasyTier
虚拟 IP: 10.144.144.2] +end +nodeA <-----> nodeB +nodeC <-----> nodeA +``` + +### 步骤 + +1. 在节点 C 上运行以下命令: + + ```sh + sudo easytier-core -d -p udp://22.1.1.1:11010 + ``` + + - `-d` 表示 DHCP 模式,自动分配虚拟 IP。 + - `-p` 指定节点 A 的公网地址和端口。 + +随后节点 C 即可与节点 A 和节点 B 通过虚拟网通信。 diff --git a/guide/network/host-public-server.md b/guide/network/host-public-server.md new file mode 100644 index 0000000..3f64bf5 --- /dev/null +++ b/guide/network/host-public-server.md @@ -0,0 +1,143 @@ +# 搭建共享节点 + +用户可以使用自己的公网节点自建用于无公网 IP 组网的公共共享节点,方便其他无公网 IP 的用户组网。 需要不带任何参数启动 EasyTier,该节点就可作为公共服务器使用(不需要 root 权限): + +```shell +easytier-core +``` + +另外 EasyTier 支持共享节点集群。每个虚拟网络(通过相同的网络名称和密钥建链)都可以充当共享节点集群,其他网络的节点可以连接到共享节点集群中的任意节点,无需公共 IP 即可发现彼此。运行自建的公共服务器集群与运行虚拟网络完全相同,不过可以跳过配置 ipv4 地址。 + +如果你希望为 EasyTier 社区贡献公共服务器,可以联系管理员,我们将告知你如何将你的节点添加到社区共享节点列表中。当然这需要你的节点有一定的带宽和稳定性。 + +## 关闭转发 + +另外,默认情况下, EasyTier 的每个节点都允许为其他虚拟网提供转发服务,即使该节点已经指定了 网络名 (`--network-name`) 和 网络密钥 (`--network-secret`)、并已加入一个虚拟网。 + +若需改变此行为,可通过 `--relay-network-whitelist` 参数限定可被转发的网络名白名单(空格分割的通配符列表,如 `"ab* abc"`)。当该参数的列表为空时,就不会为所有其他网络提供转发服务。 + +EasyTier 可以做到不转发其他虚拟网的网络包,而是只帮助他们建立 P2P 链接,只需将白名单置空,并设置仅转发 RPC 流量即可。参考命令为: + +```shell +easytier-core --relay-network-whitelist --relay-all-peer-rpc +``` + +## 私有模式 + +如果你希望 EasyTier 仅在你的虚拟网络中提供服务,而不希望其他虚拟网的节点连接到你的节点,可以使用 `--private-mode true` 参数启动 EasyTier。 + +```shell +sudo easytier-core --private-mode true --network-name my-network --network-secret my-secret +``` + +这会仅允许网络名为 `my-network` 且密钥为 `my-secret` 的节点连接到该 EasyTier 节点。 + +## 配置 systemd 自启动 + +可以参考:[安装为 Linux systemd 服务](install-as-a-systemd-service) + +值得注意的是,作为服务器运行时,由于 Linux 默认给用户配置的 fd 上限为 1024,可能会面临 fd 耗尽的问题。 + +此时应该配置 `LimitNOFILE` 在 serivce 文件中,如: + +```shell +LimitNOFILE=1048576 +``` + +配置好的 service unit 供参考: + +```shell +# cat /etc/systemd/system/easytier.service + +[Unit] +Description=EasyTier Service +After=network.target syslog.target +Wants=network.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/easytier-core --hostname --network-name --network-secret -p tcp://public.easytier.top:11010 +Restart=always +RestartSec=3 +LimitNOFILE=1048576 +Environment=TOKIO_CONSOLE=1 + +[Install] +WantedBy=multi-user.target +``` + +## 配置 fail2ban + +如您贡献了公共服务器,可能会遇到这样的问题:大量的节点尝试连接到您的服务器,但是无法建立连接 + +```plain +connection error. local: udp://0.0.0.0:11010, remote: udp://***.***.***.***:14947, err: wait resp error: wait handshake timeout: Elapsed(()) +``` + +此时建议配置 `fail2ban`,以阻止这样的用户访问节点,可以有效的降低服务器的连接数,提高用户的访问质量。 + +以 Fedora 42 为例,配置方法如下: + +```shell +# install fail2ban +sudo dnf install fail2ban + +# enable and start +sudo systemctl enable --now fail2ban +``` + +配置日志过滤器: + +```ini +# cat /etc/fail2ban/filter.d/easytier.conf +[Definition] +failregex = remote: \S+://:\d+, err: wait resp error:.+ +``` + +配置 jail: + +```ini +# cat /etc/fail2ban/jail.local +[easytier] +enabled = true +filter = easytier +backend = systemd +journalmatch = '_SYSTEMD_UNIT=easytier.service' +maxretry = 3 +bantime = 3600 +findtime = 600 +banaction = nftables-multiport +``` + +此处的策略代表:`findtime=600s` 内尝试 `maxretry=3` 次失败,则会被关进小黑屋(阻止访问)`bantime=3600s` 一小时。 + +配置好后,重载 `fail2ban` + +```shell +sudo fail2ban-client reload +# OK +``` + +配置后,检查运行状态,可以看到已经阻止的客户端列表: + +```shell +sudo fail2ban-client status easytier +``` + +```plain +Status for the jail: easytier +|- Filter +| |- Currently failed: 11 +| |- Total failed: 4742 +| `- Journal matches: _SYSTEMD_UNIT=easytier.service +`- Actions + |- Currently banned: 99 + |- Total banned: 663 + `- Banned IP list: *** +``` + +此时服务器连接数应该明显降低(可能需要等待一会儿才能看到效果): + +```shell +netstat -ntp | grep easytier +``` diff --git a/guide/network/install-as-a-macos-service.md b/guide/network/install-as-a-macos-service.md new file mode 100644 index 0000000..49f75ed --- /dev/null +++ b/guide/network/install-as-a-macos-service.md @@ -0,0 +1,33 @@ +# 安装为 macOS 服务 + +下载并安装 [serviceman](https://webinstall.dev/serviceman)。 + +打开终端,运行如下命令注册服务: + +```bash +# 使用配置文件注册 easytier 服务 +sudo serviceman add -name easytier -system \ +--workdir /var/log/easytier \ +-groupname wheel -username root \ +-cap-net-bind \ +-- easytier-core -c ~/.config/easytier.toml + +# 不使用配置文件注册 easytier 服务 +sudo serviceman add -name easytier -system \ +--workdir /var/log/easytier \ +-groupname wheel -username root \ +-cap-net-bind \ +-- easytier-core --ipv4 x.x.x.x --network-name xxx --network-secret yyy --peers tcp://peer_host:11010 +``` + +启动 easytier 服务: + +```bash +sudo serviceman start easytier +``` + +关闭 easytier 服务: + +```bash +sudo serviceman stop easytier +``` diff --git a/guide/network/install-as-a-systemd-service.md b/guide/network/install-as-a-systemd-service.md new file mode 100644 index 0000000..35f0992 --- /dev/null +++ b/guide/network/install-as-a-systemd-service.md @@ -0,0 +1,34 @@ +# 将服务安装为 Linux Systemd 服务 + +在支持 systemd 的 Linux 发行版中,可以通过以下步骤将服务配置为随系统启动: + +1. 创建新的服务文件 `/etc/systemd/system/easytier.service`,并根据需要修改 `ExecStart` 后面的命令行参数。 + +```shell +[Unit] +Description=EasyTier Service +After=network.target syslog.target +Wants=network.target + +[Service] +Type=simple +ExecStart=/root/easytier-core --ipv4 x.x.x.x --network-name xxx --network-secret yyy --peers tcp://peer_host:11010 + +[Install] +WantedBy=multi-user.target +``` + +2. 保存文件后,在命令行中执行以下命令以启用服务: + +```sh +systemctl enable easytier.service +``` + +3. 启动和停止服务可以使用以下命令: + +```sh +systemctl start easytier.service +systemctl stop easytier.service +``` + +请注意,使用 `systemctl` 命令替代 `service` 命令是更现代的做法,建议在支持 systemd 的系统中使用。 \ No newline at end of file diff --git a/guide/network/install-as-a-windows-service.md b/guide/network/install-as-a-windows-service.md new file mode 100644 index 0000000..a9d7bd8 --- /dev/null +++ b/guide/network/install-as-a-windows-service.md @@ -0,0 +1,72 @@ +# 安装为 Windows 服务 + +> 感谢 北辰℃ 提供的教程,以及由 dawn-lc 提供的一键安装/卸载脚本 + +在 Windows 系统中,将某些应用程序安装为服务可以使其在后台自动运行,无需用户手动干预,极大地提高了应用的运行稳定性和便捷性。 + +本教程将以使用 NSSM(Non-Sucking Service Manager)工具将 EasyTier 应用安装为 Windows 服务为例,详细介绍整个操作流程。 + +## 一、前期准备 + +**下载 EasyTier 应用**: + +下载最新版本的 `Windows` 操作系统的 `命令行程序` 压缩包。 + +下载完成后,将该压缩包解压到本地目录,比如`D:\EasyTier`。 + +当前目录下应至少包含以下文件: + - `easytier-core.exe` (核心程序) + - `easytier-cli.exe` (命令行工具) + - `Packet.dll` (运行库) + - `wintun.dll` (运行库) + +**下载 NSSM**: + +打开浏览器,访问 NSSM 官网 [https://nssm.cc/](https://nssm.cc/download)。 + +在官网页面中找到适用于你系统的版本(通常是最新版本),点击下载链接将其下载到本地。 + +下载完成后,找到对应您设备架构的版本(如:`win64`),将其中的`nssm.exe`解压到`EasyTier`所在的本地目录。 + + +**下载 安装/卸载 脚本**: + +在当前目录下启动PowerShell并执行以下命令: + +`iwr "https://github.com/EasyTier/EasyTier/raw/refs/heads/main/script/install.cmd" -OutFile "install.cmd"` + +`iwr "https://github.com/EasyTier/EasyTier/raw/refs/heads/main/script/uninstall.cmd" -OutFile "uninstall.cmd"` + +## 二、准备工作 + +1. 确保当前目录下包含以下文件: + - `easytier-core.exe` (核心程序) + - `easytier-cli.exe` (命令行工具) + - `nssm.exe` (服务管理工具) + - `Packet.dll` (运行库) + - `wintun.dll` (运行库) + - `install.cmd` (安装脚本) + - `uninstall.cmd` (卸载脚本) + +2. 将整个文件夹放在固定位置。 + +## 三、安装服务 + +1. 运行`install.cmd` +2. 按照提示输入配置信息。 +4. 安装完成后会自动启动服务。 + +## 四、卸载服务 + +1. 运行`uninstall.cmd` +2. 脚本会自动停止并删除服务。 + +## 五、注意事项 + +1. 安装后不要移动程序文件位置 + +## 六、常见问题 + +**Q: 如何修改服务配置?** + +A: 先卸载服务,然后重新安装 diff --git a/guide/network/kcp-proxy.md b/guide/network/kcp-proxy.md new file mode 100644 index 0000000..ab191f1 --- /dev/null +++ b/guide/network/kcp-proxy.md @@ -0,0 +1,130 @@ +# KCP 代理 + +EasyTier 一般使用 UDP 协议进行数据传输虚拟网的 IP 数据包。但是某些运营商会对 UDP 协议进行限制,导致 UDP 有较高的丢包率,影响虚拟网内 TCP 协议的传输速度。 + +为了解决此问题,EasyTier 提供 KCP 代理功能,可以代理虚拟网内的 TCP 链接,并转换为 KCP 协议进行传输。由于 KCP 有更激进的重传机制,可以有效降低丢包率,提高虚拟网内的 TCP 传输速度。 + +## 网络拓扑 + +假设网络拓扑如下: + +```mermaid +graph LR + A[应用客户端] -->|TCP| B(EasyTier
A 节点) + B -->|KCP over UDP| C(EasyTier
B 节点) + C -->|TCP| D[应用服务端] + + classDef endpoint fill:#1e90ff,stroke:#ffffff,color:#ffffff + classDef easy fill:#4682b4,stroke:#ffffff,color:#ffffff + classDef transport stroke:#ffa500,stroke-width:2px + + class A,D endpoint + class B,C easy + linkStyle 1 stroke:#ffa500,stroke-width:2px,stroke-dasharray:5 5 + + style B stroke-width:2px + style C stroke-width:2px +``` + +## 使用 KCP 代理 + +### 启用 KCP 代理 + +假设想将 A 节点上的 TCP 流量代理为 KCP 协议,只需要在 A 节点上启动 EasyTier 时指定 `--enable-kcp-proxy` 参数即可。 + +```sh +sudo easytier-core --enable-kcp-proxy +``` + +- `--enable-kcp-proxy` 启用 KCP 代理功能。 + +KCP 代理会保证版本兼容性,如果发现对端节点不支持 KCP 代理,会自动切换回 TCP 协议。 + + +### 切换到用户态网络栈 + +KCP 代理默认使用内核的网络栈,可能由于系统防火墙设置导致无法正常工作。可以尝试结合 `--use-smoltcp` 参数,切换到用户态网络栈。 + +```sh +sudo easytier-core --enable-kcp-proxy --use-smoltcp +``` + +- `--use-smoltcp` 切换到用户态网络栈。 + + +### 禁用 KCP 入站 + +如果不希望发往某个节点的流量使用 KCP 协议,可以在对端节点上启动 EasyTier 时指定 `--disable-kcp-input` 参数。 + +以简介中的例子为例,如果不希望 B 节点接收 KCP 流量,可以在 B 节点上启动 EasyTier 时指定以下命令: + +```sh +sudo easytier-core --disable-kcp-input +``` + +- `--disable-kcp-input` 禁用 KCP 入站流量。 + +这样即使 A 节点启用了 KCP 代理,A 节点发往 B 节点的流量依然使用 TCP 协议。 + + +## 网对网 KCP 支持 + +假设节点 A 是路由器,A 下的子网访问 EasyTier 其他节点本身或者其他代理子网时,也可以使用 KCP 代理,但是需要 A 节点使用用户态网络栈即 `--use-smoltcp` 参数。 + +```sh +sudo easytier-core --enable-kcp-proxy --use-smoltcp +``` + +否则仍会使用 TCP 协议。 + + +## 查看 KCP 代理状态 + +可以通过 EasyTier CLI 工具查看 KCP 代理的链接状态。 + +```bash +$ easytier-cli proxy + +┌────────────────────┬───────────────────┬─────────────────────────┬───────────┬────────────────┐ +│ src │ dst │ start_time │ state │ transport_type │ +├────────────────────┼───────────────────┼─────────────────────────┼───────────┼────────────────┤ +│ 10.126.126.7:51838 │ 10.147.223.128:22 │ 2025-02-07 10:39:08 UTC │ Connected │ Tcp │ +├────────────────────┼───────────────────┼─────────────────────────┼───────────┼────────────────┤ +│ 0.0.0.0:0 │ 10.147.223.1:80 │ 2025-02-07 10:41:28 UTC │ Connected │ Kcp │ +├────────────────────┼───────────────────┼─────────────────────────┼───────────┼────────────────┤ +│ 0.0.0.0:0 │ 10.147.223.1:80 │ 2025-02-07 10:41:18 UTC │ Connected │ Kcp │ +└────────────────────┴───────────────────┴─────────────────────────┴───────────┴────────────────┘ +``` + +## QUIC 代理 + +EasyTier v2.3.2 版本引入了 QUIC 代理的支持,原理与 KCP 代理类似,但 QUIC 的 BBR 算法可以在高丢包环境下达到更高的带宽(KCP 代理则可显著降低延迟,但可以达到的带宽上限较低)。 + +QUIC 代理可以通过在链接发起端通过 `--enable-quic-proxy` 参数启用。 + +```sh +sudo easytier-core --enable-quic-proxy +``` + +接收端的 QUIC 代理可以通过 `--disable-quic-input` 参数禁用。 + +```sh +sudo easytier-core --disable-quic-input +``` + +发送端和接收端可以通过 `easytier-cli proxy` 命令查看 QUIC 代理的链接状态。 + +```bash +$ easytier-cli proxy +┌────────────────────┬───────────────────┬─────────────────────────┬───────────┬────────────────┐ +│ src │ dst │ start_time │ state │ transport_type │ +├────────────────────┼───────────────────┼─────────────────────────┼───────────┼────────────────┤ +│ 10.126.126.7:51838 │ 10.147.223.128:22 │ 2025-02-07 10:39:08 UTC │ Connected │ Quic │ +└────────────────────┴───────────────────┴─────────────────────────┴───────────┴────────────────┘ +``` + +::: tip 提示 +QUIC 和 KCP 代理可以同时启用,但是 KCP 代理会优先于 QUIC 代理生效。 + +在同时启用后,仅在目的端关闭 KCP 输入后,QUIC 代理才会生效。 +::: diff --git a/guide/network/magic-dns.md b/guide/network/magic-dns.md new file mode 100644 index 0000000..5380d9f --- /dev/null +++ b/guide/network/magic-dns.md @@ -0,0 +1,17 @@ +# 魔法 DNS + +EasyTier 支持类似 Tailscale 的魔法 DNS 功能,允许用户通过域名访问其他节点,无需记住虚拟 IP 地址。只要在启动时加入 `--accept-dns` 参数即可启用魔法 DNS 功能。 + +魔法 DNS 默认使用 `100.100.100.101` 作为 DNS 服务器地址,可以 `ping` 该地址测试魔法 DNS 是否成功启用。 + +假设魔法 DNS 启用成功,节点 A 的主机名为 `node-a`,则其他节点可以通过 `node-a.et.net` 访问节点 A。 + +```sh +ping node-a.et.net +``` + +主机名支持中文。 + +::: tip 注意 +魔法 DNS 目前仅支持在 Windows 和 MacOS 上自动配置系统 DNS,Linux 上需要手动配置 DNS 服务器为 `100.100.100.101` 才可正常使用。 +::: diff --git a/guide/network/multi-node.md b/guide/network/multi-node.md deleted file mode 100644 index 61f29c3..0000000 --- a/guide/network/multi-node.md +++ /dev/null @@ -1,11 +0,0 @@ -# 多节点组网 - -基于刚才的双节点组网例子,如果有更多的节点需要加入虚拟网络,可以使用如下命令。 - -``` -sudo easytier-core --ipv4 10.144.144.2 --peers udp://22.1.1.1:11010 -``` - -其中 `--peers ` 参数可以填写任意一个已经在虚拟网络中的节点的监听地址。 - ---- \ No newline at end of file diff --git a/guide/network/network-to-network.md b/guide/network/network-to-network.md new file mode 100644 index 0000000..9f0716b --- /dev/null +++ b/guide/network/network-to-network.md @@ -0,0 +1,49 @@ +# 网对网 + +网对网的网络拓扑如图所示 + +```mermaid +flowchart LR + +subgraph 节点 A +nodeA[EasyTier
10.144.144.1] +end + +subgraph 节点 B +nodeB[EasyTier
10.144.144.2] +end + +id1[[10.1.1.0/24]] + +id2[[192.168.1.0/24]] + +id2 <-.子网代理.-> nodeA <--> nodeB <-.子网代理.-> id1 + +id2 -.无需 EasyTier 访问对方子网.-> id1 + +``` + +网对网配置成功后,192.168.1.0/24 子网的设备可以访问 10.1.1.0/24 子网的设备互相通信且无需安装 EasyTier。 + +## Linux 网对网配置 + +要实现网对网,需要 节点 A 是 192.168.1.0/24 子网的网关。两个 EasyTier 节点的启动及配置参数如下: + +节点 A + +```bash +# 启动 EasyTier 并代理 192.168.1.0/24 网段,并使用公共服务器帮助组网 +easytier-core -i 10.144.144.1 -n 192.168.1.0/24 -p tcp://public.easytier.top:11010 --network-name n2n_test + +# 允许网关转发流量,并配置防火墙允许转发流量 +sysctl -w net.ipv4.ip_forward=1 +iptables -A FORWARD -s 192.168.1.0/24 -j ACCEPT +iptables -A FORWARD -d 192.168.1.0/24 -j ACCEPT +``` + +节点 B + +```bash +# 启动 EasyTier 并代理 10.1.1.0/24 网段,并使用公共服务器帮助组网 +easytier-core -i 10.144.144.2 -n 10.1.1.0/24 -p tcp://public.easytier.top:11010 --network-name n2n_test +``` diff --git a/guide/network/networking-without-public-ip.md b/guide/network/networking-without-public-ip.md new file mode 100644 index 0000000..f7c6f7f --- /dev/null +++ b/guide/network/networking-without-public-ip.md @@ -0,0 +1,28 @@ +# 无公网IP组网 + +EasyTier 支持共享公网节点进行组网。目前已部署共享的公网节点 + +`tcp://public.easytier.top:11010` + +使用共享节点时,需要每个入网节点提供相同的 `--network-name` 和 `--network-secret` 参数,作为网络的唯一标识。 + +以双节点为例,节点 A 执行: + +```sh +sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -p tcp://public.easytier.top:11010 +``` + +节点 B 执行 + +```sh +sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -p tcp://public.easytier.top:11010 +``` + +命令执行成功后,节点 A 即可通过虚拟 IP 10.144.144.2 访问节点 B。 + +`--ipv4 x.x.x.x` 可以替换为 `-d` 开启 DHCP 功能,由 EasyTier 根据虚拟网内已经存在的其他虚拟 IP 自动的分配本节点的 IP 地址。 + + +节点可以连接到多个公共服务器,当其中一个公共服务器失效后,节点间依然可以使用其他存活的公共服务器通信。只需要指定多个 -p 参数即可,如:`-p tcp://1.1.1.1:11010 -p udp://1.1.1.2:11011`。需要注意,虚拟网中每个节点都要指定相同的公共服务器列表,否则可能无法正常组网。 + +--- diff --git a/guide/network/no-root.md b/guide/network/no-root.md new file mode 100644 index 0000000..ab7c1ee --- /dev/null +++ b/guide/network/no-root.md @@ -0,0 +1,7 @@ +# 无 TUN 模式 (免 Root 权限) + +由于创建 TUN 设备需要 ROOT 权限,对于一些无法获取 Root 权限的环境,EasyTier 也提供了不依赖 TUN 的使用方法。只需在启动 EasyTier 时,增加 `--no-tun` 参数即可。 + +使用无 TUN 模式组网时,节点可以通过虚拟 IP 被访问(TCP、UDP 和 ICMP 都支持),也可以做子网代理(使用 -n 参数)。但是无法主动发起对其他节点的访问。 + +为了在无 TUN 模式下主动访问其他节点,可使用 EasyTier 的 [SOCKS5 服务器功能](/guide/network/socks5)。 diff --git a/guide/network/oneclick-install-as-service.md b/guide/network/oneclick-install-as-service.md new file mode 100644 index 0000000..5aaeed1 --- /dev/null +++ b/guide/network/oneclick-install-as-service.md @@ -0,0 +1,104 @@ +# 一键注册服务 + +EasyTier Cli 提供注册服务命令,可以在大部分系统上一键将 EasyTier 注册为系统服务。注册后,EasyTier 会在系统启动时自动启动,并在后台运行。 + +使用该命令需要 `easytier-core` 和 `easytier-cli` 在同一目录下。进入该目录后,运行以下命令: + +::: code-group + +```sh [Linux] +# 假设 EasyTier 的启动参数为 -w abc +sudo ./easytier-cli service install -w abc +``` + +```powershell [Windows] +# 假设 EasyTier 的启动参数为 -w abc +.\easytier-cli.exe service install -w abc +``` +::: + +`install` 后的部分会作为 `easytier-core` 的启动参数。 + +完整示例: +::: code-group + +```sh [Linux] +# 假设 EasyTier 的启动参数为 -w abc +sudo ./easytier-cli service install \ + --description "自定义服务描述" \ # 可选,默认使用包描述 + --display-name "显示名称" \ # 可选,服务显示名称 + --disable-autostart \ # 可选,禁用开机自启(默认启用) + --core-path /path/to/easytier-core \ # 可选,指定二进制路径 + --service-work-dir /工作目录路径 \ # 可选,指定工作目录 + -- -w abc # 可选,传递给 easytier-core 的参数 +``` + +```powershell [Windows] +# 假设 EasyTier 的启动参数为 -w abc +.\easytier-cli.exe service install ` + --description "自定义服务描述" ` # 可选,默认使用包描述 + --display-name "显示名称" ` # 可选,服务显示名称 + --disable-autostart ` # 可选,禁用开机自启(默认启用) + --core-path /path/to/easytier-core ` # 可选,指定二进制路径 + --service-work-dir /工作目录路径 ` # 可选,指定工作目录 + -- -w abc # 可选,传递给 easytier-core 的参数 +``` +::: + +服务安装成功后,可以使用以下命令对服务进行管理: + +- 启动服务: + + ::: code-group + + ```sh [Linux] + sudo ./easytier-cli service start + ``` + + ```powershell [Windows] + .\easytier-cli.exe service start + ``` + + ::: + +- 停止服务: + + ::: code-group + + ```sh [Linux] + sudo ./easytier-cli service stop + ``` + + ```powershell [Windows] + .\easytier-cli.exe service stop + ``` + + ::: + +- 查看状态: + + ::: code-group + + ```sh [Linux] + sudo ./easytier-cli service status + ``` + + ```powershell [Windows] + .\easytier-cli.exe service status + ``` + + ::: + +- 卸载服务: + + ::: code-group + + ```sh [Linux] + sudo ./easytier-cli service uninstall + ``` + + ```powershell [Windows] + .\easytier-cli.exe service uninstall + ``` + + ::: diff --git a/guide/network/other.md b/guide/network/other.md deleted file mode 100644 index 8372e3c..0000000 --- a/guide/network/other.md +++ /dev/null @@ -1,3 +0,0 @@ -# 其他 - -可使用 ``easytier-core --help`` 查看全部配置项 \ No newline at end of file diff --git a/guide/network/p2p-optimize.md b/guide/network/p2p-optimize.md new file mode 100644 index 0000000..84e9a0e --- /dev/null +++ b/guide/network/p2p-optimize.md @@ -0,0 +1,71 @@ +# P2P 优化 + +如果您希望 EasyTier 更容易地与其他 EasyTier 节点建立 P2P 连接,可以通过以下方式进行优化。 + +## IPv6 + +EasyTier 支持节点间通过 IPv4 和 IPv6 进行 P2P 通信,默认情况下 EasyTier 在每个监听器上同时监听 IPv4 和 IPv6 地址。 + +只要监听器监听地址为 `0.0.0.0` EasyTier 就会自动监听 IPv6 地址,该行为可以通过 `--disable-ipv6` 参数禁用。 + +也可以手动配置仅监听 IPv6 地址。 例如: + +```sh +easytier-core -l 'tcp://[::]:12345' -l 'udp://[::]:12345' +``` + +如果您的节点都拥有公网 IPv6 地址,并且可以入站(即被外网访问),就可以通过监听地址+默认监听端口(11010)来建立 P2P 连接。 + +如果您的节点都拥有公网 IPv6 地址,但不可入站(即不能被外网访问),可以利用 Easytier(版本2.3.0以上) 的 IPv6 打洞功能进行 P2P 连接,默认情况下开启此功能。 + +当然如果您能够修改公网 IPv6 的防火墙使其可以入站,也可以大幅度提高 p2p 的成功几率。 + +如果您的 IPv6 使用了 NAT66 技术,即网络地址转换技术,可以参考 IPv4 部分,如果可以的话建议关闭 NAT66 技术。 + +## IPv4 + +如果您的节点拥有公网 IPv4 地址,并且可以入站(即被外网访问),就可以通过监听地址+默认监听端口(11010)来建立 P2P 连接。 + +如果您的节点都公网 IPv4 地址,但不可入站(即不能被外网访问),可以利用 Easytier的 IPv4 打洞功能进行 P2P 连接,默认情况下开启此功能。 + +如果您的节点是普通家庭宽带,无公网 IPv4 ,需要修改 NAT 类型来提高 P2P 的成功几率,分为以下几种情况:(如何修改 NAT 类型可自行百度搜索方法) + +有关 NAT 类型的知识可参考这篇文章:[各种 NAT 类型的解释](https://nacldragon.top/2023/NAT-Type/) + +**NAT1(在 RFC3489 中为:Full Cone NAT / 完全锥型 NAT ;在 RFC5780 中为:端点无关映射+端点无关过滤 )** + +对于 Easytier 来说如果您的设备是 NAT1 类型,建立 P2P 连接时对方 NAT 类型可以是 NAT1、NAT2、NAT3、NAT4。 + +**NAT2(在 RFC3489 中为:Restricted Cone NAT / 限制锥型 NAT ;在 RFC5780 中为:端点无关映射+地址相关过滤 )** + +对于 Easytier 来说如果您的设备是 NAT2 类型,建立 P2P 连接时对方 NAT 类型可以是 NAT1、NAT2、NAT3、NAT4。 + +**NAT3(在 RFC3489 中为:Port Restricted Cone NAT / 端口限制锥型 NAT ;在 RFC5780 中为:端点无关映射+地址和端口相关过滤 )** + +对于 Easytier 来说如果您的设备是 NAT3 类型,建立 P2P 连接时对方 NAT 类型可以是 NAT1、NAT2、NAT3、NAT4。 + +**NAT4(在 RFC3489 中为:Symmetric NAT / 对称型 NAT ;在 RFC5780 中为:地址和端口相关映射+地址和端口相关过滤 )** + +对于 Easytier 来说如果您的设备是 NAT4 类型,建立 P2P 连接时对方 NAT 类型可以是 NAT1、NAT2、NAT3、NAT4(部分)。 + +注:对于 NAT4 类型来说,由于某些原因部分 NAT4 每次建立的端口是递增或递减的,可以通过端口预测技术来实现 P2P,这种 NAT4 可以简称为 NAT4E / 对称型递增。 + +::: warning 注意 +由于某些防火墙的策略,常见于学校、公司等,即使 NAT 类型为 1 2 3 ,也可能无法建立 P2P 连接。 +部分地区的运营商可能会采取阻断 P2P 连接的策略,即使为 NAT 类型为 1 也无法进行 P2P 连接! +IPv6 在开启 NAT66 功能后和 IPv4 NAT44 功能一样,也分为以上类型。 +::: + +## 指定公网 IP 和端口 + +某些情况下,节点拥有公网的 IP 和 端口,但 EasyTier 无法正确识别 (比如 NAT 主机),可以使用 `--mapped-listeners` 配置公网 IP 和端口。 例如: + +```sh +easytier-core --mapped-listeners tcp://8.8.8.8:12345 -l tcp://0.0.0.0:11010 +``` + +该 EasyTier 实例监听本地的 11010 TCP 端口,且该端口被映射到公网的 12345 端口。其他节点会尝试连接到公网的 12345 端口。 + +## 关闭上网辅助工具 + +一些上网辅助工具会影响 STUN 测试的结果,导致 EasyTier 无法识别 NAT 类型,或者识别到错误的公网 IP 和端口。可以尝试关闭这些工具。 diff --git a/guide/network/point-to-networking.md b/guide/network/point-to-networking.md new file mode 100644 index 0000000..9a9c0c4 --- /dev/null +++ b/guide/network/point-to-networking.md @@ -0,0 +1,123 @@ +# 子网代理 + +假设网络拓扑如下,节点 B 想将其可访问的子网 10.1.1.0/24 共享给其他节点。 + +```mermaid +flowchart LR + +subgraph 节点 A IP 22.1.1.1 +nodeA[EasyTier
10.144.144.1] +end + +subgraph 节点 B +nodeB[EasyTier
10.144.144.2] +end + +id1[[10.1.1.0/24]] + +nodeA <--> nodeB <-.-> id1 + +``` + +则节点 B 的 easytier 启动参数为(新增 -n 参数) + +```sh +sudo easytier-core --ipv4 10.144.144.2 -n 10.1.1.0/24 +``` + +子网代理信息会自动同步到虚拟网络的每个节点,各个节点会自动配置相应的路由,节点 A 可以通过如下命令检查子网代理是否生效。 + +1. 检查路由信息是否已经同步,proxy_cidrs 列展示了被代理的子网。 + + ```sh + easytier-cli route + ``` + + | ipv4 | hostname | proxy_cidrs | next_hop_ipv4 | next_hop_hostname | next_hop_lat | cost | + | :----------- | :------- | :---------- | :------------ | :---------------- | :----------- | :--- | + | 10.144.144.1 | abc-dec | 10.1.1.0/24 | DIRECT | | 3.25 | 1 | + +2. 测试节点 A 是否可访问被代理子网下的节点 + + ```sh + ping 10.1.1.2 + ``` + +::: warning 提示 +子网代理的 -n 参数可以多次指定,以代理多个子网;也可以将掩码设置为 32 以代理单个 IP 地址。 + +```sh +easytier-core -n 10.1.1.0/24 -n 10.2.0.0/16 -n 10.3.3.3/32 +``` + +::: + + +## 防火墙 + +由于代理流量需要用到系统的网络栈,因此子网代理需要关闭在虚拟网卡上的防火墙。 + +- 在 Windows 上,可以将 `easytier-core.exe` 添加到防火墙的例外列表中或者直接关闭防火墙。 + +- 在 Linux 上,可以使用 `iptables` 或 `ufw` 来放行虚拟网卡上的 INPUT 和 OUTPUT 流量。 + +- 在 OpenWrt 上,可以在 LUCI 界面上放行虚拟网卡流量。 + +如果无法关闭防火墙,可以尝试用用户态的网络栈做子网代理,可以免去配置防火墙的步骤。只需要在启动 EasyTier 时添加 `--use-smoltcp` 参数即可。 + +::: warning 提示 + +用户态协议栈在性能、拥塞控制等方面会劣于内核协议栈。 + +目前 `--use-smoltcp` 参数仅影响 TCP 协议,UDP 和 ICMP 无论是否使用该参数都会使用用户态协议栈。 + +::: + +## 手工指定路由 + +默认情况下,当虚拟网中的某个节点配置了子网代理后,子网代理的网段会被同步到虚拟网内的所有节点上,并自动生成一条路由,将发往这些网段的数据包交由虚拟网处理。 + +这在大部分情况下可以简化组网,但在有些场景下,用户可能并不希望 EasyTier 在节点上自动配置路由,用户可通过 `--manual-routes` 参数手工配置需要转发到虚拟网的流量。 + +使用 `--manual-routes` 后,只有该参数配置的网段才会进入虚拟网,如果该参数后的列表为空,则 EasyTier 不会处理任何非虚拟网网段的流量。例如: + +```sh +sudo easytier-core --ipv4 10.144.144.2 -n 10.1.1.0/24 --manual-routes 10.1.1.0/24 +``` + +`--manual-routes` 可以多次指定,来配置多个网段,格式与 `-n` 参数相同。 + +则该节点上仅会将 10.1.1.0/24 网段的流量交由虚拟网处理,其他网段的流量不会进入虚拟网。 + +## 网段映射 + +假设有如下场景:A 节点和 B 节点在内部网络中都有 `192.168.1.0/24` 的子网(网段相同但是物理网络不同),若 A、B 两节点希望对该网段进行代理,则需要将该网段映射到不同的虚拟网段上。 + +以下命令 A 节点将 `192.168.1.0/24` 映射到 `10.1.1.0/24`,B 节点将 `192.168.1.0/24` 映射到 `10.2.2.0/24`。 + +```sh +# 在节点 A 上运行 +sudo easytier-core --ipv4 10.144.144.1 -n '192.168.1.0/24->10.1.1.0/24' + +# 在节点 B 上运行 +sudo easytier-core --ipv4 10.144.144.2 -n '192.168.1.0/24->10.2.2.0/24' +``` + +虚拟网内其他节点,可以通过访问 `10.1.1.X` 来访问 A 节点代理的 `192.168.1.X`;通过访问 `10.2.2.X` 来访问 B 节点代理的 `192.168.1.X`。 + +::: warning 注意 +映射后的网段大小必须与原网段大小相同,否则会导致 EasyTier 启动失败。 +::: + +## 禁用内置NAT + +默认情况下,子网代理中启用了内置 NAT,在用户态处理数据包转发,使非网关设备也能成为子网入口,同时规避了不同操作系统上对于数据包转发的限制。 + +如果 Easytier 工作在网关设备上,用户可通过 `--proxy-forward-by-system` 参数将子网代理数据包交由系统内核转发,此时内置 NAT 关闭。 + +需要注意当该选项启用后,子网代理的数据包转发将完全依赖于操作系统,请确认操作系统的防火墙、转发规则、路由规则等配置正确。 + + +## 网对网 + +A 节点下的子网和 B 节点下的子网互相访问称为网对网, 网对网的配置请参考章节 [网对网](network-to-network)。 diff --git a/guide/network/point-to.md b/guide/network/point-to.md deleted file mode 100644 index fef758f..0000000 --- a/guide/network/point-to.md +++ /dev/null @@ -1,43 +0,0 @@ -# 子网代理(点对网) - -假设网络拓扑如下,节点 B 想将其可访问的子网 10.1.1.0/24 共享给其他节点。 - -```mermaid -flowchart LR - -subgraph 节点 A IP 22.1.1.1 -nodea[EasyTier\n10.144.144.1] -end - -subgraph 节点 B -nodeb[EasyTier\n10.144.144.2] -end - -id1[[10.1.1.0/24]] - -nodea <--> nodeb <-.-> id1 - -``` - -则节点 B 的 easytier 启动参数为(新增 -n 参数) - -```sh -sudo easytier-core --ipv4 10.144.144.2 -n 10.1.1.0/24 -``` - -子网代理信息会自动同步到虚拟网络的每个节点,各个节点会自动配置相应的路由,节点 A 可以通过如下命令检查子网代理是否生效。 - -1. 检查路由信息是否已经同步,proxy_cidrs 列展示了被代理的子网。 - - ```sh - easytier-cli route - ``` - ![alt text](/assets/image-3.png) - -2. 测试节点 A 是否可访问被代理子网下的节点 - - ```sh - ping 10.1.1.2 - ``` - ---- \ No newline at end of file diff --git a/guide/network/quick-networking.md b/guide/network/quick-networking.md new file mode 100644 index 0000000..228529b --- /dev/null +++ b/guide/network/quick-networking.md @@ -0,0 +1,226 @@ +# 快速组网 + +## 利用共享节点组网 + +当没有公网 IP 时,可使用 EasyTier 社区提供的免费共享节点快速组网。节点间会自动尝试 NAT 穿透并建立 P2P 连接,P2P 失败时由共享节点中转数据。 + +通过以下步骤可以构建一个简单的双节点虚拟网络: + +```mermaid +flowchart LR + S[公共共享节点] + +subgraph 虚拟网 abc + A[节点A] + B[节点B] +end + +A -->|连接| S +B -->|连接| S +A <-.->|P2P直连| B +``` + +### 步骤示例 + +假设有两个节点 A 和 B: + +#### 1. 在节点 A 上运行 + +::: code-group + +```sh [Linux] +# 管理员权限运行 +./easytier-core -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 +``` + +```powershell [Windows] +# 管理员权限运行 +.\easytier-core.exe -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 +``` + +::: + +- `-d` 自动分配虚拟 IP,默认分配 `10.126.126.0/24` 网段,可使用 `-i 10.11.11.0/24` 指定其他虚拟 IP。 +- `--network-name` 指定虚拟网络名称(支持中文)。注意:若与其他用户网络名冲突,可能导致组网失败。 +- `--network-secret` 指定虚拟网络的密码,用于保护网络安全。 +- `-p` 指定节点地址,此处为官方共享节点,也可用[其他公共节点](https://uptime.easytier.cn) + +#### 2. 在节点 B 上运行 + +::: code-group + +```sh [Linux] +# 管理员权限运行 +./easytier-core -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 +``` + +```powershell [Windows] +# 管理员权限运行 +.\easytier-core.exe -d --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 +``` + +::: + +- `-d` 表示 DHCP 模式,自动分配虚拟 IP +- `-p` 需指定与节点 A 使用相同的共享节点 + +#### 3. 测试组网 + +在节点 B 上测试与节点 A 的连通性: + +```sh +ping 10.126.126.1 +ping 10.126.126.2 +``` + +::: warning 注意 +如无法 ping 通,可能是防火墙阻止入站流量。请关闭防火墙或添加放行规则。 +::: + +#### 4. 加入更多节点 + +可以继续在其他节点上运行相同命令,加入同一虚拟网络。 + +## 查看虚拟网络状态 + +EasyTier 启动后,可用 easytier-cli 管理和查看状态。 + +- 查看虚拟网中的节点信息: + +```sh +easytier-cli peer +``` + +--- + +| ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | +| :----------- | :------- | :--- | :----- | :-------- | :------- | :------- | :----------- | :------- | :-------- | +| 10.144.144.1 | abc-dec | 1 | 3.452 | 0 | 17.33kB | 20.42kB | udp | FullCone | 390879727 | + +- 查看虚拟网路由信息: + +```sh +easytier-cli route +``` + +--- + +| ipv4 | hostname | proxy_cidrs | next_hop_ipv4 | next_hop_hostname | next_hop_lat | cost | +| :----------- | :------- | :---------- | :------------ | :---------------- | :----------- | :--- | +| 10.144.144.1 | abc-dec | | DIRECT | | 3.646 | 1 | + +- 查看本节点信息: + +```sh +easytier-cli node +``` + +--- + +::: details 输出示例 + +``` +┌───────────────┬──────────────────────┐ +│ Virtual IP │ 10.144.144.1 │ +├───────────────┼──────────────────────┤ +│ Hostname │ archlinux-base │ +├───────────────┼──────────────────────┤ +│ Proxy CIDRs │ 10.147.223.0/24 │ +├───────────────┼──────────────────────┤ +│ Peer ID │ 2616333191 │ +├───────────────┼──────────────────────┤ +│ Public IP │ 75.52.125.26 │ +├───────────────┼──────────────────────┤ +│ UDP Stun Type │ FullCone │ +├───────────────┼──────────────────────┤ +│ Listener 1 │ tcp://0.0.0.0:11010 │ +├───────────────┼──────────────────────┤ +│ Listener 2 │ udp://0.0.0.0:11010 │ +├───────────────┼──────────────────────┤ +│ Listener 3 │ wg://0.0.0.0:11011 │ +├───────────────┼──────────────────────┤ +│ Listener 4 │ ws://0.0.0.0:11011/ │ +├───────────────┼──────────────────────┤ +│ Listener 5 │ wss://0.0.0.0:11012/ │ +├───────────────┼──────────────────────┤ +│ Listener 6 │ udp://[::]:37039 │ +└───────────────┴──────────────────────┘ +``` + +::: + +--- + +## 同时使用多个共享节点组网 + +为提升可用性,可同时连接多个共享节点,只需指定多个 `-p` 参数: + +```sh +-p tcp://1.1.1.1:11010 -p udp://1.1.1.2:11011 +``` + +建议所有节点指定相同的共享节点列表。 + +### 组网原理示意 + +下图展示了多个共享节点集群下的组网模式: + +```mermaid +flowchart LR +subgraph 共享节点 + nodeA[共享节点 A
网络名: Public] + nodeB[共享节点 B
网络名: Public] +end +subgraph 节点 C + nodeC[节点 C
网络名: abc] +end +subgraph 节点 D + nodeD[节点 D
网络名: abc] +end +nodeA <--> nodeB +nodeC <--> 共享节点 +nodeD <--> 共享节点 +``` + +即使出现网络分区,C 只能连到 A,D 只能连到 B,C 和 D 依然可通信: + +```mermaid +flowchart LR +subgraph 节点 C + nodeC[节点 C
网络名: abc] +end +subgraph 共享节点 + nodeA[共享节点 A
网络名: Public] + nodeB[共享节点 B
网络名: Public] +end +subgraph 节点 D + nodeD[节点 D
网络名: abc] +end +nodeA <--> nodeB +nodeC <--> nodeA +nodeB <--> nodeD +``` + +--- + +## 同时加入/组建多个虚拟网络 + +EasyTier 支持同一设备运行多个进程,每个进程加入不同虚拟网。注意: +- 不同虚拟网的虚拟 IP 网段不能重叠,否则路由冲突; +- 启动多个实例时需指定不同监听端口,否则端口冲突。 + +示例: + +```sh +# 管理员权限运行 +./easytier-core --network-name net1 -p tcp://public.easytier.cn:11010 -l 11010 +./easytier-core --network-name net2 -p tcp://public.easytier.cn:11010 -l 21010 +``` + +- `-l` 指定监听端口。 + +--- + +## 搭建共享节点 + +如果希望搭建自己的共享节点,可以参考 [搭建共享节点](host-public-server) 文档。 diff --git a/guide/network/socks5.md b/guide/network/socks5.md new file mode 100644 index 0000000..d3de6ab --- /dev/null +++ b/guide/network/socks5.md @@ -0,0 +1,5 @@ +# SOCKS5 + +EasyTier 支持创建 SOCKS5 服务器,节点上的其他程序可以通过将代理设置为 EasyTier 的 SOCKS5 服务,即可访问虚拟网和虚拟网中的其他代理子网。 + +SOCKS5 服务的开启参数形为 `--socks5 12333`,将此参数加入 easytier-core 启动命令后,本机的 12333 端口即可服务于 SOCKS5 客户端。目前 SOCKS5 服务端无需用户名和密码验证,可直接使用。 diff --git a/guide/network/two-node.md b/guide/network/two-node.md deleted file mode 100644 index 968c1c6..0000000 --- a/guide/network/two-node.md +++ /dev/null @@ -1,50 +0,0 @@ - -# 双节点组网 - -假设双节点的网络拓扑如下 - ```mermaid - flowchart LR - - subgraph 节点 A IP 22.1.1.1 - nodea[EasyTier\n10.144.144.1] - end - - subgraph 节点 B - nodeb[EasyTier\n10.144.144.2] - end - - nodea <-----> nodeb - - ``` - -1. 在节点 A 上执行: - ```sh - sudo easytier-core --ipv4 10.144.144.1 - ``` - 命令执行成功会有如下打印。 - - ![alt text](/assets/image-2.png) - -2. 在节点 B 执行 - ```sh - sudo easytier-core --ipv4 10.144.144.2 --peers udp://22.1.1.1:11010 - ``` - -3. 测试联通性 - - 两个节点应成功连接并能够在虚拟子网内通信 - ```sh - ping 10.144.144.2 - ``` - - 使用 easytier-cli 查看子网中的节点信息 - ```sh - easytier-cli peer - ``` - ![alt text](/assets/image.png) - ```sh - easytier-cli route - ``` - ![alt text](/assets/image-1.png) - ---- \ No newline at end of file diff --git a/guide/network/use-easytier-with-wireguard-client.md b/guide/network/use-easytier-with-wireguard-client.md new file mode 100644 index 0000000..b5e16a3 --- /dev/null +++ b/guide/network/use-easytier-with-wireguard-client.md @@ -0,0 +1,81 @@ +# 使用 WireGuard 客户端接入 + +EasyTier 可以用作 WireGuard 服务端,让任意安装了 WireGuard 客户端的设备访问 EasyTier 网络。对于目前 EasyTier 不支持的平台(如 iOS),可以使用这种方式接入 EasyTier 网络。 + + +## 网络拓扑 + +假设网络拓扑如下,A 节点和 B 节点使用网 [双节点组网](decentralized-networking#two-nodes) 方式组网,并且 B 节点通过 [子网代理](point-to-networking) 代理了 `10.1.1.0/24` 子网。 + +```mermaid +flowchart LR +ios[[iPhone
安装 WireGuard]] + +subgraph 节点 A [公网 IP: 22.1.1.1] + nodea[EasyTier
虚拟 IP: 10.144.144.1] +end + +subgraph 节点 B + nodeb[EasyTier
虚拟 IP: 10.144.144.2] +end + +id1[[子网
10.1.1.0/24]] + +ios <-.-> nodea <--> nodeb <-.-> id1 +``` + +我们需要 iPhone 通过节点 A 访问 EasyTier 网络,则可进行如下配置。 + + +## 配置步骤 + +### 1. 配置节点 A + +在节点 A 的 `easytier-core` 命令中,加入 `--vpn-portal` 参数,指定 WireGuard 服务监听的端口,以及 WireGuard 网络使用的网段。 + +```sh +# 以下参数的含义为:监听 0.0.0.0:11013 端口,WireGuard 使用 10.14.14.0/24 网段 +sudo easytier-core --ipv4 10.144.144.1 --vpn-portal wg://0.0.0.0:11013/10.14.14.0/24 +``` + +### 2. 获取 WireGuard 客户端配置 + +`easytier-core` 启动成功后,使用 `easytier-cli` 获取 WireGuard 客户端的配置。 + +```sh +$> easytier-cli vpn-portal +portal_name: wireguard + +client_config: +[Interface] +PrivateKey = 9VDvlaIC9XHUvRuE06hD2CEDrtGF+0lDthgr9SZfIho= +Address = 10.14.14.0/24 # should assign an ip from this cidr manually + +[Peer] +PublicKey = zhrZQg4QdPZs8CajT3r4fmzcNsWpBL9ImQCUsnlXyGM= +AllowedIPs = 192.168.80.0/20,10.147.223.0/24,10.144.144.0/24 +Endpoint = 0.0.0.0:11013 # should be the public ip of the easytier server + +connected_clients: +[] +``` + + +## 使用客户端配置 + +使用 Client Config 前,需要将以下字段修改为实际值: + +- **Interface Address**:修改为客户端的 IP,如 `10.14.14.1/24`,若接入多个 WireGuard 客户端,需确保每个客户端的 IP 不同。 +- **Peer Endpoint**:修改为 EasyTier 节点 A 的公网 IP 和端口 `22.1.1.11:11013`。 + +将配置文件导入 WireGuard 客户端,即可访问 EasyTier 网络。 + + +::: tip 提示 +如果需要支持多个客户端,可以在 `easytier-core` 的 `--vpn-portal` 参数中指定更大的网段,例如 `10.14.0.0/16`。 +::: + + +## 备注 + +WireGuard 客户端访问 EasyTier 虚拟网络的所有流量都会经过 EasyTier 节点 A,需要确保节点 A 的网络连接稳定,并且一般情况下需要 A 节点有公网 IP 才可以方便的使用。 diff --git a/guide/network/use-wireguard-client.md b/guide/network/use-wireguard-client.md deleted file mode 100644 index 66ff5ed..0000000 --- a/guide/network/use-wireguard-client.md +++ /dev/null @@ -1,57 +0,0 @@ -# 使用 WireGuard 客户端接入 - -EasyTier 可以用作 WireGuard 服务端,让任意安装了 WireGuard 客户端的设备访问 EasyTier 网络。对于目前 EasyTier 不支持的平台 (如 iOS、Android 等),可以使用这种方式接入 EasyTier 网络。 - -假设网络拓扑如下: - -```mermaid -flowchart LR - -ios[[iPhone \n 安装 WireGuard]] - -subgraph 节点 A IP 22.1.1.1 -nodea[EasyTier\n10.144.144.1] -end - -subgraph 节点 B -nodeb[EasyTier\n10.144.144.2] -end - -id1[[10.1.1.0/24]] - -ios <-.-> nodea <--> nodeb <-.-> id1 -``` - -我们需要 iPhone 通过节点 A 访问 EasyTier 网络,则可进行如下配置: - -在节点 A 的 easytier-core 命令中,加入 --vpn-portal 参数,指定 WireGuard 服务监听的端口,以及 WireGuard 网络使用的网段。 - -``` -# 以下参数的含义为: 监听 0.0.0.0:11013 端口,WireGuard 使用 10.14.14.0/24 网段 -sudo easytier-core --ipv4 10.144.144.1 --vpn-portal wg://0.0.0.0:11013/10.14.14.0/24 -``` - -easytier-core 启动成功后,使用 easytier-cli 获取 WireGuard Client 的配置。 - -``` -$> easytier-cli vpn-portal -portal_name: wireguard - -client_config: -[Interface] -PrivateKey = 9VDvlaIC9XHUvRuE06hD2CEDrtGF+0lDthgr9SZfIho= -Address = 10.14.14.0/24 # should assign an ip from this cidr manually - -[Peer] -PublicKey = zhrZQg4QdPZs8CajT3r4fmzcNsWpBL9ImQCUsnlXyGM= -AllowedIPs = 192.168.80.0/20,10.147.223.0/24,10.144.144.0/24 -Endpoint = 0.0.0.0:11013 # should be the public ip of the vpn server - -connected_clients: -[] - -``` - -使用 Client Config 前,需要将 Interface Address 和 Peer Endpoint 分别修改为客户端的 IP 和 EasyTier 节点的 IP。将配置文件导入 WireGuard 客户端,即可访问 EasyTier 网络。 - ---- \ No newline at end of file diff --git a/guide/network/web-console.md b/guide/network/web-console.md new file mode 100644 index 0000000..df3ad8d --- /dev/null +++ b/guide/network/web-console.md @@ -0,0 +1,58 @@ +# 使用 Web 控制台 + +EasyTier 支持使用 [Web 控制台](https://easytier.cn/web#/) 来管理 EasyTier 节点,包括查看节点状态、配置节点参数、查看节点日志等。 + +## 注册账号 + +首次使用 Web 控制台需要注册账号,[注册地址](https://easytier.cn/web#/auth/register)。 + +## 运行 EasyTier 节点 + +如果希望 EasyTier 节点可以被 Web 控制台管理,需要在启动时指定 `--config-server` 或 `-w` 参数,例如: + +```sh +sudo ./easytier-core -w <你的用户名> +``` + +> 请将 `<你的用户名>` 替换为你在 Web 控制台注册的用户名。 + +如果终端出现类似 “连接成功” 或 “已连接服务器” 的提示,则表示 Easytier Core 已成功连接到 Web 控制台的服务器。 + +::: tip 提示 +Web 后端通过机器唯一码来识别设备和持久化配置,默认情况下 EasyTier 会自动从系统中获取机器唯一码。若机器码获取失败会导致重启后配置丢失,建议使用 `--machine-id` 参数指定机器码,例如: + +```sh +sudo ./easytier-core -w <你的用户名> --machine-id abc123 +``` + +请确保机器码在所有设备中唯一且不变。 **强烈建议 Docker 环境下手动指定机器码。** +::: + +::: danger 注意 +一台机器只能有一个 EasyTier 进程被 Web 控制台管理,如果有多个进程可能会导致奇怪的问题。 +::: + +::: tip 提示 + +可以通过 `--hostname <自定义主机名>` 参数指定控制台上显示的主机名。 + +::: + +## 使用 Web 控制台 + +使用刚才注册的用户名和密码登录 [Web 控制台](https://easytier.cn/web#/),登录成功后会看到节点列表。 + +在网页上选择你需要配置的设备。 + +![alt text](/assets/web-homepage.png) + +点开设备后,点击绿色的连接按钮。 + +![alt text](/assets/web-device-list.png) +![alt text](/assets/web-device-config.png) + +进行配置 + +![alt text](/assets/web-device-run-network.png) + +接下来的配置步骤与之前配置带 GUI 的程序相同。 diff --git a/guide/network/without-public-ip.md b/guide/network/without-public-ip.md deleted file mode 100644 index 09049e1..0000000 --- a/guide/network/without-public-ip.md +++ /dev/null @@ -1,23 +0,0 @@ -# 无公网IP组网 - -EasyTier 支持共享公网节点进行组网。目前已部署共享的公网节点 - -``tcp://easytier.public.kkrainbow.top:11010`` - -使用共享节点时,需要每个入网节点提供相同的 ``--network-name`` 和 ``--network-secret`` 参数,作为网络的唯一标识。 - -以双节点为例,节点 A 执行: - -```sh -sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -e tcp://easytier.public.kkrainbow.top:11010 -``` - -节点 B 执行 - -```sh -sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -e tcp://easytier.public.kkrainbow.top:11010 -``` - -命令执行成功后,节点 A 即可通过虚拟 IP 10.144.144.2 访问节点 B。 - ---- \ No newline at end of file diff --git a/guide/networking.md b/guide/networking.md index fe5e855..702c187 100644 --- a/guide/networking.md +++ b/guide/networking.md @@ -1,6 +1,7 @@ -# 组网 -::: tip 注意 -下文仅描述命令行工具的使用,图形界面程序可参考下述概念自行配置。 +# 通过命令行组网 + +::: warning 注意 +下文仅描述在使用命令行工具的情况下组网,图形界面程序可参考下述概念进行配置,也可参考 [图形界面 GUI 组网](/guide/gui/index)。 ::: -确保已按照 [安装指南](/guide/installation) 安装 EasyTier,并且 easytier-core 和 easytier-cli 两个命令都已经可用。 \ No newline at end of file +确保已按照 [安装指南](/guide/installation) 安装 EasyTier,并且 easytier-core 和 easytier-cli 两个命令都已经可用。 diff --git a/guide/perf.md b/guide/perf.md new file mode 100644 index 0000000..115e48d --- /dev/null +++ b/guide/perf.md @@ -0,0 +1,148 @@ +# 性能测试 + +参与测试的软件及其版本(为避嫌 + 公平待遇,用“某”代替): + +| 软件名 | 版本 | 链接 | +| -------- | ------ | ------------------------------------ | +| EasyTier | 1.2.1 | https://github.com/EasyTier/EasyTier | +| 某组网工具 A | 2024.7 月版本 | | + +待测: + +- WireGuard +- TailScale +- ZeroTier + + +## X86 + +| | | +| -------- | -------------------------------------------- | +| 机器型号 | 阿里云 ecs.ic5.2xlarge | +| vCPU | 8 vCPU | +| RAM | 8G | +| CPU 型号 | Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz | +| 操作系统 | Ubuntu 22.04 64位 | + +## 测试结果 + +| 软件 | 测试项目 | 性能 ( 无 -R / 带 -R ) Gbit/s | +| :-----------: | :-------------: | :---------------------------: | +| LoopBack 设备 | | 28.3 / 28.3 | +| EasyTier | UDP 无加密 | 1.43 / 1.46 | +| EasyTier | UDP AES-128-GCM | 1.36 / 1.37 | +| EasyTier | TCP 无加密 | 1.31 / 1.41 | +| EasyTier | TCP AES-128-GCM | 1.42 / 1.41 | +| | | | +| 某组网工具 A | UDP 无加密 | 1.10 / 1.11 | +| 某组网工具 A | UDP AES-128-GCM | 0.93 / 0.98 | + +## 复现方式 + +### 基础准备 + +测试基于 Linux 的网络命名空间功能,使用 Ubuntu 虚拟机、物理机、 Docker 容器等都可以完成测试。 + +初始化命令(root 权限执行) + +```bash +apt update +apt install iperf3 iptables -y + +ip netns add red +ip netns add green +ip link add br0 type bridge +ip link set br0 up +ip addr add 192.168.0.1/16 dev br0 + +ip link add vethcab0 type veth peer name red0 +ip link set vethcab0 master br0 +ip link set red0 netns red +ip netns exec red ip link set lo up +ip netns exec red ip link set red0 up +ip netns exec red ip addr add 192.168.0.2/16 dev red0 +ip netns exec red ip route add default via 192.168.0.1 +ip link set vethcab0 up + +ip link add vethcab1 type veth peer name green0 +ip link set vethcab1 master br0 +ip link set green0 netns green +ip netns exec green ip link set lo up +ip netns exec green ip link set green0 up +ip netns exec green ip addr add 192.168.0.3/16 dev green0 +ip netns exec green ip route add default via 192.168.0.1 +ip link set vethcab1 up + +sysctl net.ipv4.ip_forward=1 +sysctl net.bridge.bridge-nf-call-iptables=0 +sysctl net.bridge.bridge-nf-call-ip6tables=0 +sysctl net.ipv6.conf.lo.disable_ipv6=0 + +# 注: EasyTier 不依赖公网服务,可以不配置 iptables 转发 +iptables -t nat -A POSTROUTING -j MASQUERADE +iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o eth0 -j MASQUERADE +iptables -A FORWARD -i eht0 -j ACCEPT +iptables --policy FORWARD ACCEPT + +nohup ip netns exec red iperf3 -s & +``` + +另外需要确保待测试的程序在 PATH 环境变量中 + +下面的 iperf3 未带 -R,实际测试时会测得带 -R 的数据。 + +### LoopBack + +```bash +ip netns exec green iperf3 -c 192.168.0.2 +``` + +### EasyTier + +#### UDP 不带加密: +```bash +ip netns exec red easytier-core -i 10.126.126.2 --multi-thread -u +ip netns exec green easytier-core -i 10.126.126.3 -p udp://192.168.0.2:11010 --multi-thread -u +ip netns exec green iperf3 -c 10.126.126.2 +``` + +#### UDP 加密: +```bash +ip netns exec red easytier-core -i 10.126.126.2 --multi-thread +ip netns exec green easytier-core -i 10.126.126.3 -p udp://192.168.0.2:11010 --multi-thread +ip netns exec green iperf3 -c 10.126.126.2 +``` + +#### TCP 不带加密 + +```bash +ip netns exec red easytier-core -i 10.126.126.2 --multi-thread -u +ip netns exec green easytier-core -i 10.126.126.3 -p tcp://192.168.0.2:11010 --multi-thread -u +ip netns exec green iperf3 -c 10.126.126.2 +``` + +#### TCP 加密 + +```bash +ip netns exec red easytier-core -i 10.126.126.2 --multi-thread +ip netns exec green easytier-core -i 10.126.126.3 -p tcp://192.168.0.2:11010 --multi-thread +ip netns exec green iperf3 -c 10.126.126.2 +``` + +### 某组网工具 A + +#### UDP 无加密 + +```bash +ip netns exec red xxx -k iperf -s 8.134.146.7:29872 --ip 10.26.0.2 +ip netns exec green xxx -k iperf -s 8.134.146.7:29872 --ip 10.26.0.3 +ip netns exec green iperf3 -c 10.26.0.2 +``` + +#### UDP 带加密 + +```bash +ip netns exec red xxx -k iperf -s 8.134.146.7:29872 -w 1234 --ip 10.26.0.2 +ip netns exec green xxx -k iperf -s 8.134.146.7:29872 -w 1234 --ip 10.26.0.3 +ip netns exec green iperf3 -c 10.26.0.2 +``` diff --git a/guide/roadmap.md b/guide/roadmap.md index 0bed98d..a5462a5 100644 --- a/guide/roadmap.md +++ b/guide/roadmap.md @@ -1,6 +1,8 @@ # 路线图 -- [ ] 完善文档和用户指南。 -- [ ] 支持 TCP 打洞等特性。 -- [ ] 支持 Android、IOS 等移动平台。 -- [ ] 支持 Web 配置管理。 \ No newline at end of file +- [ ] 支持使用 KCP / FEC 等优化 P2P 传输。 +- [ ] 支持 UPnP。 +- [ ] 支持 IOS。 +- [ ] 支持 TCP 打洞。 +- [x] 支持 Web 配置管理。 +- [x] 完善文档和用户指南 diff --git a/index.md b/index.md index 5627f12..95986c1 100644 --- a/index.md +++ b/index.md @@ -3,33 +3,98 @@ layout: home hero: - name: Easytier + name: EasyTier text: 由 Rust 和 Tokio 驱动 - tagline: 一个简单、安全、去中心化的内网穿透 VPN 组网方案 + tagline: ✨ 一个简单、安全、去中心化的异地组网方案 + image: + light: '/gui-config-light.png' + dark: '/gui-config-dark.png' + alt: 'Easytier GUI 配置界面' actions: - theme: brand text: 快速开始 - link: /guide/installation + link: /guide/introduction - theme: alt - text: 在 Github 上查看 - link: https://github.com/KKRainbow/EasyTier + text: 下载 + link: /guide/download + - theme: alt + text: Web 控制台 + link: https://easytier.cn/web + - theme: sponsor + text: 💚 赞助 + link: /#sponsor features: - title: 去中心化 - details: 无需依赖中心化服务,节点平等且独立。 - - title: 安全 - details: 支持利用 WireGuard 加密通信,也支持 AES-GCM 加密保护中转流量。 - - title: 高性能 - details: 全链路零拷贝,性能与主流组网软件相当。 + details: 节点平等独立,无需中心化服务。
不区分客户端/服务端。 + link: /guide/network/decentralized-networking + - title: 易用 + details: 网页、客户端、命令行多方式操作
支持一键组网 + link: /guide/network/web-console - title: 跨平台 - details: 支持 MacOS/Linux/Windows,未来将支持 IOS 和 Android。可执行文件静态链接,部署简单。 - - title: 无公网 IP 组网 - details: 支持利用共享的公网节点组网,可参考配置指南 - - title: NAT 穿透 - details: 支持基于 UDP 的 NAT 穿透,即使在复杂的网络环境下也能建立稳定的连接。 - - title: 子网代理(点对网) - details: 节点可以将可访问的网段作为代理暴露给 VPN 子网,允许其他节点通过该节点访问这些子网。 + details: 支持 Win / MacOS / Linux / FreeBSD / Android
兼容 X86 / ARM / MIPS 架构 + link: /guide/download + - title: 安全 + details: AES-GCM 或 WireGuard 加密
防止中间人攻击 + link: / + - title: 高效 NAT 穿透 + details: 支持 UDP、IPv6 穿透
可打通 NAT4-NAT4 网络 + link: / + - title: 子网代理 + details: 节点可共享子网供其他节点访问。 + link: /guide/network/point-to-networking - title: 智能路由 - details: 根据流量智能选择链路,减少延迟,提高吞吐量。 + details: 延迟优先,自动选路
提供最佳网络体验 + link: /guide/network/configurations + - title: 高性能 + details: 全链路零拷贝
支持 TCP / UDP / WSS / WG 等协议 + link: /guide/perf + - title: 抗 UDP 丢包 + details: KCP / QUIC 代理
优化高丢包环境下的延迟和带宽 + link: /guide/network/kcp-proxy --- +## 相关链接 + +- QQ 群: + - 一群 [949700262](https://qm.qq.com/q/wFoTUChqZW) + - 二群 [837676408](https://qm.qq.com/q/4V33DrfgHe) + - 三群 [957189589](https://qm.qq.com/q/YNyTQjwlai) +- Telegram:https://t.me/easytier +- Discord:https://discord.gg/yRCkdu8brx + +## 鸣谢 + + + +## 赞助 {#sponsor} + +如果您觉得 EasyTier 对您有所帮助,欢迎赞助我们。 + +软件的开发和维护需要大量的时间和精力,您的赞助将帮助我们更好地维护和改进 EasyTier。 + +
+
+ 微信 +
+
+ 支付宝 +
+
+ + diff --git a/metadata.data.ts b/metadata.data.ts new file mode 100644 index 0000000..9bc7f7f --- /dev/null +++ b/metadata.data.ts @@ -0,0 +1,10 @@ +export default { + load() { + return { + easytier_latest_version: '2.4.5', + github_accels: [ + 'https://ghfast.top/', + ], + } + }, +} diff --git a/package.json b/package.json index 3fefb58..150d641 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,27 @@ { "name": "easytier-doc", + "type": "module", "version": "0.0.0", "description": "", "scripts": { "docs:dev": "vitepress dev", "docs:build": "vitepress build", - "docs:preview": "vitepress preview" + "docs:preview": "vitepress preview", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "prepare": "git submodule update --init --recursive" }, - "keywords": [], - "author": "" -} \ No newline at end of file + "dependencies": { + "@theojs/lumen": "workspace:*", + "typescript": "^5.7.3" + }, + "devDependencies": { + "@antfu/eslint-config": "3.14.0", + "eslint": "9.33.0", + "eslint-plugin-format": "1.0.1", + "markdown-it-task-lists": "2.1.1", + "mermaid": "11.12.0", + "vitepress": "1.6.3", + "vitepress-plugin-mermaid": "2.0.17" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b9f188..abad521 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,6493 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@theojs/lumen': + specifier: workspace:* + version: link:.vitepress/third_party/lumen/src + typescript: + specifier: ^5.7.3 + version: 5.7.3 + devDependencies: + '@antfu/eslint-config': + specifier: 3.14.0 + version: 3.14.0(@typescript-eslint/utils@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.33.0(jiti@2.4.2)))(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + eslint: + specifier: 9.33.0 + version: 9.33.0(jiti@2.4.2) + eslint-plugin-format: + specifier: 1.0.1 + version: 1.0.1(eslint@9.33.0(jiti@2.4.2)) + markdown-it-task-lists: + specifier: 2.1.1 + version: 2.1.1 + mermaid: + specifier: 11.12.0 + version: 11.12.0 + vitepress: + specifier: 1.6.3 + version: 1.6.3(@algolia/client-search@5.19.0)(@types/node@22.10.7)(postcss@8.5.1)(search-insights@2.17.3)(typescript@5.7.3) + vitepress-plugin-mermaid: + specifier: 2.0.17 + version: 2.0.17(mermaid@11.12.0)(vitepress@1.6.3(@algolia/client-search@5.19.0)(@types/node@22.10.7)(postcss@8.5.1)(search-insights@2.17.3)(typescript@5.7.3)) + + .vitepress/third_party/lumen/src: + dependencies: + '@iconify/vue': + specifier: ^4.3.0 + version: 4.3.0(vue@3.5.13(typescript@5.7.3)) + '@types/node': + specifier: ^22.10.7 + version: 22.10.7 + bumpp: + specifier: ^9.10.1 + version: 9.10.1 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 + iconify-icon: + specifier: ^2.3.0 + version: 2.3.0 + twikoo: + specifier: ^1.6.41 + version: 1.6.41 + typescript: + specifier: ^5.7.3 + version: 5.7.3 + vite: + specifier: ^6.0.7 + version: 6.0.7(@types/node@22.10.7)(jiti@2.4.2)(yaml@2.7.0) + vue: + specifier: ^3.5.13 + version: 3.5.13(typescript@5.7.3) + devDependencies: + vitepress: + specifier: ^1.5.0 + version: 1.5.0(@algolia/client-search@5.19.0)(@types/node@22.10.7)(postcss@8.5.1)(search-insights@2.17.3)(typescript@5.7.3) + +packages: + + '@algolia/autocomplete-core@1.17.7': + resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7': + resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} + peerDependencies: + search-insights: '>= 1 < 3' + + '@algolia/autocomplete-preset-algolia@1.17.7': + resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/autocomplete-shared@1.17.7': + resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} + peerDependencies: + '@algolia/client-search': '>= 4.9.1 < 6' + algoliasearch: '>= 4.9.1 < 6' + + '@algolia/client-abtesting@5.19.0': + resolution: {integrity: sha512-dMHwy2+nBL0SnIsC1iHvkBao64h4z+roGelOz11cxrDBrAdASxLxmfVMop8gmodQ2yZSacX0Rzevtxa+9SqxCw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-analytics@5.19.0': + resolution: {integrity: sha512-CDW4RwnCHzU10upPJqS6N6YwDpDHno7w6/qXT9KPbPbt8szIIzCHrva4O9KIfx1OhdsHzfGSI5hMAiOOYl4DEQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-common@5.19.0': + resolution: {integrity: sha512-2ERRbICHXvtj5kfFpY5r8qu9pJII/NAHsdgUXnUitQFwPdPL7wXiupcvZJC7DSntOnE8AE0lM7oDsPhrJfj5nQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-insights@5.19.0': + resolution: {integrity: sha512-xPOiGjo6I9mfjdJO7Y+p035aWePcbsItizIp+qVyfkfZiGgD+TbNxM12g7QhFAHIkx/mlYaocxPY/TmwPzTe+A==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-personalization@5.19.0': + resolution: {integrity: sha512-B9eoce/fk8NLboGje+pMr72pw+PV7c5Z01On477heTZ7jkxoZ4X92dobeGuEQop61cJ93Gaevd1of4mBr4hu2A==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-query-suggestions@5.19.0': + resolution: {integrity: sha512-6fcP8d4S8XRDtVogrDvmSM6g5g6DndLc0pEm1GCKe9/ZkAzCmM3ZmW1wFYYPxdjMeifWy1vVEDMJK7sbE4W7MA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-search@5.19.0': + resolution: {integrity: sha512-Ctg3xXD/1VtcwmkulR5+cKGOMj4r0wC49Y/KZdGQcqpydKn+e86F6l3tb3utLJQVq4lpEJud6kdRykFgcNsp8Q==} + engines: {node: '>= 14.0.0'} + + '@algolia/ingestion@1.19.0': + resolution: {integrity: sha512-LO7w1MDV+ZLESwfPmXkp+KLeYeFrYEgtbCZG6buWjddhYraPQ9MuQWLhLLiaMlKxZ/sZvFTcZYuyI6Jx4WBhcg==} + engines: {node: '>= 14.0.0'} + + '@algolia/monitoring@1.19.0': + resolution: {integrity: sha512-Mg4uoS0aIKeTpu6iv6O0Hj81s8UHagi5TLm9k2mLIib4vmMtX7WgIAHAcFIaqIZp5D6s5EVy1BaDOoZ7buuJHA==} + engines: {node: '>= 14.0.0'} + + '@algolia/recommend@5.19.0': + resolution: {integrity: sha512-PbgrMTbUPlmwfJsxjFhal4XqZO2kpBNRjemLVTkUiti4w/+kzcYO4Hg5zaBgVqPwvFDNQ8JS4SS3TBBem88u+g==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-browser-xhr@5.19.0': + resolution: {integrity: sha512-GfnhnQBT23mW/VMNs7m1qyEyZzhZz093aY2x8p0era96MMyNv8+FxGek5pjVX0b57tmSCZPf4EqNCpkGcGsmbw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-fetch@5.19.0': + resolution: {integrity: sha512-oyTt8ZJ4T4fYvW5avAnuEc6Laedcme9fAFryMD9ndUTIUe/P0kn3BuGcCLFjN3FDmdrETHSFkgPPf1hGy3sLCw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-node-http@5.19.0': + resolution: {integrity: sha512-p6t8ue0XZNjcRiqNkb5QAM0qQRAKsCiebZ6n9JjWA+p8fWf8BvnhO55y2fO28g3GW0Imj7PrAuyBuxq8aDVQwQ==} + engines: {node: '>= 14.0.0'} + + '@antfu/eslint-config@3.14.0': + resolution: {integrity: sha512-SBQOFrF/d2aqsVhxcHZ6g5DAoUaNyaV3Vd+lGNJx4CfSuwk9EuC8sRUF819GkNdCMbH5wNdFoJ4+Tsd9sr/NBw==} + hasBin: true + peerDependencies: + '@eslint-react/eslint-plugin': ^1.19.0 + '@prettier/plugin-xml': ^3.4.1 + '@unocss/eslint-plugin': '>=0.50.0' + astro-eslint-parser: ^1.0.2 + eslint: ^9.10.0 + eslint-plugin-astro: ^1.2.0 + eslint-plugin-format: '>=0.1.0' + eslint-plugin-react-hooks: ^5.0.0 + eslint-plugin-react-refresh: ^0.4.4 + eslint-plugin-solid: ^0.14.3 + eslint-plugin-svelte: '>=2.35.1' + prettier-plugin-astro: ^0.14.0 + prettier-plugin-slidev: ^1.0.5 + svelte-eslint-parser: '>=0.37.0' + peerDependenciesMeta: + '@eslint-react/eslint-plugin': + optional: true + '@prettier/plugin-xml': + optional: true + '@unocss/eslint-plugin': + optional: true + astro-eslint-parser: + optional: true + eslint-plugin-astro: + optional: true + eslint-plugin-format: + optional: true + eslint-plugin-react-hooks: + optional: true + eslint-plugin-react-refresh: + optional: true + eslint-plugin-solid: + optional: true + eslint-plugin-svelte: + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-slidev: + optional: true + svelte-eslint-parser: + optional: true + + '@antfu/install-pkg@1.0.0': + resolution: {integrity: sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==} + + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + + '@antfu/utils@0.7.10': + resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + + '@antfu/utils@9.2.1': + resolution: {integrity: sha512-TMilPqXyii1AsiEii6l6ubRzbo76p6oshUSYPaKsmXDavyMLqjzVDkcp3pHp5ELMUNJHATcEOGxKTTsX9yYhGg==} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.5': + resolution: {integrity: sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.26.5': + resolution: {integrity: sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==} + engines: {node: '>=6.9.0'} + + '@braintree/sanitize-url@6.0.4': + resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==} + + '@braintree/sanitize-url@7.1.1': + resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + + '@chevrotain/cst-dts-gen@11.0.3': + resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + + '@chevrotain/gast@11.0.3': + resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} + + '@chevrotain/regexp-to-ast@11.0.3': + resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} + + '@chevrotain/types@11.0.3': + resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} + + '@chevrotain/utils@11.0.3': + resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + + '@clack/core@0.4.1': + resolution: {integrity: sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA==} + + '@clack/prompts@0.9.1': + resolution: {integrity: sha512-JIpyaboYZeWYlyP0H+OoPPxd6nqueG/CmN6ixBiNFsIDHREevjIf0n0Ohh5gr5C8pEDknzgvz+pIJ8dMhzWIeg==} + + '@docsearch/css@3.8.2': + resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} + + '@docsearch/js@3.8.2': + resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==} + + '@docsearch/react@3.8.2': + resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} + peerDependencies: + '@types/react': '>= 16.8.0 < 19.0.0' + react: '>= 16.8.0 < 19.0.0' + react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + search-insights: + optional: true + + '@dprint/formatter@0.3.0': + resolution: {integrity: sha512-N9fxCxbaBOrDkteSOzaCqwWjso5iAe+WJPsHC021JfHNj2ThInPNEF13ORDKta3llq5D1TlclODCvOvipH7bWQ==} + + '@dprint/markdown@0.17.8': + resolution: {integrity: sha512-ukHFOg+RpG284aPdIg7iPrCYmMs3Dqy43S1ejybnwlJoFiW02b+6Bbr5cfZKFRYNP3dKGM86BqHEnMzBOyLvvA==} + + '@dprint/toml@0.6.4': + resolution: {integrity: sha512-bZXIUjxr0LIuHWshZr/5mtUkOrnh0NKVZEF6ACojW5z7zkJu7s9sV2mMXm8XQDqN4cJzdHYUYzUyEGdfciaLJA==} + + '@es-joy/jsdoccomment@0.49.0': + resolution: {integrity: sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==} + engines: {node: '>=16'} + + '@es-joy/jsdoccomment@0.50.0': + resolution: {integrity: sha512-+zZymuVLH6zVwXPtCAtC+bDymxmEwEqDftdAK+f407IF1bnX49anIxvBhCA1AqUIfD6egj1jM1vUnSuijjNyYg==} + engines: {node: '>=18'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-plugin-eslint-comments@4.4.1': + resolution: {integrity: sha512-lb/Z/MzbTf7CaVYM9WCFNQZ4L1yi3ev2fsFPF99h31ljhSEyUoyEsKsNWiU+qD1glbYTDJdqgyaLKtyTkkqtuQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/compat@1.2.5': + resolution: {integrity: sha512-5iuG/StT+7OfvhoBHPlmxkPA9om6aDUFgmD4+mWKAGsYt4vCe8rypneG03AuseyRHBmcCLXQtIH5S26tIoggLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.10.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.1': + resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.13.0': + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.33.0': + resolution: {integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/markdown@6.2.1': + resolution: {integrity: sha512-cKVd110hG4ICHmWhIwZJfKmmJBvbiDWyrHODJknAtudKgZtlROGoLX9UEOA0o746zC0hCY4UV4vR+aOGW9S6JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.8': + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@iconify-json/simple-icons@1.2.20': + resolution: {integrity: sha512-WlQ95zrdxxizrFt2HtkfYjyWatLfE8Z7BKOkew9quG5S5AKYVxF1PkTtOs8LDWShce1DpvxKWQne4W5DQyEGZg==} + + '@iconify-json/simple-icons@1.2.39': + resolution: {integrity: sha512-XlhW73c4dHvUrwWckVY76HDjnaZ2fWKD6hNZtd5kuv23GC0g3Lu0MXnYscpkIYOeiXO+Gtlw8FM53J7C84mCtA==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.0.2': + resolution: {integrity: sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==} + + '@iconify/vue@4.3.0': + resolution: {integrity: sha512-Xq0h6zMrHBbrW8jXJ9fISi+x8oDQllg5hTDkDuxnWiskJ63rpJu9CvJshj8VniHVTbsxCg9fVoPAaNp3RQI5OQ==} + peerDependencies: + vue: '>=3' + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@mermaid-js/mermaid-mindmap@9.3.0': + resolution: {integrity: sha512-IhtYSVBBRYviH1Ehu8gk69pMDF8DSRqXBRDMWrEfHoaMruHeaP2DXA3PBnuwsMaCdPQhlUUcy/7DBLAEIXvCAw==} + + '@mermaid-js/parser@0.6.2': + resolution: {integrity: sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@rollup/rollup-android-arm-eabi@4.31.0': + resolution: {integrity: sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.31.0': + resolution: {integrity: sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.31.0': + resolution: {integrity: sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.31.0': + resolution: {integrity: sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.31.0': + resolution: {integrity: sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.31.0': + resolution: {integrity: sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.31.0': + resolution: {integrity: sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.31.0': + resolution: {integrity: sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.31.0': + resolution: {integrity: sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.31.0': + resolution: {integrity: sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.31.0': + resolution: {integrity: sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.31.0': + resolution: {integrity: sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.31.0': + resolution: {integrity: sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.31.0': + resolution: {integrity: sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.31.0': + resolution: {integrity: sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.31.0': + resolution: {integrity: sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.31.0': + resolution: {integrity: sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.31.0': + resolution: {integrity: sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.31.0': + resolution: {integrity: sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==} + cpu: [x64] + os: [win32] + + '@shikijs/core@1.27.2': + resolution: {integrity: sha512-ns1dokDr0KE1lQ9mWd4rqaBkhSApk0qGCK1+lOqwnkQSkVZ08UGqXj1Ef8dAcTMZNFkN6PSNjkL5TYNX7pyPbQ==} + + '@shikijs/core@2.5.0': + resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} + + '@shikijs/engine-javascript@1.27.2': + resolution: {integrity: sha512-0JB7U5vJc16NShBdxv9hSSJYSKX79+32O7F4oXIxJLdYfomyFvx4B982ackUI9ftO9T3WwagkiiD3nOxOOLiGA==} + + '@shikijs/engine-javascript@2.5.0': + resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} + + '@shikijs/engine-oniguruma@1.27.2': + resolution: {integrity: sha512-FZYKD1KN7srvpkz4lbGLOYWlyDU4Rd+2RtuKfABTkafAPOFr+J6umfIwY/TzOQqfNtWjL7SAwPAO0dcOraRLaQ==} + + '@shikijs/engine-oniguruma@2.5.0': + resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} + + '@shikijs/langs@1.27.2': + resolution: {integrity: sha512-MSrknKL0DbeXvhtSigMLIzjPOOQfvK7fsbcRv2NUUB0EvuTTomY8/U+lAkczYrXY2+dygKOapJKk8ScFYbtoNw==} + + '@shikijs/langs@2.5.0': + resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} + + '@shikijs/themes@1.27.2': + resolution: {integrity: sha512-Yw/uV7EijjWavIIZLoWneTAohcbBqEKj6XMX1bfMqO3llqTKsyXukPp1evf8qPqzUHY7ibauqEaQchhfi857mg==} + + '@shikijs/themes@2.5.0': + resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} + + '@shikijs/transformers@1.27.2': + resolution: {integrity: sha512-BJFeXP9/zlYidJocv2ShkOvXI22fepS2oK/vItfCbCcuJ0783eWgEn6/mMrXmk+p+Twu49ntDVQe665uy6RPWw==} + + '@shikijs/transformers@2.5.0': + resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==} + + '@shikijs/types@1.27.2': + resolution: {integrity: sha512-DM9OWUyjmdYdnKDpaGB/GEn9XkToyK1tqxuqbmc5PV+5K8WjjwfygL3+cIvbkSw2v1ySwHDgqATq/+98pJ4Kyg==} + + '@shikijs/types@2.5.0': + resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} + + '@shikijs/vscode-textmate@10.0.1': + resolution: {integrity: sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@stylistic/eslint-plugin@2.13.0': + resolution: {integrity: sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.40.0' + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@22.10.7': + resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@typescript-eslint/eslint-plugin@8.20.0': + resolution: {integrity: sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/parser@8.20.0': + resolution: {integrity: sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/scope-manager@8.20.0': + resolution: {integrity: sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.20.0': + resolution: {integrity: sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/types@8.20.0': + resolution: {integrity: sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.20.0': + resolution: {integrity: sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/utils@8.20.0': + resolution: {integrity: sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' + + '@typescript-eslint/visitor-keys@8.20.0': + resolution: {integrity: sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.2.1': + resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} + + '@vitejs/plugin-vue@5.2.1': + resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@vitest/eslint-plugin@1.1.25': + resolution: {integrity: sha512-u8DpDnMbPcqBmJOB4PeEtn6q7vKmLVTLFMpzoxSAo0hjYdl4iYSHRleqwPQo0ywc7UV0S6RKIahYRQ3BnZdMVw==} + peerDependencies: + '@typescript-eslint/utils': '>= 8.0' + eslint: '>= 8.57.0' + typescript: '>= 5.0.0' + vitest: '*' + peerDependenciesMeta: + typescript: + optional: true + vitest: + optional: true + + '@vue/compiler-core@3.5.13': + resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} + + '@vue/compiler-dom@3.5.13': + resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} + + '@vue/compiler-sfc@3.5.13': + resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} + + '@vue/compiler-ssr@3.5.13': + resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} + + '@vue/devtools-api@7.7.0': + resolution: {integrity: sha512-bHEv6kT85BHtyGgDhE07bAUMAy7zpv6nnR004nSTd0wWMrAOtcrYoXO5iyr20Hkf5jR8obQOfS3byW+I3l2CCA==} + + '@vue/devtools-kit@7.7.0': + resolution: {integrity: sha512-5cvZ+6SA88zKC8XiuxUfqpdTwVjJbvYnQZY5NReh7qlSGPvVDjjzyEtW+gdzLXNSd8tStgOjAdMCpvDQamUXtA==} + + '@vue/devtools-shared@7.7.0': + resolution: {integrity: sha512-jtlQY26R5thQxW9YQTpXbI0HoK0Wf9Rd4ekidOkRvSy7ChfK0kIU6vvcBtjj87/EcpeOSK49fZAicaFNJcoTcQ==} + + '@vue/reactivity@3.5.13': + resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} + + '@vue/runtime-core@3.5.13': + resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} + + '@vue/runtime-dom@3.5.13': + resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} + + '@vue/server-renderer@3.5.13': + resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} + peerDependencies: + vue: 3.5.13 + + '@vue/shared@3.5.13': + resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} + + '@vueuse/core@11.3.0': + resolution: {integrity: sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA==} + + '@vueuse/core@12.8.2': + resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} + + '@vueuse/integrations@11.3.0': + resolution: {integrity: sha512-5fzRl0apQWrDezmobchoiGTkGw238VWESxZHazfhP3RM7pDSiyXy18QbfYkILoYNTd23HPAfQTJpkUc5QbkwTw==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/integrations@12.8.2': + resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/metadata@11.3.0': + resolution: {integrity: sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==} + + '@vueuse/metadata@12.8.2': + resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} + + '@vueuse/shared@11.3.0': + resolution: {integrity: sha512-P8gSSWQeucH5821ek2mn/ciCk+MS/zoRKqdQIM3bHq6p7GXDAJLmnRRKmF5F65sAVJIfzQlwR3aDzwCn10s8hA==} + + '@vueuse/shared@12.8.2': + resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + algoliasearch@5.19.0: + resolution: {integrity: sha512-zrLtGhC63z3sVLDDKGW+SlCRN9eJHFTgdEmoAOpsVh6wgGL1GgTTDou7tpCBjevzgIvi3AIyDAQO3Xjbg5eqZg==} + engines: {node: '>= 14.0.0'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + birpc@0.2.19: + resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + + bumpp@9.10.1: + resolution: {integrity: sha512-KG7oQmv6cz7QQwOvM3x/yPcF8+VBEtuLEEecmohNyb4+bLbtSVpJp8brjzcZYQN7UOyR4i0qIIYThnsBgP8uCA==} + engines: {node: '>=10'} + hasBin: true + + c12@2.0.1: + resolution: {integrity: sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001695: + resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + chevrotain-allstar@0.3.1: + resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.0.3: + resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + ci-info@4.1.0: + resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==} + engines: {node: '>=8'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + + clean-regexp@1.0.0: + resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} + engines: {node: '>=4'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.0: + resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + engines: {node: ^14.18.0 || >=16.10.0} + + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + + core-js-compat@3.40.0: + resolution: {integrity: sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==} + + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.1: + resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.11: + resolution: {integrity: sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + dayjs@1.11.18: + resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.3: + resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dompurify@3.2.7: + resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + + electron-to-chromium@1.5.83: + resolution: {integrity: sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==} + + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + enhanced-resolve@5.18.0: + resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-compat-utils@0.5.1: + resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + + eslint-compat-utils@0.6.4: + resolution: {integrity: sha512-/u+GQt8NMfXO8w17QendT4gvO5acfxQsAKirAt0LVxDnr2N8YLCVbregaNc/Yhp7NM128DwCaRvr8PLDfeNkQw==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + + eslint-config-flat-gitignore@1.0.0: + resolution: {integrity: sha512-EWpSLrAP80IdcYK5sIhq/qAY0pmUdBnbzqzpE3QAn6H6wLBN26cMRoMNU9Di8upTzUSL6TXeYRxWhTYuz8+UQA==} + peerDependencies: + eslint: ^9.5.0 + + eslint-flat-config-utils@1.0.0: + resolution: {integrity: sha512-tmzcXeCsa24/u3glyw1Mo7KfC/r9a5Vsu1nPCkX7uefD7C5Z4x922Q2KP/drhTLbOI5lcFHYpfXjKhqqnUWObw==} + + eslint-formatting-reporter@0.0.0: + resolution: {integrity: sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==} + peerDependencies: + eslint: '>=8.40.0' + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-json-compat-utils@0.2.1: + resolution: {integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==} + engines: {node: '>=12'} + peerDependencies: + '@eslint/json': '*' + eslint: '*' + jsonc-eslint-parser: ^2.4.0 + peerDependenciesMeta: + '@eslint/json': + optional: true + + eslint-merge-processors@1.0.0: + resolution: {integrity: sha512-4GybyHmhXtT7/W8RAouQzNM0791sYasJCTYHIAYjuiJvbNFY0jMKkoESREhX+mjX37dxiN6v4EqhZ1nc0tJF7A==} + peerDependencies: + eslint: '*' + + eslint-parser-plain@0.1.1: + resolution: {integrity: sha512-KRgd6wuxH4U8kczqPp+Oyk4irThIhHWxgFgLDtpgjUGVIS3wGrJntvZW/p6hHq1T4FOwnOtCNkvAI4Kr+mQ/Hw==} + + eslint-plugin-antfu@2.7.0: + resolution: {integrity: sha512-gZM3jq3ouqaoHmUNszb1Zo2Ux7RckSvkGksjLWz9ipBYGSv1EwwBETN6AdiUXn+RpVHXTbEMPAPlXJazcA6+iA==} + peerDependencies: + eslint: '*' + + eslint-plugin-command@2.1.0: + resolution: {integrity: sha512-S3gvDSCRHLdRG7NYaevLvGA0g/txOju7NEB2di7SE80NtbCwsvpi/fft045YuTZpOzqCRUfuye39raldmpXXYQ==} + peerDependencies: + eslint: '*' + + eslint-plugin-es-x@7.8.0: + resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '>=8' + + eslint-plugin-format@1.0.1: + resolution: {integrity: sha512-Tdns+CDjS+m7QrM85wwRi2yLae88XiWVdIOXjp9mDII0pmTBQlczPCmjpKnjiUIY3yPZNLqb5Ms/A/JXcBF2Dw==} + peerDependencies: + eslint: ^8.40.0 || ^9.0.0 + + eslint-plugin-import-x@4.6.1: + resolution: {integrity: sha512-wluSUifMIb7UfwWXqx7Yx0lE/SGCcGXECLx/9bCmbY2nneLwvAZ4vkd1IXDjPKFvdcdUgr1BaRnaRpx3k2+Pfw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + eslint-plugin-jsdoc@50.6.2: + resolution: {integrity: sha512-n7GNZ4czMAAbDg7DsDA7PvHo1IPIUwAXYmxTx6j/hTlXbt5V0x5q/kGkiJ7s4wA9SpB/yaiK8jF7CO237lOLew==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-plugin-jsonc@2.18.2: + resolution: {integrity: sha512-SDhJiSsWt3nItl/UuIv+ti4g3m4gpGkmnUJS9UWR3TrpyNsIcnJoBRD7Kof6cM4Rk3L0wrmY5Tm3z7ZPjR2uGg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + eslint-plugin-n@17.15.1: + resolution: {integrity: sha512-KFw7x02hZZkBdbZEFQduRGH4VkIH4MW97ClsbAM4Y4E6KguBJWGfWG1P4HEIpZk2bkoWf0bojpnjNAhYQP8beA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.23.0' + + eslint-plugin-no-only-tests@3.3.0: + resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} + engines: {node: '>=5.0.0'} + + eslint-plugin-perfectionist@4.6.0: + resolution: {integrity: sha512-kOswTebUK0LlYExRwqz7YQtvyTUIRsKfp8XrwBBeHGh2e8MBOS6K+7VvG6HpmNckyKySi1I96uPeAlptMFGcRQ==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + eslint: '>=8.0.0' + + eslint-plugin-regexp@2.7.0: + resolution: {integrity: sha512-U8oZI77SBtH8U3ulZ05iu0qEzIizyEDXd+BWHvyVxTOjGwcDcvy/kEpgFG4DYca2ByRLiVPFZ2GeH7j1pdvZTA==} + engines: {node: ^18 || >=20} + peerDependencies: + eslint: '>=8.44.0' + + eslint-plugin-toml@0.12.0: + resolution: {integrity: sha512-+/wVObA9DVhwZB1nG83D2OAQRrcQZXy+drqUnFJKymqnmbnbfg/UPmEMCKrJNcEboUGxUjYrJlgy+/Y930mURQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + eslint-plugin-unicorn@56.0.1: + resolution: {integrity: sha512-FwVV0Uwf8XPfVnKSGpMg7NtlZh0G0gBarCaFcMUOoqPxXryxdYxTRRv4kH6B9TFCVIrjRXG+emcxIk2ayZilog==} + engines: {node: '>=18.18'} + peerDependencies: + eslint: '>=8.56.0' + + eslint-plugin-unused-imports@4.1.4: + resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + + eslint-plugin-vue@9.32.0: + resolution: {integrity: sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-plugin-yml@1.16.0: + resolution: {integrity: sha512-t4MNCetPjTn18/fUDlQ/wKkcYjnuLYKChBrZ0qUaNqRigVqChHWzTP8SrfFi5s4keX3vdlkWRSu8zHJMdKwxWQ==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + eslint-processor-vue-blocks@1.0.0: + resolution: {integrity: sha512-q+Wn9bCml65NwYtuINVCE5dUqZa/uVoY4jfc8qEDwWbcGqdRyfJJmAONNZsreA4Q9EJqjYGjk8Hk1QuwAktgkw==} + peerDependencies: + '@vue/compiler-sfc': ^3.3.0 + eslint: ^8.50.0 || ^9.0.0 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.33.0: + resolution: {integrity: sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.18.0: + resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + + fdir@6.4.3: + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up-simple@1.0.0: + resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} + engines: {node: '>=18'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + focus-trap@7.6.4: + resolution: {integrity: sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + + giget@1.2.3: + resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.14.0: + resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-to-html@9.0.4: + resolution: {integrity: sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + iconify-icon@2.3.0: + resolution: {integrity: sha512-C0beI9oTDxQz6voI5CKl7MiJf0Lw4UU8K4G4t6pcUDClLmCvuMOpcvd8MAztQ2SfoH0iv7WHdxBFjekKPFKH2Q==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsdoc-type-pratt-parser@4.1.0: + resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} + engines: {node: '>=12.0.0'} + + jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsonc-eslint-parser@2.4.0: + resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + katex@0.16.22: + resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + langium@3.3.1: + resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} + engines: {node: '>=16.0.0'} + + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + local-pkg@1.0.0: + resolution: {integrity: sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==} + engines: {node: '>=14'} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + + markdown-it-task-lists@2.1.1: + resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + marked@16.3.0: + resolution: {integrity: sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==} + engines: {node: '>= 20'} + hasBin: true + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.0.0: + resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.0.0: + resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + mermaid@11.12.0: + resolution: {integrity: sha512-ZudVx73BwrMJfCFmSSJT84y6u5brEoV8DOItdHomNLz32uBjNrelm7mg95X7g+C6UoQH/W6mBLGDEDv73JdxBg==} + + micromark-core-commonmark@2.0.2: + resolution: {integrity: sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.0: + resolution: {integrity: sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.0.3: + resolution: {integrity: sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.1: + resolution: {integrity: sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==} + + micromark@4.0.1: + resolution: {integrity: sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minisearch@7.1.1: + resolution: {integrity: sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw==} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.7.4: + resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + natural-orderby@5.0.0: + resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==} + engines: {node: '>=18'} + + node-fetch-native@1.6.4: + resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + non-layered-tidy-tree-layout@2.0.2: + resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + nypm@0.3.12: + resolution: {integrity: sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + + ohash@1.1.4: + resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + oniguruma-to-es@2.1.0: + resolution: {integrity: sha512-Iq/949c5IueVC5gQR7OYXs0uHsDIePcgZFlVRIVGfQcWwbKG+nsyWfthswdytShlRdkZADY+bWSi+BRyUL81gA==} + + oniguruma-to-es@3.1.1: + resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-manager-detector@0.2.8: + resolution: {integrity: sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==} + + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-gitignore@2.0.0: + resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} + engines: {node: '>=14'} + + parse-imports@2.2.1: + resolution: {integrity: sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==} + engines: {node: '>= 18'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.2: + resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss@8.5.1: + resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} + engines: {node: ^10 || ^12 || >=14} + + preact@10.25.4: + resolution: {integrity: sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.4.2: + resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} + engines: {node: '>=14'} + hasBin: true + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + + read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + + readdirp@4.1.1: + resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} + engines: {node: '>= 14.18.0'} + + refa@0.12.1: + resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + regex-recursion@5.1.1: + resolution: {integrity: sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@5.1.1: + resolution: {integrity: sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==} + + regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} + + regexp-ast-analysis@0.7.1: + resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + regjsparser@0.10.0: + resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} + hasBin: true + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + + rollup@4.31.0: + resolution: {integrity: sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scslre@0.3.0: + resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} + engines: {node: ^14.0.0 || >=16.0.0} + + search-insights@2.17.3: + resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shiki@1.27.2: + resolution: {integrity: sha512-QtA1C41oEVixKog+V8I3ia7jjGls7oCZ8Yul8vdHrVBga5uPoyTtMvFF4lMMXIyAZo5A5QbXq91bot2vA6Q+eQ==} + + shiki@2.5.0: + resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slashes@3.0.12: + resolution: {integrity: sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + + spdx-license-ids@3.0.21: + resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + stable-hash@0.0.4: + resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} + engines: {node: '>=16'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + synckit@0.6.2: + resolution: {integrity: sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==} + engines: {node: '>=12.20'} + + synckit@0.9.2: + resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} + engines: {node: ^14.18.0 || >=16.0.0} + + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + + tinyglobby@0.2.10: + resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toml-eslint-parser@0.10.0: + resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + ts-api-utils@2.0.0: + resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + twikoo@1.6.41: + resolution: {integrity: sha512-Hrp/fvk2N5oqin+9bkFVULstL+YMwQjn/vVL8r5FPIhyRBA0zWAgUri4hhCji4FsXegKR6+ihyMPTXBRENjnnA==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + update-browserslist-db@1.1.2: + resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@5.4.11: + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@5.4.19: + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@6.0.7: + resolution: {integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitepress-plugin-mermaid@2.0.17: + resolution: {integrity: sha512-IUzYpwf61GC6k0XzfmAmNrLvMi9TRrVRMsUyCA8KNXhg/mQ1VqWnO0/tBVPiX5UoKF1mDUwqn5QV4qAJl6JnUg==} + peerDependencies: + mermaid: 10 || 11 + vitepress: ^1.0.0 || ^1.0.0-alpha + + vitepress@1.5.0: + resolution: {integrity: sha512-q4Q/G2zjvynvizdB3/bupdYkCJe2umSAMv9Ju4d92E6/NXJ59z70xB0q5p/4lpRyAwflDsbwy1mLV9Q5+nlB+g==} + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + + vitepress@1.6.3: + resolution: {integrity: sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==} + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-eslint-parser@9.4.3: + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + vue@3.5.13: + resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml-eslint-parser@1.2.3: + resolution: {integrity: sha512-4wZWvE398hCP7O8n3nXKu/vdq1HcH01ixYlCREaJL5NUMwQ0g3MaGFUBNSlmBtKmhbtVG/Cm6lyYmSVTEVil8A==} + engines: {node: ^14.17.0 || >=16.0.0} + + yaml@2.7.0: + resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} + engines: {node: '>= 14'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.19.0)(algoliasearch@5.19.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.19.0)(algoliasearch@5.19.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.19.0)(algoliasearch@5.19.0) + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + - search-insights + + '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.19.0)(algoliasearch@5.19.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.19.0)(algoliasearch@5.19.0) + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + - algoliasearch + + '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.19.0)(algoliasearch@5.19.0)': + dependencies: + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.19.0)(algoliasearch@5.19.0) + '@algolia/client-search': 5.19.0 + algoliasearch: 5.19.0 + + '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.19.0)(algoliasearch@5.19.0)': + dependencies: + '@algolia/client-search': 5.19.0 + algoliasearch: 5.19.0 + + '@algolia/client-abtesting@5.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + '@algolia/requester-browser-xhr': 5.19.0 + '@algolia/requester-fetch': 5.19.0 + '@algolia/requester-node-http': 5.19.0 + + '@algolia/client-analytics@5.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + '@algolia/requester-browser-xhr': 5.19.0 + '@algolia/requester-fetch': 5.19.0 + '@algolia/requester-node-http': 5.19.0 + + '@algolia/client-common@5.19.0': {} + + '@algolia/client-insights@5.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + '@algolia/requester-browser-xhr': 5.19.0 + '@algolia/requester-fetch': 5.19.0 + '@algolia/requester-node-http': 5.19.0 + + '@algolia/client-personalization@5.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + '@algolia/requester-browser-xhr': 5.19.0 + '@algolia/requester-fetch': 5.19.0 + '@algolia/requester-node-http': 5.19.0 + + '@algolia/client-query-suggestions@5.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + '@algolia/requester-browser-xhr': 5.19.0 + '@algolia/requester-fetch': 5.19.0 + '@algolia/requester-node-http': 5.19.0 + + '@algolia/client-search@5.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + '@algolia/requester-browser-xhr': 5.19.0 + '@algolia/requester-fetch': 5.19.0 + '@algolia/requester-node-http': 5.19.0 + + '@algolia/ingestion@1.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + '@algolia/requester-browser-xhr': 5.19.0 + '@algolia/requester-fetch': 5.19.0 + '@algolia/requester-node-http': 5.19.0 + + '@algolia/monitoring@1.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + '@algolia/requester-browser-xhr': 5.19.0 + '@algolia/requester-fetch': 5.19.0 + '@algolia/requester-node-http': 5.19.0 + + '@algolia/recommend@5.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + '@algolia/requester-browser-xhr': 5.19.0 + '@algolia/requester-fetch': 5.19.0 + '@algolia/requester-node-http': 5.19.0 + + '@algolia/requester-browser-xhr@5.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + + '@algolia/requester-fetch@5.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + + '@algolia/requester-node-http@5.19.0': + dependencies: + '@algolia/client-common': 5.19.0 + + '@antfu/eslint-config@3.14.0(@typescript-eslint/utils@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.33.0(jiti@2.4.2)))(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3)': + dependencies: + '@antfu/install-pkg': 1.0.0 + '@clack/prompts': 0.9.1 + '@eslint-community/eslint-plugin-eslint-comments': 4.4.1(eslint@9.33.0(jiti@2.4.2)) + '@eslint/markdown': 6.2.1 + '@stylistic/eslint-plugin': 2.13.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/eslint-plugin': 8.20.0(@typescript-eslint/parser@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/parser': 8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + '@vitest/eslint-plugin': 1.1.25(@typescript-eslint/utils@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + eslint: 9.33.0(jiti@2.4.2) + eslint-config-flat-gitignore: 1.0.0(eslint@9.33.0(jiti@2.4.2)) + eslint-flat-config-utils: 1.0.0 + eslint-merge-processors: 1.0.0(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-antfu: 2.7.0(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-command: 2.1.0(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-import-x: 4.6.1(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + eslint-plugin-jsdoc: 50.6.2(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-jsonc: 2.18.2(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-n: 17.15.1(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-no-only-tests: 3.3.0 + eslint-plugin-perfectionist: 4.6.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + eslint-plugin-regexp: 2.7.0(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-toml: 0.12.0(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-unicorn: 56.0.1(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.20.0(@typescript-eslint/parser@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-vue: 9.32.0(eslint@9.33.0(jiti@2.4.2)) + eslint-plugin-yml: 1.16.0(eslint@9.33.0(jiti@2.4.2)) + eslint-processor-vue-blocks: 1.0.0(@vue/compiler-sfc@3.5.13)(eslint@9.33.0(jiti@2.4.2)) + globals: 15.14.0 + jsonc-eslint-parser: 2.4.0 + local-pkg: 1.0.0 + parse-gitignore: 2.0.0 + picocolors: 1.1.1 + toml-eslint-parser: 0.10.0 + vue-eslint-parser: 9.4.3(eslint@9.33.0(jiti@2.4.2)) + yaml-eslint-parser: 1.2.3 + yargs: 17.7.2 + optionalDependencies: + eslint-plugin-format: 1.0.1(eslint@9.33.0(jiti@2.4.2)) + transitivePeerDependencies: + - '@eslint/json' + - '@typescript-eslint/utils' + - '@vue/compiler-sfc' + - supports-color + - typescript + - vitest + + '@antfu/install-pkg@1.0.0': + dependencies: + package-manager-detector: 0.2.8 + tinyexec: 0.3.2 + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.3.0 + tinyexec: 1.0.1 + + '@antfu/utils@0.7.10': {} + + '@antfu/utils@9.2.1': {} + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/parser@7.26.5': + dependencies: + '@babel/types': 7.26.5 + + '@babel/types@7.26.5': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@braintree/sanitize-url@6.0.4': + optional: true + + '@braintree/sanitize-url@7.1.1': {} + + '@chevrotain/cst-dts-gen@11.0.3': + dependencies: + '@chevrotain/gast': 11.0.3 + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/gast@11.0.3': + dependencies: + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/regexp-to-ast@11.0.3': {} + + '@chevrotain/types@11.0.3': {} + + '@chevrotain/utils@11.0.3': {} + + '@clack/core@0.4.1': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@clack/prompts@0.9.1': + dependencies: + '@clack/core': 0.4.1 + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@docsearch/css@3.8.2': {} + + '@docsearch/js@3.8.2(@algolia/client-search@5.19.0)(search-insights@2.17.3)': + dependencies: + '@docsearch/react': 3.8.2(@algolia/client-search@5.19.0)(search-insights@2.17.3) + preact: 10.25.4 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/react' + - react + - react-dom + - search-insights + + '@docsearch/react@3.8.2(@algolia/client-search@5.19.0)(search-insights@2.17.3)': + dependencies: + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.19.0)(algoliasearch@5.19.0)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.19.0)(algoliasearch@5.19.0) + '@docsearch/css': 3.8.2 + algoliasearch: 5.19.0 + optionalDependencies: + search-insights: 2.17.3 + transitivePeerDependencies: + - '@algolia/client-search' + + '@dprint/formatter@0.3.0': {} + + '@dprint/markdown@0.17.8': {} + + '@dprint/toml@0.6.4': {} + + '@es-joy/jsdoccomment@0.49.0': + dependencies: + comment-parser: 1.4.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.1.0 + + '@es-joy/jsdoccomment@0.50.0': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.8 + '@typescript-eslint/types': 8.20.0 + comment-parser: 1.4.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.1.0 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.24.2': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.24.2': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.24.2': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.24.2': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.24.2': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.24.2': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.24.2': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.24.2': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.24.2': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.24.2': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.24.2': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.24.2': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.24.2': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.24.2': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.24.2': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.24.2': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.24.2': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.24.2': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.24.2': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.24.2': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.24.2': + optional: true + + '@eslint-community/eslint-plugin-eslint-comments@4.4.1(eslint@9.33.0(jiti@2.4.2))': + dependencies: + escape-string-regexp: 4.0.0 + eslint: 9.33.0(jiti@2.4.2) + ignore: 5.3.2 + + '@eslint-community/eslint-utils@4.7.0(eslint@9.33.0(jiti@2.4.2))': + dependencies: + eslint: 9.33.0(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/compat@1.2.5(eslint@9.33.0(jiti@2.4.2))': + optionalDependencies: + eslint: 9.33.0(jiti@2.4.2) + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.3.1': {} + + '@eslint/core@0.13.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.33.0': {} + + '@eslint/markdown@6.2.1': + dependencies: + '@eslint/plugin-kit': 0.2.8 + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm: 3.0.0 + micromark-extension-gfm: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.8': + dependencies: + '@eslint/core': 0.13.0 + levn: 0.4.1 + + '@eslint/plugin-kit@0.3.5': + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@iconify-json/simple-icons@1.2.20': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify-json/simple-icons@1.2.39': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.0.2': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@antfu/utils': 9.2.1 + '@iconify/types': 2.0.0 + debug: 4.4.3 + globals: 15.15.0 + kolorist: 1.8.0 + local-pkg: 1.1.2 + mlly: 1.8.0 + transitivePeerDependencies: + - supports-color + + '@iconify/vue@4.3.0(vue@3.5.13(typescript@5.7.3))': + dependencies: + '@iconify/types': 2.0.0 + vue: 3.5.13(typescript@5.7.3) + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@mermaid-js/mermaid-mindmap@9.3.0': + dependencies: + '@braintree/sanitize-url': 6.0.4 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + khroma: 2.1.0 + non-layered-tidy-tree-layout: 2.0.2 + optional: true + + '@mermaid-js/parser@0.6.2': + dependencies: + langium: 3.3.1 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.18.0 + + '@pkgr/core@0.1.1': {} + + '@rollup/rollup-android-arm-eabi@4.31.0': + optional: true + + '@rollup/rollup-android-arm64@4.31.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.31.0': + optional: true + + '@rollup/rollup-darwin-x64@4.31.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.31.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.31.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.31.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.31.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.31.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.31.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.31.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.31.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.31.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.31.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.31.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.31.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.31.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.31.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.31.0': + optional: true + + '@shikijs/core@1.27.2': + dependencies: + '@shikijs/engine-javascript': 1.27.2 + '@shikijs/engine-oniguruma': 1.27.2 + '@shikijs/types': 1.27.2 + '@shikijs/vscode-textmate': 10.0.1 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.4 + + '@shikijs/core@2.5.0': + dependencies: + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.4 + + '@shikijs/engine-javascript@1.27.2': + dependencies: + '@shikijs/types': 1.27.2 + '@shikijs/vscode-textmate': 10.0.1 + oniguruma-to-es: 2.1.0 + + '@shikijs/engine-javascript@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 3.1.1 + + '@shikijs/engine-oniguruma@1.27.2': + dependencies: + '@shikijs/types': 1.27.2 + '@shikijs/vscode-textmate': 10.0.1 + + '@shikijs/engine-oniguruma@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@1.27.2': + dependencies: + '@shikijs/types': 1.27.2 + + '@shikijs/langs@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/themes@1.27.2': + dependencies: + '@shikijs/types': 1.27.2 + + '@shikijs/themes@2.5.0': + dependencies: + '@shikijs/types': 2.5.0 + + '@shikijs/transformers@1.27.2': + dependencies: + shiki: 1.27.2 + + '@shikijs/transformers@2.5.0': + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/types': 2.5.0 + + '@shikijs/types@1.27.2': + dependencies: + '@shikijs/vscode-textmate': 10.0.1 + '@types/hast': 3.0.4 + + '@shikijs/types@2.5.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.1': {} + + '@shikijs/vscode-textmate@10.0.2': {} + + '@stylistic/eslint-plugin@2.13.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3)': + dependencies: + '@typescript-eslint/utils': 8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + eslint: 9.33.0(jiti@2.4.2) + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + estraverse: 5.3.0 + picomatch: 4.0.2 + transitivePeerDependencies: + - supports-color + - typescript + + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.7': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/doctrine@0.0.9': {} + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.6': {} + + '@types/estree@1.0.8': {} + + '@types/geojson@7946.0.16': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdurl@2.0.0': {} + + '@types/ms@2.1.0': {} + + '@types/node@22.10.7': + dependencies: + undici-types: 6.20.0 + + '@types/normalize-package-data@2.4.4': {} + + '@types/trusted-types@2.0.7': + optional: true + + '@types/unist@3.0.3': {} + + '@types/web-bluetooth@0.0.20': {} + + '@types/web-bluetooth@0.0.21': {} + + '@typescript-eslint/eslint-plugin@8.20.0(@typescript-eslint/parser@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/type-utils': 8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/utils': 8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.20.0 + eslint: 9.33.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.20.0 + debug: 4.4.1 + eslint: 9.33.0(jiti@2.4.2) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.20.0': + dependencies: + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/visitor-keys': 8.20.0 + + '@typescript-eslint/type-utils@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3) + '@typescript-eslint/utils': 8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + debug: 4.4.3 + eslint: 9.33.0(jiti@2.4.2) + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.20.0': {} + + '@typescript-eslint/typescript-estree@8.20.0(typescript@5.7.3)': + dependencies: + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/visitor-keys': 8.20.0 + debug: 4.4.3 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 2.0.0(typescript@5.7.3) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/typescript-estree': 8.20.0(typescript@5.7.3) + eslint: 9.33.0(jiti@2.4.2) + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.20.0': + dependencies: + '@typescript-eslint/types': 8.20.0 + eslint-visitor-keys: 4.2.1 + + '@ungap/structured-clone@1.2.1': {} + + '@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@22.10.7))(vue@3.5.13(typescript@5.7.3))': + dependencies: + vite: 5.4.11(@types/node@22.10.7) + vue: 3.5.13(typescript@5.7.3) + + '@vitejs/plugin-vue@5.2.1(vite@5.4.19(@types/node@22.10.7))(vue@3.5.13(typescript@5.7.3))': + dependencies: + vite: 5.4.19(@types/node@22.10.7) + vue: 3.5.13(typescript@5.7.3) + + '@vitest/eslint-plugin@1.1.25(@typescript-eslint/utils@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3)': + dependencies: + '@typescript-eslint/utils': 8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + eslint: 9.33.0(jiti@2.4.2) + optionalDependencies: + typescript: 5.7.3 + + '@vue/compiler-core@3.5.13': + dependencies: + '@babel/parser': 7.26.5 + '@vue/shared': 3.5.13 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.13': + dependencies: + '@vue/compiler-core': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/compiler-sfc@3.5.13': + dependencies: + '@babel/parser': 7.26.5 + '@vue/compiler-core': 3.5.13 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + estree-walker: 2.0.2 + magic-string: 0.30.17 + postcss: 8.5.1 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.13': + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/devtools-api@7.7.0': + dependencies: + '@vue/devtools-kit': 7.7.0 + + '@vue/devtools-kit@7.7.0': + dependencies: + '@vue/devtools-shared': 7.7.0 + birpc: 0.2.19 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.2 + + '@vue/devtools-shared@7.7.0': + dependencies: + rfdc: 1.4.1 + + '@vue/reactivity@3.5.13': + dependencies: + '@vue/shared': 3.5.13 + + '@vue/runtime-core@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/shared': 3.5.13 + + '@vue/runtime-dom@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/runtime-core': 3.5.13 + '@vue/shared': 3.5.13 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.7.3))': + dependencies: + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + vue: 3.5.13(typescript@5.7.3) + + '@vue/shared@3.5.13': {} + + '@vueuse/core@11.3.0(vue@3.5.13(typescript@5.7.3))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 11.3.0 + '@vueuse/shared': 11.3.0(vue@3.5.13(typescript@5.7.3)) + vue-demi: 0.14.10(vue@3.5.13(typescript@5.7.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/core@12.8.2(typescript@5.7.3)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 12.8.2 + '@vueuse/shared': 12.8.2(typescript@5.7.3) + vue: 3.5.13(typescript@5.7.3) + transitivePeerDependencies: + - typescript + + '@vueuse/integrations@11.3.0(focus-trap@7.6.4)(vue@3.5.13(typescript@5.7.3))': + dependencies: + '@vueuse/core': 11.3.0(vue@3.5.13(typescript@5.7.3)) + '@vueuse/shared': 11.3.0(vue@3.5.13(typescript@5.7.3)) + vue-demi: 0.14.10(vue@3.5.13(typescript@5.7.3)) + optionalDependencies: + focus-trap: 7.6.4 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/integrations@12.8.2(focus-trap@7.6.4)(typescript@5.7.3)': + dependencies: + '@vueuse/core': 12.8.2(typescript@5.7.3) + '@vueuse/shared': 12.8.2(typescript@5.7.3) + vue: 3.5.13(typescript@5.7.3) + optionalDependencies: + focus-trap: 7.6.4 + transitivePeerDependencies: + - typescript + + '@vueuse/metadata@11.3.0': {} + + '@vueuse/metadata@12.8.2': {} + + '@vueuse/shared@11.3.0(vue@3.5.13(typescript@5.7.3))': + dependencies: + vue-demi: 0.14.10(vue@3.5.13(typescript@5.7.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/shared@12.8.2(typescript@5.7.3)': + dependencies: + vue: 3.5.13(typescript@5.7.3) + transitivePeerDependencies: + - typescript + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.14.0: {} + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + algoliasearch@5.19.0: + dependencies: + '@algolia/client-abtesting': 5.19.0 + '@algolia/client-analytics': 5.19.0 + '@algolia/client-common': 5.19.0 + '@algolia/client-insights': 5.19.0 + '@algolia/client-personalization': 5.19.0 + '@algolia/client-query-suggestions': 5.19.0 + '@algolia/client-search': 5.19.0 + '@algolia/ingestion': 1.19.0 + '@algolia/monitoring': 1.19.0 + '@algolia/recommend': 5.19.0 + '@algolia/requester-browser-xhr': 5.19.0 + '@algolia/requester-fetch': 5.19.0 + '@algolia/requester-node-http': 5.19.0 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + are-docs-informative@0.0.2: {} + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + birpc@0.2.19: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001695 + electron-to-chromium: 1.5.83 + node-releases: 2.0.19 + update-browserslist-db: 1.1.2(browserslist@4.24.4) + + builtin-modules@3.3.0: {} + + bumpp@9.10.1: + dependencies: + c12: 2.0.1 + cac: 6.7.14 + escalade: 3.2.0 + js-yaml: 4.1.0 + jsonc-parser: 3.3.1 + package-manager-detector: 0.2.8 + prompts: 2.4.2 + semver: 7.6.3 + tinyexec: 0.3.2 + tinyglobby: 0.2.10 + transitivePeerDependencies: + - magicast + + c12@2.0.1: + dependencies: + chokidar: 4.0.3 + confbox: 0.1.8 + defu: 6.1.4 + dotenv: 16.4.7 + giget: 1.2.3 + jiti: 2.4.2 + mlly: 1.7.4 + ohash: 1.1.4 + pathe: 1.1.2 + perfect-debounce: 1.0.0 + pkg-types: 1.3.1 + rc9: 2.1.2 + + cac@6.7.14: {} + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001695: {} + + ccount@2.0.1: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + chevrotain-allstar@0.3.1(chevrotain@11.0.3): + dependencies: + chevrotain: 11.0.3 + lodash-es: 4.17.21 + + chevrotain@11.0.3: + dependencies: + '@chevrotain/cst-dts-gen': 11.0.3 + '@chevrotain/gast': 11.0.3 + '@chevrotain/regexp-to-ast': 11.0.3 + '@chevrotain/types': 11.0.3 + '@chevrotain/utils': 11.0.3 + lodash-es: 4.17.21 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.1 + + chownr@2.0.0: {} + + ci-info@4.1.0: {} + + citty@0.1.6: + dependencies: + consola: 3.4.0 + + clean-regexp@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + + comment-parser@1.4.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + consola@3.4.0: {} + + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + + core-js-compat@3.40.0: + dependencies: + browserslist: 4.24.4 + + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.1 + + cytoscape-fcose@2.2.0(cytoscape@3.33.1): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.33.1 + + cytoscape@3.33.1: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.0: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.11: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.21 + + dayjs@1.11.13: {} + + dayjs@1.11.18: {} + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.0.2: + dependencies: + character-entities: 2.0.2 + + deep-is@0.1.4: {} + + defu@6.1.4: {} + + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + + dequal@2.0.3: {} + + destr@2.0.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dompurify@3.2.7: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + dotenv@16.4.7: {} + + electron-to-chromium@1.5.83: {} + + emoji-regex-xs@1.0.0: {} + + emoji-regex@8.0.0: {} + + enhanced-resolve@5.18.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + entities@4.5.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-module-lexer@1.6.0: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-compat-utils@0.5.1(eslint@9.33.0(jiti@2.4.2)): + dependencies: + eslint: 9.33.0(jiti@2.4.2) + semver: 7.6.3 + + eslint-compat-utils@0.6.4(eslint@9.33.0(jiti@2.4.2)): + dependencies: + eslint: 9.33.0(jiti@2.4.2) + semver: 7.6.3 + + eslint-config-flat-gitignore@1.0.0(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@eslint/compat': 1.2.5(eslint@9.33.0(jiti@2.4.2)) + eslint: 9.33.0(jiti@2.4.2) + find-up-simple: 1.0.0 + + eslint-flat-config-utils@1.0.0: + dependencies: + pathe: 2.0.2 + + eslint-formatting-reporter@0.0.0(eslint@9.33.0(jiti@2.4.2)): + dependencies: + eslint: 9.33.0(jiti@2.4.2) + prettier-linter-helpers: 1.0.0 + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + eslint-json-compat-utils@0.2.1(eslint@9.33.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0): + dependencies: + eslint: 9.33.0(jiti@2.4.2) + esquery: 1.6.0 + jsonc-eslint-parser: 2.4.0 + + eslint-merge-processors@1.0.0(eslint@9.33.0(jiti@2.4.2)): + dependencies: + eslint: 9.33.0(jiti@2.4.2) + + eslint-parser-plain@0.1.1: {} + + eslint-plugin-antfu@2.7.0(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@antfu/utils': 0.7.10 + eslint: 9.33.0(jiti@2.4.2) + + eslint-plugin-command@2.1.0(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@es-joy/jsdoccomment': 0.50.0 + eslint: 9.33.0(jiti@2.4.2) + + eslint-plugin-es-x@7.8.0(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + eslint: 9.33.0(jiti@2.4.2) + eslint-compat-utils: 0.5.1(eslint@9.33.0(jiti@2.4.2)) + + eslint-plugin-format@1.0.1(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@dprint/formatter': 0.3.0 + '@dprint/markdown': 0.17.8 + '@dprint/toml': 0.6.4 + eslint: 9.33.0(jiti@2.4.2) + eslint-formatting-reporter: 0.0.0(eslint@9.33.0(jiti@2.4.2)) + eslint-parser-plain: 0.1.1 + prettier: 3.4.2 + synckit: 0.9.2 + + eslint-plugin-import-x@4.6.1(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3): + dependencies: + '@types/doctrine': 0.0.9 + '@typescript-eslint/scope-manager': 8.20.0 + '@typescript-eslint/utils': 8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + debug: 4.4.1 + doctrine: 3.0.0 + enhanced-resolve: 5.18.0 + eslint: 9.33.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + get-tsconfig: 4.8.1 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + stable-hash: 0.0.4 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-jsdoc@50.6.2(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@es-joy/jsdoccomment': 0.49.0 + are-docs-informative: 0.0.2 + comment-parser: 1.4.1 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint: 9.33.0(jiti@2.4.2) + espree: 10.4.0 + esquery: 1.6.0 + parse-imports: 2.2.1 + semver: 7.6.3 + spdx-expression-parse: 4.0.0 + synckit: 0.9.2 + transitivePeerDependencies: + - supports-color + + eslint-plugin-jsonc@2.18.2(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.4.2)) + eslint: 9.33.0(jiti@2.4.2) + eslint-compat-utils: 0.6.4(eslint@9.33.0(jiti@2.4.2)) + eslint-json-compat-utils: 0.2.1(eslint@9.33.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0) + espree: 9.6.1 + graphemer: 1.4.0 + jsonc-eslint-parser: 2.4.0 + natural-compare: 1.4.0 + synckit: 0.6.2 + transitivePeerDependencies: + - '@eslint/json' + + eslint-plugin-n@17.15.1(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.4.2)) + enhanced-resolve: 5.18.0 + eslint: 9.33.0(jiti@2.4.2) + eslint-plugin-es-x: 7.8.0(eslint@9.33.0(jiti@2.4.2)) + get-tsconfig: 4.8.1 + globals: 15.14.0 + ignore: 5.3.2 + minimatch: 9.0.5 + semver: 7.6.3 + + eslint-plugin-no-only-tests@3.3.0: {} + + eslint-plugin-perfectionist@4.6.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3): + dependencies: + '@typescript-eslint/types': 8.20.0 + '@typescript-eslint/utils': 8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + eslint: 9.33.0(jiti@2.4.2) + natural-orderby: 5.0.0 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-regexp@2.7.0(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + comment-parser: 1.4.1 + eslint: 9.33.0(jiti@2.4.2) + jsdoc-type-pratt-parser: 4.1.0 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + scslre: 0.3.0 + + eslint-plugin-toml@0.12.0(eslint@9.33.0(jiti@2.4.2)): + dependencies: + debug: 4.4.1 + eslint: 9.33.0(jiti@2.4.2) + eslint-compat-utils: 0.6.4(eslint@9.33.0(jiti@2.4.2)) + lodash: 4.17.21 + toml-eslint-parser: 0.10.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-unicorn@56.0.1(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.4.2)) + ci-info: 4.1.0 + clean-regexp: 1.0.0 + core-js-compat: 3.40.0 + eslint: 9.33.0(jiti@2.4.2) + esquery: 1.6.0 + globals: 15.14.0 + indent-string: 4.0.0 + is-builtin-module: 3.2.1 + jsesc: 3.1.0 + pluralize: 8.0.0 + read-pkg-up: 7.0.1 + regexp-tree: 0.1.27 + regjsparser: 0.10.0 + semver: 7.6.3 + strip-indent: 3.0.0 + + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.20.0(@typescript-eslint/parser@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.33.0(jiti@2.4.2)): + dependencies: + eslint: 9.33.0(jiti@2.4.2) + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.20.0(@typescript-eslint/parser@8.20.0(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.33.0(jiti@2.4.2))(typescript@5.7.3) + + eslint-plugin-vue@9.32.0(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.4.2)) + eslint: 9.33.0(jiti@2.4.2) + globals: 13.24.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.6.3 + vue-eslint-parser: 9.4.3(eslint@9.33.0(jiti@2.4.2)) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-yml@1.16.0(eslint@9.33.0(jiti@2.4.2)): + dependencies: + debug: 4.4.1 + eslint: 9.33.0(jiti@2.4.2) + eslint-compat-utils: 0.6.4(eslint@9.33.0(jiti@2.4.2)) + lodash: 4.17.21 + natural-compare: 1.4.0 + yaml-eslint-parser: 1.2.3 + transitivePeerDependencies: + - supports-color + + eslint-processor-vue-blocks@1.0.0(@vue/compiler-sfc@3.5.13)(eslint@9.33.0(jiti@2.4.2)): + dependencies: + '@vue/compiler-sfc': 3.5.13 + eslint: 9.33.0(jiti@2.4.2) + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.33.0(jiti@2.4.2): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.33.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.4.2 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + exsolve@1.0.7: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.18.0: + dependencies: + reusify: 1.0.4 + + fdir@6.4.3(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up-simple@1.0.0: {} + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + focus-trap@7.6.4: + dependencies: + tabbable: 6.2.0 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-stream@8.0.1: {} + + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + giget@1.2.3: + dependencies: + citty: 0.1.6 + consola: 3.4.0 + defu: 6.1.4 + node-fetch-native: 1.6.4 + nypm: 0.3.12 + ohash: 1.1.4 + pathe: 1.1.2 + tar: 6.2.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globals@14.0.0: {} + + globals@15.14.0: {} + + globals@15.15.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + hachure-fill@0.5.2: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hast-util-to-html@9.0.4: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hookable@5.5.3: {} + + hosted-git-info@2.8.9: {} + + html-void-elements@3.0.0: {} + + human-signals@5.0.0: {} + + iconify-icon@2.3.0: + dependencies: + '@iconify/types': 2.0.0 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + internmap@1.0.1: {} + + internmap@2.0.3: {} + + is-arrayish@0.2.1: {} + + is-builtin-module@3.2.1: + dependencies: + builtin-modules: 3.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-stream@3.0.0: {} + + is-what@4.1.16: {} + + isexe@2.0.0: {} + + jiti@2.4.2: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsdoc-type-pratt-parser@4.1.0: {} + + jsesc@0.5.0: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonc-eslint-parser@2.4.0: + dependencies: + acorn: 8.14.0 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + semver: 7.6.3 + + jsonc-parser@3.3.1: {} + + katex@0.16.22: + dependencies: + commander: 8.3.0 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + khroma@2.1.0: {} + + kleur@3.0.3: {} + + kolorist@1.8.0: {} + + langium@3.3.1: + dependencies: + chevrotain: 11.0.3 + chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lines-and-columns@1.2.4: {} + + local-pkg@1.0.0: + dependencies: + mlly: 1.7.4 + pkg-types: 1.3.1 + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.17.21: {} + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + longest-streak@3.1.0: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + mark.js@8.11.1: {} + + markdown-it-task-lists@2.1.1: {} + + markdown-table@3.0.4: {} + + marked@16.3.0: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.0.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.2.1 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + mermaid@11.12.0: + dependencies: + '@braintree/sanitize-url': 7.1.1 + '@iconify/utils': 3.0.2 + '@mermaid-js/parser': 0.6.2 + '@types/d3': 7.4.3 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.11 + dayjs: 1.11.18 + dompurify: 3.2.7 + katex: 0.16.22 + khroma: 2.1.0 + lodash-es: 4.17.21 + marked: 16.3.0 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + transitivePeerDependencies: + - supports-color + + micromark-core-commonmark@2.0.2: + dependencies: + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-table@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.1 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.0 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.1 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.1 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.0.3: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.1: {} + + micromark@4.0.1: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-fn@4.0.0: {} + + min-indent@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minisearch@7.1.1: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mitt@3.0.1: {} + + mkdirp@1.0.4: {} + + mlly@1.7.4: + dependencies: + acorn: 8.14.0 + pathe: 2.0.2 + pkg-types: 1.3.1 + ufo: 1.5.4 + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + ms@2.1.3: {} + + nanoid@3.3.8: {} + + natural-compare@1.4.0: {} + + natural-orderby@5.0.0: {} + + node-fetch-native@1.6.4: {} + + node-releases@2.0.19: {} + + non-layered-tidy-tree-layout@2.0.2: + optional: true + + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.10 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + nypm@0.3.12: + dependencies: + citty: 0.1.6 + consola: 3.4.0 + execa: 8.0.1 + pathe: 1.1.2 + pkg-types: 1.3.1 + ufo: 1.5.4 + + ohash@1.1.4: {} + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + oniguruma-to-es@2.1.0: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 5.1.1 + regex-recursion: 5.1.1 + + oniguruma-to-es@3.1.1: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 6.0.1 + regex-recursion: 6.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + package-manager-detector@0.2.8: {} + + package-manager-detector@1.3.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-gitignore@2.0.0: {} + + parse-imports@2.2.1: + dependencies: + es-module-lexer: 1.6.0 + slashes: 3.0.12 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-data-parser@0.1.0: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + pathe@1.1.2: {} + + pathe@2.0.2: {} + + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.7.4 + pathe: 2.0.2 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + pluralize@8.0.0: {} + + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.1: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.25.4: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.4.2: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + property-information@6.5.0: {} + + punycode@2.3.1: {} + + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.3 + + read-pkg-up@7.0.1: + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + + read-pkg@5.2.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + + readdirp@4.1.1: {} + + refa@0.12.1: + dependencies: + '@eslint-community/regexpp': 4.12.1 + + regex-recursion@5.1.1: + dependencies: + regex: 5.1.1 + regex-utilities: 2.3.0 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@5.1.1: + dependencies: + regex-utilities: 2.3.0 + + regex@6.0.1: + dependencies: + regex-utilities: 2.3.0 + + regexp-ast-analysis@0.7.1: + dependencies: + '@eslint-community/regexpp': 4.12.1 + refa: 0.12.1 + + regexp-tree@0.1.27: {} + + regjsparser@0.10.0: + dependencies: + jsesc: 0.5.0 + + require-directory@2.1.1: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.0.4: {} + + rfdc@1.4.1: {} + + robust-predicates@3.0.2: {} + + rollup@4.31.0: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.31.0 + '@rollup/rollup-android-arm64': 4.31.0 + '@rollup/rollup-darwin-arm64': 4.31.0 + '@rollup/rollup-darwin-x64': 4.31.0 + '@rollup/rollup-freebsd-arm64': 4.31.0 + '@rollup/rollup-freebsd-x64': 4.31.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.31.0 + '@rollup/rollup-linux-arm-musleabihf': 4.31.0 + '@rollup/rollup-linux-arm64-gnu': 4.31.0 + '@rollup/rollup-linux-arm64-musl': 4.31.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.31.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.31.0 + '@rollup/rollup-linux-riscv64-gnu': 4.31.0 + '@rollup/rollup-linux-s390x-gnu': 4.31.0 + '@rollup/rollup-linux-x64-gnu': 4.31.0 + '@rollup/rollup-linux-x64-musl': 4.31.0 + '@rollup/rollup-win32-arm64-msvc': 4.31.0 + '@rollup/rollup-win32-ia32-msvc': 4.31.0 + '@rollup/rollup-win32-x64-msvc': 4.31.0 + fsevents: 2.3.3 + + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + + scslre@0.3.0: + dependencies: + '@eslint-community/regexpp': 4.12.1 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + + search-insights@2.17.3: {} + + semver@5.7.2: {} + + semver@7.6.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shiki@1.27.2: + dependencies: + '@shikijs/core': 1.27.2 + '@shikijs/engine-javascript': 1.27.2 + '@shikijs/engine-oniguruma': 1.27.2 + '@shikijs/langs': 1.27.2 + '@shikijs/themes': 1.27.2 + '@shikijs/types': 1.27.2 + '@shikijs/vscode-textmate': 10.0.1 + '@types/hast': 3.0.4 + + shiki@2.5.0: + dependencies: + '@shikijs/core': 2.5.0 + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/langs': 2.5.0 + '@shikijs/themes': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + slashes@3.0.12: {} + + source-map-js@1.2.1: {} + + space-separated-tokens@2.0.2: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.21 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.21 + + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.21 + + spdx-license-ids@3.0.21: {} + + speakingurl@14.0.1: {} + + stable-hash@0.0.4: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-final-newline@3.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + stylis@4.3.6: {} + + superjson@2.2.2: + dependencies: + copy-anything: 3.0.5 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + synckit@0.6.2: + dependencies: + tslib: 2.8.1 + + synckit@0.9.2: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.8.1 + + tabbable@6.2.0: {} + + tapable@2.2.1: {} + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + tinyexec@0.3.2: {} + + tinyexec@1.0.1: {} + + tinyglobby@0.2.10: + dependencies: + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toml-eslint-parser@0.10.0: + dependencies: + eslint-visitor-keys: 3.4.3 + + trim-lines@3.0.1: {} + + ts-api-utils@2.0.0(typescript@5.7.3): + dependencies: + typescript: 5.7.3 + + ts-dedent@2.2.0: {} + + tslib@2.8.1: {} + + twikoo@1.6.41: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + type-fest@0.6.0: {} + + type-fest@0.8.1: {} + + typescript@5.7.3: {} + + ufo@1.5.4: {} + + ufo@1.6.1: {} + + undici-types@6.20.0: {} + + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + update-browserslist-db@1.1.2(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + uuid@11.1.0: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + vfile-message@4.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + + vite@5.4.11(@types/node@22.10.7): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.1 + rollup: 4.31.0 + optionalDependencies: + '@types/node': 22.10.7 + fsevents: 2.3.3 + + vite@5.4.19(@types/node@22.10.7): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.1 + rollup: 4.31.0 + optionalDependencies: + '@types/node': 22.10.7 + fsevents: 2.3.3 + + vite@6.0.7(@types/node@22.10.7)(jiti@2.4.2)(yaml@2.7.0): + dependencies: + esbuild: 0.24.2 + postcss: 8.5.1 + rollup: 4.31.0 + optionalDependencies: + '@types/node': 22.10.7 + fsevents: 2.3.3 + jiti: 2.4.2 + yaml: 2.7.0 + + vitepress-plugin-mermaid@2.0.17(mermaid@11.12.0)(vitepress@1.6.3(@algolia/client-search@5.19.0)(@types/node@22.10.7)(postcss@8.5.1)(search-insights@2.17.3)(typescript@5.7.3)): + dependencies: + mermaid: 11.12.0 + vitepress: 1.6.3(@algolia/client-search@5.19.0)(@types/node@22.10.7)(postcss@8.5.1)(search-insights@2.17.3)(typescript@5.7.3) + optionalDependencies: + '@mermaid-js/mermaid-mindmap': 9.3.0 + + vitepress@1.5.0(@algolia/client-search@5.19.0)(@types/node@22.10.7)(postcss@8.5.1)(search-insights@2.17.3)(typescript@5.7.3): + dependencies: + '@docsearch/css': 3.8.2 + '@docsearch/js': 3.8.2(@algolia/client-search@5.19.0)(search-insights@2.17.3) + '@iconify-json/simple-icons': 1.2.20 + '@shikijs/core': 1.27.2 + '@shikijs/transformers': 1.27.2 + '@shikijs/types': 1.27.2 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.2.1(vite@5.4.11(@types/node@22.10.7))(vue@3.5.13(typescript@5.7.3)) + '@vue/devtools-api': 7.7.0 + '@vue/shared': 3.5.13 + '@vueuse/core': 11.3.0(vue@3.5.13(typescript@5.7.3)) + '@vueuse/integrations': 11.3.0(focus-trap@7.6.4)(vue@3.5.13(typescript@5.7.3)) + focus-trap: 7.6.4 + mark.js: 8.11.1 + minisearch: 7.1.1 + shiki: 1.27.2 + vite: 5.4.11(@types/node@22.10.7) + vue: 3.5.13(typescript@5.7.3) + optionalDependencies: + postcss: 8.5.1 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - '@vue/composition-api' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - react + - react-dom + - sass + - sass-embedded + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + + vitepress@1.6.3(@algolia/client-search@5.19.0)(@types/node@22.10.7)(postcss@8.5.1)(search-insights@2.17.3)(typescript@5.7.3): + dependencies: + '@docsearch/css': 3.8.2 + '@docsearch/js': 3.8.2(@algolia/client-search@5.19.0)(search-insights@2.17.3) + '@iconify-json/simple-icons': 1.2.39 + '@shikijs/core': 2.5.0 + '@shikijs/transformers': 2.5.0 + '@shikijs/types': 2.5.0 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 5.2.1(vite@5.4.19(@types/node@22.10.7))(vue@3.5.13(typescript@5.7.3)) + '@vue/devtools-api': 7.7.0 + '@vue/shared': 3.5.13 + '@vueuse/core': 12.8.2(typescript@5.7.3) + '@vueuse/integrations': 12.8.2(focus-trap@7.6.4)(typescript@5.7.3) + focus-trap: 7.6.4 + mark.js: 8.11.1 + minisearch: 7.1.1 + shiki: 2.5.0 + vite: 5.4.19(@types/node@22.10.7) + vue: 3.5.13(typescript@5.7.3) + optionalDependencies: + postcss: 8.5.1 + transitivePeerDependencies: + - '@algolia/client-search' + - '@types/node' + - '@types/react' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - react + - react-dom + - sass + - sass-embedded + - search-insights + - sortablejs + - stylus + - sugarss + - terser + - typescript + - universal-cookie + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-uri@3.0.8: {} + + vue-demi@0.14.10(vue@3.5.13(typescript@5.7.3)): + dependencies: + vue: 3.5.13(typescript@5.7.3) + + vue-eslint-parser@9.4.3(eslint@9.33.0(jiti@2.4.2)): + dependencies: + debug: 4.4.1 + eslint: 9.33.0(jiti@2.4.2) + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + lodash: 4.17.21 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + vue@3.5.13(typescript@5.7.3): + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-sfc': 3.5.13 + '@vue/runtime-dom': 3.5.13 + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.7.3)) + '@vue/shared': 3.5.13 + optionalDependencies: + typescript: 5.7.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + xml-name-validator@4.0.0: {} + + y18n@5.0.8: {} + + yallist@4.0.0: {} + + yaml-eslint-parser@1.2.3: + dependencies: + eslint-visitor-keys: 3.4.3 + lodash: 4.17.21 + yaml: 2.7.0 + + yaml@2.7.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..efdeaff --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - . + - .vitepress/third_party/lumen/src diff --git a/public/.well-known/applinking.json b/public/.well-known/applinking.json new file mode 100644 index 0000000..12aa83e --- /dev/null +++ b/public/.well-known/applinking.json @@ -0,0 +1,9 @@ +{ + "applinking": { + "apps": [ + { + "appIdentifier": "6917581172490635834" + } + ] + } +} diff --git a/public/easytier.png b/public/easytier.png new file mode 100644 index 0000000..26c6d8f Binary files /dev/null and b/public/easytier.png differ diff --git a/public/gui-config-dark.png b/public/gui-config-dark.png new file mode 100644 index 0000000..64a28fe Binary files /dev/null and b/public/gui-config-dark.png differ diff --git a/public/gui-config-light.png b/public/gui-config-light.png new file mode 100644 index 0000000..cb7ed3d Binary files /dev/null and b/public/gui-config-light.png differ diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..38e60bd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ESNext", + "jsx": "preserve", + "baseUrl": ".", + "module": "ESNext", + "moduleResolution": "node", + "paths": { + "@theme/*": [ + ".vitepress/theme/*" + ] + }, + "resolveJsonModule": true, + "allowJs": true, + "strict": true, + "outDir": "dist", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true + }, + "include": [ + "env.d.ts", + ".vitepress/**/*" + ] +}