Merge branch 'main' into quest-file

This commit is contained in:
Lin, Hung-Shin committed 2023-11-22 11:39:24 +08:00
commit 21ca083628
46 files changed
+653 -220

No files matched your search

+1 -1
View File
@@ -17,7 +17,7 @@ module.exports = {
sourceType: 'module', sourceType: 'module',
}, },
rules: { rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-console': 'warn',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'import/extensions': 'off', 'import/extensions': 'off',
camelcase: ['error', { properties: 'never', ignoreDestructuring: true }], camelcase: ['error', { properties: 'never', ignoreDestructuring: true }],
+11 -2
View File
@@ -2,6 +2,15 @@ name: CI Checks
on: on:
workflow_call: workflow_call:
secrets:
GOOGLE_CLIENT_ID:
required: true
GOOGLE_CLIENT_SECRET:
required: true
GH_CLIENT_ID:
required: true
GH_CLIENT_SECRET:
required: true
jobs: jobs:
ci: ci:
@@ -40,6 +49,6 @@ jobs:
env: env:
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
GITHUB_CLIENT_ID: ${{ secrets.GITHUB_CLIENT_ID }} GITHUB_CLIENT_ID: ${{ secrets.GH_CLIENT_ID }}
GITHUB_CLIENT_SECRET: ${{ secrets.GITHUB_CLIENT_SECRET }} GITHUB_CLIENT_SECRET: ${{ secrets.GH_CLIENT_SECRET }}
run: docker-compose -f docker-compose.testing.yml run cypress run: docker-compose -f docker-compose.testing.yml run cypress
+1
View File
@@ -12,6 +12,7 @@ concurrency:
jobs: jobs:
ci-checks: ci-checks:
uses: ./.github/workflows/ci-checks.yml uses: ./.github/workflows/ci-checks.yml
secrets: inherit
build: build:
needs: ci-checks needs: ci-checks
+1
View File
@@ -10,6 +10,7 @@ concurrency:
jobs: jobs:
ci-checks: ci-checks:
uses: ./.github/workflows/ci-checks.yml uses: ./.github/workflows/ci-checks.yml
secrets: inherit
code_review: code_review:
permissions: permissions:
+1
View File
@@ -12,6 +12,7 @@ concurrency:
jobs: jobs:
ci-checks: ci-checks:
uses: ./.github/workflows/ci-checks.yml uses: ./.github/workflows/ci-checks.yml
secrets: inherit
build: build:
needs: ci-checks needs: ci-checks
+87 -19
View File
@@ -41,7 +41,7 @@ const hasSocialLogins = computed(() => {
}) })
const success = () => { const success = () => {
router.push({ name: 'game', params: { questId: 'helloworld' } }) router.push({ name: 'map' })
} }
const error = (err) => { const error = (err) => {
@@ -100,6 +100,7 @@ const handleChange = () => {
<form @submit.prevent="handleSubmit()" class="w-full"> <form @submit.prevent="handleSubmit()" class="w-full">
<div v-if="socialLogin && hasSocialLogins"> <div v-if="socialLogin && hasSocialLogins">
<a <a
id="GoogleLogin"
v-if="availableMethods.google" v-if="availableMethods.google"
class="inline-flex justify-center items-center rounded-lg py-2 mt-3 bg-bg-primary text-text-secondary w-full border-text-secondary border-2" class="inline-flex justify-center items-center rounded-lg py-2 mt-3 bg-bg-primary text-text-secondary w-full border-text-secondary border-2"
href="/api/v1/auth/google" href="/api/v1/auth/google"
@@ -108,9 +109,13 @@ const handleChange = () => {
:icon="['fab', 'google']" :icon="['fab', 'google']"
class="text-text-secondary px-3 text-lg" class="text-text-secondary px-3 text-lg"
/> />
<span class="font-medium">Continue with Google</span> <span class="font-medium">
<!-- Continue with Google -->
以 Google 繼續
</span>
</a> </a>
<a <a
id="GitHubLogin"
v-if="availableMethods.github" v-if="availableMethods.github"
class="inline-flex justify-center items-center rounded-lg py-2 mt-3 bg-bg-primary text-text-secondary w-full border-text-secondary border-2" class="inline-flex justify-center items-center rounded-lg py-2 mt-3 bg-bg-primary text-text-secondary w-full border-text-secondary border-2"
href="/api/v1/auth/github" href="/api/v1/auth/github"
@@ -119,17 +124,22 @@ const handleChange = () => {
:icon="['fab', 'github']" :icon="['fab', 'github']"
class="text-text-secondary px-3 text-lg" class="text-text-secondary px-3 text-lg"
/> />
<span class="font-medium">Continue with GitHub</span> <span class="font-medium">
<!-- Continue with GitHub -->
以 GitHub 繼續
</span>
</a> </a>
<p <p
id="or"
class="text-text-secondary flex justify-center mt-3" class="text-text-secondary flex justify-center mt-3"
v-if="type === 'login' || type === 'register'" v-if="type === 'login' || type === 'register'"
> >
or <!-- or -->
或
</p> </p>
</div> </div>
<div class="mb-6"> <div class="mb-6">
<input <!-- <input
type="text" type="text"
id="username" id="username"
ref="usernameInput" ref="usernameInput"
@@ -139,6 +149,17 @@ const handleChange = () => {
required required
@input="handleChange()" @input="handleChange()"
autocomplete="username" autocomplete="username"
/> -->
<input
type="text"
id="username"
ref="usernameInput"
class="my-4 bg-bg-primary text-text-primary bg- rounded-md block w-full px-3 h-10 shadow-sm focus:outline-none placeholder:text-text-line focus:ring-2 focus:ring-text-primary ring-1 ring-bg-secondary"
:placeholder="type === 'login' ? '電子郵件 / 帳號名稱' : '帳號名稱'"
v-model="username"
required
@input="handleChange()"
autocomplete="username"
/> />
<label <label
class="text-sm font-normal text-text-secondary" class="text-sm font-normal text-text-secondary"
@@ -149,7 +170,7 @@ const handleChange = () => {
</label> </label>
</div> </div>
<div class="mb-6" v-if="type === 'register'"> <div class="mb-6" v-if="type === 'register'">
<input <!-- <input
type="email" type="email"
id="email" id="email"
ref="emailInput" ref="emailInput"
@@ -159,10 +180,21 @@ const handleChange = () => {
required required
@input="handleChange()" @input="handleChange()"
autocomplete="email" autocomplete="email"
/> -->
<input
type="email"
id="email"
ref="emailInput"
class="my-4 bg-bg-primary text-text-primary bg- rounded-md block w-full px-3 h-10 shadow-sm focus:outline-none placeholder:text-text-line focus:ring-2 focus:ring-text-primary ring-1 ring-bg-secondary"
placeholder="電子郵件"
v-model="email"
required
@input="handleChange()"
autocomplete="email"
/> />
</div> </div>
<div class="mb-6" v-if="type === 'login' || type === 'register'"> <div class="mb-6" v-if="type === 'login' || type === 'register'">
<input <!-- <input
type="password" type="password"
id="password" id="password"
class="my-4 bg-bg-primary text-text-primary bg- rounded-md block w-full px-3 h-10 shadow-sm focus:outline-none placeholder:text-text-line focus:ring-2 focus:ring-text-primary ring-1 ring-bg-secondary" class="my-4 bg-bg-primary text-text-primary bg- rounded-md block w-full px-3 h-10 shadow-sm focus:outline-none placeholder:text-text-line focus:ring-2 focus:ring-text-primary ring-1 ring-bg-secondary"
@@ -170,30 +202,66 @@ const handleChange = () => {
v-model="password" v-model="password"
required required
@input="handleChange()" @input="handleChange()"
/> -->
<input
type="password"
id="password"
class="my-4 bg-bg-primary text-text-primary bg- rounded-md block w-full px-3 h-10 shadow-sm focus:outline-none placeholder:text-text-line focus:ring-2 focus:ring-text-primary ring-1 ring-bg-secondary"
placeholder="密碼"
v-model="password"
required
@input="handleChange()"
/> />
</div> </div>
<p class="text-error flex justify-center" v-if="errorMessage"> <p
id="ErrorDisplay"
class="text-error flex justify-center"
v-if="errorMessage"
>
{{ errorMessage }} {{ errorMessage }}
</p> </p>
<button <button
id="LogInOrSignUp"
class="inline-flex justify-center rounded-lg font-black py-2 bg-text-primary text-bg w-full" class="inline-flex justify-center rounded-lg font-black py-2 bg-text-primary text-bg w-full"
type="submit" type="submit"
> >
<span v-if="type === 'login'">Log In</span> <span v-if="type === 'login'">
<span v-else>Sign Up</span> <!-- Log In -->
登入
</span>
<span v-else>
<!-- Sign Up -->
註冊
</span>
</button> </button>
<p class="text-text flex justify-center mt-3"> <p id="SwitchLoginRegister" class="text-text flex justify-center mt-3">
<span v-if="type === 'register'" <span id="CheckRegistered" v-if="type === 'register'">
>Already have an account? <!-- Already have an account? -->
<RouterLink class="text-text-primary font-bold" to="/login"> 已經有帳號了嗎?
<u>Log in</u> <RouterLink
id="SwitchtoLogin"
class="text-text-primary font-bold"
to="/login"
>
<u>
<!-- Log in -->
登入
</u>
</RouterLink></span </RouterLink></span
> >
<span v-else-if="type === 'login'" <span id="CheckRegistered" v-else-if="type === 'login'">
>Don't have an account? <!-- Don't have an account? -->
<RouterLink class="text-text-primary font-bold" to="/register"> 還沒有帳號嗎?
<u>Sign up</u> <RouterLink
id="SwitchtoRegister"
class="text-text-primary font-bold"
to="/register"
>
<u>
<!-- Sign up -->
註冊
</u>
</RouterLink></span </RouterLink></span
> >
</p> </p>
+8 -2
View File
@@ -1,7 +1,13 @@
<template> <template>
<font-awesome-icon :icon="['fas', 'list']" class="text-text-primary p-1" /> <font-awesome-icon :icon="['fas', 'list']" class="text-text-primary p-1" />
<h1 class="inline text-text-primary font-xl">Command Cheatsheets</h1> <h1 id="CommandBook" class="inline text-text-primary font-xl">
<!-- Command Cheatsheets -->
指令之書
</h1>
<div id="cheatsheets"> <div id="cheatsheets">
<p class="text-text-secondary">--Command List--</p> <p class="text-text-secondary">
<!-- --Command List-- -->
-- --指令列表-- --
</p>
</div> </div>
</template> </template>
+7 -2
View File
@@ -18,14 +18,19 @@ setTimeout(() => {
<div v-if="showModal" class="modal"> <div v-if="showModal" class="modal">
<div class="modal-content bg-bg-secondary rounded-lg"> <div class="modal-content bg-bg-secondary rounded-lg">
<span class="close text-text" @click="closeModal">&times;</span> <span class="close text-text" @click="closeModal">&times;</span>
<h2 class="text-2xl text-text mb-5 w-full inline-flex justify-center"> <h2
Quest Completed! id="QuestCompleted"
class="text-2xl text-text mb-5 w-full inline-flex justify-center"
>
<!-- Quest Completed! -->
關卡完成!
</h2> </h2>
<p class="text-base text-text"> <p class="text-base text-text">
您已成功完成挑戰,真的非常感謝您的參與!<br /> 您已成功完成挑戰,真的非常感謝您的參與!<br />
如果您能撥冗填寫這份問卷,我們將不勝感激,這對我們非常重要! 如果您能撥冗填寫這份問卷,我們將不勝感激,這對我們非常重要!
</p> </p>
<a <a
id="SurveyButton"
:href="surveyUrl" :href="surveyUrl"
target="_blank" target="_blank"
class="inline-flex justify-center rounded-lg text-base font-black py-2 mt-3 bg-text-primary w-full" class="inline-flex justify-center rounded-lg text-base font-black py-2 mt-3 bg-text-primary w-full"
+39 -14
View File
@@ -1,12 +1,3 @@
<script setup>
import { createSession } from '../store/session'
const reset = async () => {
console.log('resetting...')
await createSession()
}
</script>
<template> <template>
<section class="h-[10%] p-3 grid grid-cols-3 gap-4 place-content-between"> <section class="h-[10%] p-3 grid grid-cols-3 gap-4 place-content-between">
<button disabled id="solution" class="bg-bg-disabled rounded-lg p-2"> <button disabled id="solution" class="bg-bg-disabled rounded-lg p-2">
@@ -14,21 +5,55 @@ const reset = async () => {
:icon="['far', 'circle-question']" :icon="['far', 'circle-question']"
class="text-text-disabled" class="text-text-disabled"
/> />
<p class="text-text-disabled inline ml-1">Solution</p> <p class="text-text-disabled inline ml-1">
<!-- Solution -->
解答
</p>
</button> </button>
<button id="reset" class="bg-bg-secondary rounded-lg p-2" @click="reset"> <button id="reset" class="bg-bg-secondary rounded-lg p-2" @click="reset">
<font-awesome-icon <font-awesome-icon
:icon="['fas', 'arrow-rotate-left']" :icon="['fas', 'arrow-rotate-left']"
class="text-text-primary" class="text-text-primary"
/> />
<p class="text-text-primary inline ml-1">Reset</p> <p class="text-text-primary inline ml-1">
<!-- Reset -->
重來
</p>
</button> </button>
<button disabled id="continue" class="bg-bg-disabled rounded-lg p-2"> <button
id="continue"
class="bg-bg-secondary rounded-lg p-2"
@click="continuePlay"
>
<font-awesome-icon <font-awesome-icon
:icon="['far', 'circle-right']" :icon="['far', 'circle-right']"
class="text-text-disabled" class="text-text-primary"
/> />
<p class="text-text-disabled inline ml-1">Continue</p> <p class="text-text-primary inline ml-1">
<!-- Continue -->
繼續
</p>
</button> </button>
</section> </section>
</template> </template>
<script setup>
import { useRouter } from 'vue-router'
import { useToast } from 'vue-toastification'
import { createSession } from '../store/session'
const router = useRouter()
const reset = async () => {
console.log('resetting...')
await createSession()
}
const continuePlay = async () => {
try {
router.push({ name: 'map' })
} catch (err) {
// useToast().error('Failed to load map')
useToast().error('無法讀取地圖')
console.error(err)
}
}
</script>
+19 -1
View File
@@ -15,7 +15,17 @@ const handleLogout = async () => {
reset() reset()
router.push({ name: 'login' }) router.push({ name: 'login' })
} catch (err) { } catch (err) {
toast.error('Failed to logout') // toast.error('Failed to logout')
toast.error('登出失敗')
console.error(err)
}
}
const handleMap = async () => {
try {
router.push({ name: 'map' })
} catch (err) {
// toast.error('Failed to load map')
toast.error('無法讀取地圖')
console.error(err) console.error(err)
} }
} }
@@ -38,7 +48,9 @@ const handleLogout = async () => {
/> />
</button> --> </button> -->
<p <p
id="HeaderText"
class="text-text-primary inline-block font-bold whitespace-nowrap" class="text-text-primary inline-block font-bold whitespace-nowrap"
style="font-size: 2vh"
v-if="sessionStore.quest" v-if="sessionStore.quest"
> >
{{ sessionStore.quest.title }} {{ sessionStore.quest.title }}
@@ -63,6 +75,12 @@ const handleLogout = async () => {
class="text-text-primary h-full w-full" class="text-text-primary h-full w-full"
/> />
</button> --> </button> -->
<button title="Map" @click="handleMap" class="h-5 w-5">
<font-awesome-icon
:icon="['fas', 'map']"
class="text-text-primary h-full w-full"
/>
</button>
<a <a
title="Bug Report" title="Bug Report"
:href="bugReportUrl" :href="bugReportUrl"
+5 -2
View File
@@ -30,8 +30,11 @@ const right = () => {
:icon="['far', 'lightbulb']" :icon="['far', 'lightbulb']"
class="text-yellow-200 p-2 content-center" class="text-yellow-200 p-2 content-center"
/> />
<h1 class="inline text-text font-xl p-2 m-1">Hint</h1> <h1 class="inline text-text w-1/2 font-xl p-2 m-1">
<div v-if="current !== -1" class="w-full flex justify-end"> <!-- Hint -->
提示
</h1>
<div v-if="current !== -1" class="flex w-full justify-end items-end">
<button <button
class="p-2 m-1 w-1/8" class="p-2 m-1 w-1/8"
id="Lbutton" id="Lbutton"
+10 -6
View File
@@ -14,6 +14,7 @@ const marginX = 100
const marginY = 65 const marginY = 65
const chartContainer = ref(null) const chartContainer = ref(null)
let chartInstance = null let chartInstance = null
const fullwidth = window.screen.width
use([GraphChart, SVGRenderer, TitleComponent, TooltipComponent]) use([GraphChart, SVGRenderer, TitleComponent, TooltipComponent])
@@ -49,11 +50,11 @@ const genOption = (nodes, edges) => ({
symbol: () => { symbol: () => {
return NodeImage return NodeImage
}, },
symbolSize: [200, 100], symbolSize: [fullwidth / 10, fullwidth / 20],
roam: true, roam: false,
label: { label: {
show: true, show: true,
fontSize: 22, fontSize: fullwidth / 90,
}, },
edgeSymbol: ['pin', 'arrow'], edgeSymbol: ['pin', 'arrow'],
edgeSymbolSize: [2, 15], edgeSymbolSize: [2, 15],
@@ -126,7 +127,8 @@ function initChart(option) {
if (unlocked) { if (unlocked) {
router.push({ name: 'game', params: { questId: id } }) router.push({ name: 'game', params: { questId: id } })
} else { } else {
toast.warning('You have not completed the previous quest yet!') // toast.warning('You have not completed the previous quest yet!')
toast.warning('你還沒完成前一個關卡!')
} }
}) })
} }
@@ -147,9 +149,11 @@ onMounted(async () => {
class="p-2 stroke-2 scale-y-120 justify-center items-center" class="p-2 stroke-2 scale-y-120 justify-center items-center"
/> />
<h1 <h1
class="text-text-primary text-xl p-2.5 absolute w-fit z-2 font-mono font-bold flex flax-wrap" class="p-4 absolute w-fit z-2 font-mono flex flax-wrap text-xl"
style="width: 30%; height: 6%; font-size: 3vh; color: #00ff00"
> >
Get through your linux journey! <!-- Get through your linux journey! -->
踏上你的Linux冒險之旅吧!
</h1> </h1>
<div class="flex flex-wrap absolute w-full h-full z-1"> <div class="flex flex-wrap absolute w-full h-full z-1">
<div ref="chartContainer" class="w-full h-full flex flex-wrap"></div> <div ref="chartContainer" class="w-full h-full flex flex-wrap"></div>
+5 -2
View File
@@ -11,8 +11,11 @@ import sessionStore from '../store/session'
<div> <div>
<div v-if="sessionStore.quest" class="whitespace-pre-wrap"> <div v-if="sessionStore.quest" class="whitespace-pre-wrap">
<p class="text-text">{{ sessionStore.quest.instruction }}</p> <p class="text-text">{{ sessionStore.quest.instruction }}</p>
<hr class="border-border border-2 my-2" /> <br />
<p class="text-text">Tasks:</p> <p id="tasks" class="text-text">
<!-- Tasks: -->
任務:
</p>
<ul v-if="sessionStore.session"> <ul v-if="sessionStore.session">
<li v-for="task in sessionStore.session.tasks" :key="task.id"> <li v-for="task in sessionStore.session.tasks" :key="task.id">
<p v-if="task.completed" class="text-text-primary"> <p v-if="task.completed" class="text-text-primary">
+13 -8
View File
@@ -1,3 +1,16 @@
<template>
<font-awesome-icon :icon="['fas', 'terminal']" class="text-text p-1" />
<button id="Terminal" class="text-text h-[5%]">
<!-- Terminal -->
終端機
</button>
<div
id="terminal"
ref="terminal"
class="bg-bg-secondary h-[calc(100%-5%)] p-0.5"
></div>
</template>
<script setup> <script setup>
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { useTerminal } from '../store/session' import { useTerminal } from '../store/session'
@@ -10,11 +23,3 @@ onMounted(() => {
window.addEventListener('resize', () => term.resizeScreen()) window.addEventListener('resize', () => term.resizeScreen())
}) })
</script> </script>
<template>
<div class="h-[10%] w-full max-h-full">
<font-awesome-icon :icon="['fas', 'terminal']" class="text-text p-1" />
<button class="text-text h-full">Terminal</button>
</div>
<div id="terminal" ref="terminal" class="h-[90%] p-2"></div>
</template>
+2
View File
@@ -15,6 +15,7 @@ import {
faArrowRight, faArrowRight,
faBug, faBug,
faCircleExclamation, faCircleExclamation,
faMap,
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { import {
faLightbulb, faLightbulb,
@@ -36,6 +37,7 @@ library.add(
faArrowRightFromBracket, faArrowRightFromBracket,
faArrowRight, faArrowRight,
faBug, faBug,
faMap,
faCircleExclamation, faCircleExclamation,
faLightbulb, faLightbulb,
faCircleQuestion, faCircleQuestion,
+4 -9
View File
@@ -10,11 +10,9 @@ const routes = [
{ {
path: '/', path: '/',
redirect: { redirect: {
name: 'game', name: 'register',
params: {
questId: 'helloworld',
},
}, },
meta: { requiresGuest: true },
}, },
{ {
path: '/login', path: '/login',
@@ -66,13 +64,10 @@ router.beforeEach(async (to, from, next) => {
}) })
} else if (to.meta.requiresGuest && loggedIn) { } else if (to.meta.requiresGuest && loggedIn) {
next({ next({
name: 'game', // Or any default authenticated user route name: 'map',
params: {
questId: 'helloworld',
},
}) })
} else { } else {
next() // Always call next() at the end next()
} }
}) })
+8 -4
View File
@@ -8,21 +8,24 @@ import { checkUsername, chooseUsername } from '../utils/auth'
async function check({ username, error }) { async function check({ username, error }) {
if (username) { if (username) {
if (!isValidUsername(username)) { if (!isValidUsername(username)) {
error('Invalid username.') // error('Invalid username.')
error('無效的帳號名稱')
return return
} }
try { try {
await checkUsername(username) await checkUsername(username)
} catch (err) { } catch (err) {
if (err instanceof TooManyRequestsError) { if (err instanceof TooManyRequestsError) {
error('Too many requests. Try again in 2 minutes.') // error('Too many requests. Try again in 2 minutes.')
error('太多請求,兩分鐘後再試一次。')
return return
} }
if (err instanceof ValidationError) { if (err instanceof ValidationError) {
error(err.message) error(err.message)
return return
} }
error('Something went wrong. Please try again later.') // error('Something went wrong. Please try again later.')
error('出了點問題,請再試一次。')
} }
} }
} }
@@ -33,7 +36,8 @@ async function handleSubmit({ username, success, error }) {
success() success()
} catch (err) { } catch (err) {
if (err instanceof TooManyRequestsError) { if (err instanceof TooManyRequestsError) {
error('Too many requests. Try again in 2 minutes.') // error('Too many requests. Try again in 2 minutes.')
error('太多請求,兩分鐘後再試一次。')
return return
} }
if (err instanceof ValidationError) { if (err instanceof ValidationError) {
+6 -4
View File
@@ -30,13 +30,15 @@ onMounted(async () => {
await init(props.questId) await init(props.questId)
} catch (err) { } catch (err) {
if (err instanceof LoadQuestError) { if (err instanceof LoadQuestError) {
toast.error(`Failed to load quest: ${err.questId}. Please check the URL.`) // toast.error(`Failed to load quest: ${err.questId}. Please check the URL.`)
toast.error(`無法讀取關卡: ${err.questId},請確認網頁連結。`)
return return
} }
if (err instanceof LoadSessionError) { if (err instanceof LoadSessionError) {
toast.error( // toast.error(
`Failed to create session for quest: ${err.questId}. Please re-login and try again.` // `Failed to create session for quest: ${err.questId}. Please re-login and try again.`
) // )
toast.error(`無法建立工作階段: ${err.questId},請重新登入再試一次。`)
return return
} }
console.error(err) console.error(err)
+8 -4
View File
@@ -15,19 +15,23 @@ const handleLogin = async ({ username, password, success, error }) => {
if (isSuccess) success() if (isSuccess) success()
} catch (err) { } catch (err) {
if (err instanceof TooManyRequestsError) { if (err instanceof TooManyRequestsError) {
error('Too many requests. Try again in 2 minutes.') // error('Too many requests. Try again in 2 minutes.')
error('太多請求,兩分鐘後再試一次。')
return return
} }
if (err instanceof UnauthorizedError) { if (err instanceof UnauthorizedError) {
error('Wrong username or password.') // error('Wrong username or password.')
error('錯誤的帳號名稱或密碼。')
return return
} }
if (err instanceof ValidationError) { if (err instanceof ValidationError) {
error('Invalid username or password.') // error('Invalid username or password.')
error('無效的帳號名稱或密碼。')
return return
} }
console.error(err) console.error(err)
error('Something went wrong.') // error('Something went wrong.')
error('出了點問題。')
} }
} }
</script> </script>
+4 -3
View File
@@ -7,10 +7,11 @@ import GameHeaderPart from '../components/GameHeaderPart.vue'
<div class="h-[6vh] w-full"> <div class="h-[6vh] w-full">
<GameHeaderPart /> <GameHeaderPart />
<p <p
class="text-text-primary absolute z-1 font-bold inline-block" class="text-text-primary absolute z-1 font-bold inline-block tracking-widest"
style="left: 1.2%; top: 1.5%; height: 100%" style="left: 1.2%; top: 1.5%; height: 100%; font-size: 2vh"
> >
Quest Map <!-- Quest Map -->
Unix編年史
</p> </p>
</div> </div>
<div class="flex flex-col justify-center items-center w-full h-[94vh]"> <div class="flex flex-col justify-center items-center w-full h-[94vh]">
+18 -10
View File
@@ -17,11 +17,13 @@ function handleRegister({ username, email, password, success, error }) {
.then(success) .then(success)
.catch((err) => { .catch((err) => {
if (err instanceof TooManyRequestsError) { if (err instanceof TooManyRequestsError) {
error('Too many requests. Try again in 2 minutes.') // error('Too many requests. Try again in 2 minutes.')
error('太多請求,兩分鐘後再試一次。')
return return
} }
if (err instanceof UnauthorizedError) { if (err instanceof UnauthorizedError) {
error('Invalid username or password.') // error('Invalid username or password.')
error('無效的帳號名稱或密碼。')
return return
} }
if (err instanceof ValidationError) { if (err instanceof ValidationError) {
@@ -29,23 +31,27 @@ function handleRegister({ username, email, password, success, error }) {
return return
} }
console.error(err) console.error(err)
error('Something went wrong.') // error('Something went wrong.')
error('出了點問題。')
}) })
} }
async function check({ username, email, password, error }) { async function check({ username, email, password, error }) {
if (username && !isValidUsername(username)) { if (username && !isValidUsername(username)) {
error('Invalid username.') // error('Invalid username.')
error('無效的帳號名稱')
return return
} }
if (email && !validator.isEmail(email)) { if (email && !validator.isEmail(email)) {
error('Invalid email.') // error('Invalid email.')
error('無效的電子郵件')
return return
} }
if (password && !validator.isStrongPassword(password, passwordPolicy)) { if (password && !validator.isStrongPassword(password, passwordPolicy)) {
error( // error(
'Your password must be 8+ characters with at least one number, one upper and one lower case letter.' // 'Your password must be 8+ characters with at least one number, one upper and one lower case letter.'
) // )
error('密碼必須超過8個字元,至少包含一個數字、一個大寫及一個小寫。')
return return
} }
try { try {
@@ -56,11 +62,13 @@ async function check({ username, email, password, error }) {
return return
} }
if (err instanceof TooManyRequestsError) { if (err instanceof TooManyRequestsError) {
error('Too many requests. Try again in 2 minutes.') // error('Too many requests. Try again in 2 minutes.')
error('太多請求,兩分鐘後再試一次。')
return return
} }
console.error(err) console.error(err)
error('Something went wrong.') // error('Something went wrong.')
error('出了點問題。')
} }
} }
</script> </script>
+1
View File
@@ -28,6 +28,7 @@ services:
- players - players
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./logs:/app/logs
env_file: env_file:
- .env - .env
environment: environment:
+1
View File
@@ -41,6 +41,7 @@ services:
- players - players
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./logs:/app/logs
environment: environment:
- BASE_URL=http://localhost:5173 - BASE_URL=http://localhost:5173
- MONGO_URL=mongodb://user:user-password@db-test:27017/odyssey-test - MONGO_URL=mongodb://user:user-password@db-test:27017/odyssey-test
+1
View File
@@ -33,6 +33,7 @@ services:
- ./server/swagger.yaml:/app/server/swagger.yaml - ./server/swagger.yaml:/app/server/swagger.yaml
- ./packages:/app/packages - ./packages:/app/packages
- ./quests:/app/quests - ./quests:/app/quests
- ./logs:/app/logs
environment: environment:
- BASE_URL=http://localhost:5173 - BASE_URL=http://localhost:5173
- MONGO_URL=mongodb://db:27017/odyssey-test - MONGO_URL=mongodb://db:27017/odyssey-test
+1
View File
@@ -36,6 +36,7 @@
"socket.io": "^4.7.1", "socket.io": "^4.7.1",
"swagger-ui-express": "^5.0.0", "swagger-ui-express": "^5.0.0",
"validator": "^13.11.0", "validator": "^13.11.0",
"winston": "^3.11.0",
"yaml": "^2.3.1" "yaml": "^2.3.1"
}, },
"devDependencies": { "devDependencies": {
+2 -1
View File
@@ -2,6 +2,7 @@ import { matchedData } from 'express-validator'
import { genJWT } from '../../utils/auth.js' import { genJWT } from '../../utils/auth.js'
import { createUser } from '../../models/userManager.js' import { createUser } from '../../models/userManager.js'
import { asyncHandler } from '../../middleware/error.js' import { asyncHandler } from '../../middleware/error.js'
import logger from '../../utils/logger.js'
export const issueToken = asyncHandler(async (req, res) => { export const issueToken = asyncHandler(async (req, res) => {
const { user } = req const { user } = req
@@ -31,7 +32,7 @@ export const register = asyncHandler(async (req, res, next) => {
export function logout(req, res) { export function logout(req, res) {
req.logout((err) => { req.logout((err) => {
if (err) { if (err) {
console.error(err) logger.error(err)
res.status(500).json({ res.status(500).json({
message: 'error logging out', message: 'error logging out',
}) })
+3 -4
View File
@@ -4,6 +4,7 @@ import { Session } from '@linux-odyssey/models'
import { getAndStartContainer, attachContainer } from '../containers/docker.js' import { getAndStartContainer, attachContainer } from '../containers/docker.js'
import SessionMiddleware from '../middleware/session.js' import SessionMiddleware from '../middleware/session.js'
import { genJWT } from '../utils/auth.js' import { genJWT } from '../utils/auth.js'
import logger from '../utils/logger.js'
const sessions = new Map() const sessions = new Map()
@@ -37,7 +38,7 @@ async function connectContainer(socket, next) {
// console.log(socket.handshake) // console.log(socket.handshake)
const { sessionId } = socket.handshake.query const { sessionId } = socket.handshake.query
if (!(sessionId && validator.isMongoId(sessionId))) { if (!(sessionId && validator.isMongoId(sessionId))) {
console.warn('Invalid Session ID.', sessionId) logger.warn('Invalid Session ID.', user.username, sessionId)
next(new Error('Invalid Session ID.')) next(new Error('Invalid Session ID.'))
return return
} }
@@ -71,8 +72,6 @@ function onConnect(socket) {
socket.emit('terminal', chunk.toString()) socket.emit('terminal', chunk.toString())
}) })
socket.on('message', console.log)
socket.on('terminal', function incoming(message) { socket.on('terminal', function incoming(message) {
stream.socket.write(message) stream.socket.write(message)
}) })
@@ -98,6 +97,6 @@ export default (server) => {
io.on('connection', onConnect) io.on('connection', onConnect)
io.on('connect_error', (err) => { io.on('connect_error', (err) => {
console.error(err) logger.error('Socket connect error: ', err)
}) })
} }
+2 -1
View File
@@ -4,6 +4,7 @@ import passwordStrategy from './passwordStrategy.js'
import jwtStrategy from './jwtStrategy.js' import jwtStrategy from './jwtStrategy.js'
import googleStrategy from './googleStrategy.js' import googleStrategy from './googleStrategy.js'
import githubStrategy from './githubStrategy.js' import githubStrategy from './githubStrategy.js'
import logger from '../utils/logger.js'
passport.use(passwordStrategy) passport.use(passwordStrategy)
passport.use(jwtStrategy) passport.use(jwtStrategy)
@@ -14,7 +15,7 @@ const enabledMethods = {
github: githubStrategy !== null, github: githubStrategy !== null,
} }
console.log('Enabled login methods', enabledMethods) logger.info('Enabled login methods', enabledMethods)
if (enabledMethods.google) { if (enabledMethods.google) {
passport.use(googleStrategy) passport.use(googleStrategy)
+6 -5
View File
@@ -1,12 +1,13 @@
import { getQuestDockerfiles } from '../utils/quest.js' import { getQuestDockerfiles } from '../utils/quest.js'
import { buildQuestImage } from '../containers/docker.js' import { buildQuestImage } from '../containers/docker.js'
import logger from '../utils/logger.js'
async function buildImages() { async function buildImages() {
const quests = await getQuestDockerfiles() const quests = await getQuestDockerfiles()
console.log(`Building ${quests.length} images`) logger.info(`Building ${quests.length} images`)
return Promise.all( return Promise.all(
quests.map(({ id, questPath }) => { quests.map(({ id, questPath }) => {
console.log(`Building image for ${id}`) logger.info(`Building image for ${id}`)
return buildQuestImage(questPath, id) return buildQuestImage(questPath, id)
}) })
) )
@@ -14,11 +15,11 @@ async function buildImages() {
buildImages() buildImages()
.then((result) => { .then((result) => {
console.log(result) logger.info(result)
console.log('Done!') logger.info('Done!')
process.exit(0) process.exit(0)
}) })
.catch((err) => { .catch((err) => {
console.error(err) logger.error(err)
process.exit(1) process.exit(1)
}) })
+2 -1
View File
@@ -1,6 +1,7 @@
import connectDB from '@linux-odyssey/models' import connectDB from '@linux-odyssey/models'
import { removeExpired } from '../containers/expiryChecker.js' import { removeExpired } from '../containers/expiryChecker.js'
import config from '../config.js' import config from '../config.js'
import logger from '../utils/logger.js'
connectDB(config.db) connectDB(config.db)
.then(removeExpired) .then(removeExpired)
@@ -8,6 +9,6 @@ connectDB(config.db)
process.exit(0) process.exit(0)
}) })
.catch((err) => { .catch((err) => {
console.error(err) logger.error(err)
process.exit(1) process.exit(1)
}) })
+1
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
import { genSecret } from '../utils/env.js' import { genSecret } from '../utils/env.js'
function setup() { function setup() {
+4
View File
@@ -46,6 +46,10 @@ const config = {
mountQuest: get('MOUNT_QUEST', ''), mountQuest: get('MOUNT_QUEST', ''),
hostPwd: get('HOST_PWD', ''), hostPwd: get('HOST_PWD', ''),
}, },
log: {
path: get('LOG_PATH', '../logs'),
},
} }
config.baseUrl = get('BASE_URL', `http://${config.host}:${config.port}`) config.baseUrl = get('BASE_URL', `http://${config.host}:${config.port}`)
+26 -11
View File
@@ -1,5 +1,6 @@
import Docker from 'dockerode' import Docker from 'dockerode'
import config, { getQuestImage } from '../config.js' import config, { getQuestImage } from '../config.js'
import logger from '../utils/logger.js'
const engine = new Docker() const engine = new Docker()
@@ -25,7 +26,7 @@ export function createContainer(name, questId) {
} }
const { hostPwd, mountQuest } = config.docker const { hostPwd, mountQuest } = config.docker
if (!config.isProduction && hostPwd && mountQuest) { if (!config.isProduction && hostPwd && mountQuest) {
console.log('Mounting quest folder', mountQuest) logger.debug('Mounting quest folder', mountQuest)
option.HostConfig.Binds = [ option.HostConfig.Binds = [
`${hostPwd}/quests/${mountQuest}/home:/home/commander`, `${hostPwd}/quests/${mountQuest}/home:/home/commander`,
`${hostPwd}/packages/container:/usr/local/lib/container`, `${hostPwd}/packages/container:/usr/local/lib/container`,
@@ -35,7 +36,7 @@ export function createContainer(name, questId) {
} }
export async function getAndStartContainer(id) { export async function getAndStartContainer(id) {
console.log(`Getting container: ${id}`) logger.debug(`Getting container: ${id}`)
const container = engine.getContainer(id) const container = engine.getContainer(id)
if (!container) { if (!container) {
throw new Error(`Container ${id} not found`) throw new Error(`Container ${id} not found`)
@@ -76,11 +77,21 @@ export async function deleteContainer(id) {
try { try {
await container.stop() await container.stop()
} catch (error) { } catch (error) {
console.warn('Failed to stop container', id) logger.warn('Failed to stop container', id)
} }
await container.remove() await container.remove()
} }
function parseJSONOutput(data) {
const { stream, error } = JSON.parse(data.toString())
if (stream) {
logger.info(stream)
}
if (error) {
throw error
}
}
export function buildQuestImage(questPath, questId) { export function buildQuestImage(questPath, questId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
engine.buildImage( engine.buildImage(
@@ -99,23 +110,27 @@ export function buildQuestImage(questPath, questId) {
} }
response.on('data', (data) => { response.on('data', (data) => {
// Process the data (this could be Docker build output) // Process the data (this could be Docker build output)
const { stream, error } = JSON.parse(data.toString()) try {
if (stream) { data
console.log(stream) .toString()
} .split('\n')
if (error) { .map((line) => line.trim())
console.error(error) .filter((line) => line !== '')
.forEach((line) => {
parseJSONOutput(line)
})
} catch (error) {
reject(error) reject(error)
} }
}) })
response.on('end', () => { response.on('end', () => {
console.log(`Build completed for ${questId}`) logger.info(`Build completed for ${questId}`)
resolve(questId) resolve(questId)
}) })
response.on('error', (error) => { response.on('error', (error) => {
console.error(`Build failed for ${questId}:`, error) logger.error(`Build failed for ${questId}:`, error)
reject(error) reject(error)
}) })
} }
+2 -2
View File
@@ -2,6 +2,7 @@ import schedule from 'node-schedule'
import { Session } from '@linux-odyssey/models' import { Session } from '@linux-odyssey/models'
import config from '../config.js' import config from '../config.js'
import { deleteContainer } from './docker.js' import { deleteContainer } from './docker.js'
import logger from '../utils/logger.js'
async function disableSession(session) { async function disableSession(session) {
try { try {
@@ -29,7 +30,6 @@ export async function removeExpired() {
// Find all sessions that are containerId is not null and lastActivityAt is // Find all sessions that are containerId is not null and lastActivityAt is
// less than the current time minus the expiry time // less than the current time minus the expiry time
try { try {
console.log('Checking for expired sessions')
const sessions = await Session.find({ const sessions = await Session.find({
containerId: { $ne: null }, containerId: { $ne: null },
lastActivityAt: { $lt: new Date(Date.now() - config.containerExpiry) }, lastActivityAt: { $lt: new Date(Date.now() - config.containerExpiry) },
@@ -42,7 +42,7 @@ export async function removeExpired() {
) )
) )
} catch (err) { } catch (err) {
console.error('Remove expiring failed:', err) logger.error('Remove expiring failed:', err)
} }
} }
+2 -1
View File
@@ -4,6 +4,7 @@ import { Quest } from '@linux-odyssey/models'
import { pushToSession } from '../api/socket.js' import { pushToSession } from '../api/socket.js'
import SessionHandler from './sessionHandler.js' import SessionHandler from './sessionHandler.js'
import { checkFile } from '../containers/cli.js' import { checkFile } from '../containers/cli.js'
import logger from '../utils/logger.js'
// 檢查 pattern 是否符合 input // 檢查 pattern 是否符合 input
const checkMatch = (pattern, input) => { const checkMatch = (pattern, input) => {
@@ -114,7 +115,7 @@ export default class CommandHandler extends SessionHandler {
this.quest = await Quest.findById(this.session.quest) this.quest = await Quest.findById(this.session.quest)
const stages = this.getStages() const stages = this.getStages()
if (stages.length === 0) { if (stages.length === 0) {
console.error('stage not found', this.session.tasks) logger.warn('stage not found', this.session.tasks)
return {} return {}
} }
+8 -8
View File
@@ -16,21 +16,21 @@ import { globalRateLimit } from './middleware/rateLimiter.js'
import sessionMiddleware from './middleware/session.js' import sessionMiddleware from './middleware/session.js'
import expiryRemovalScheduler from './containers/expiryChecker.js' import expiryRemovalScheduler from './containers/expiryChecker.js'
import setupTest from './utils/setupTest.js' import setupTest from './utils/setupTest.js'
import logger from './utils/logger.js'
async function main() { async function main() {
if (!config.secret) { if (!config.secret) {
console.warn( logger.error(
'No SECRET_KEY found in .env! To set up a persistent key, please run the setup script:' 'No SECRET_KEY found in .env! To set up a persistent key, please run `yarn setup`:'
) )
console.warn('yarn setup')
process.exit(1) process.exit(1)
} }
try { try {
await connectDB(config.db) await connectDB(config.db)
console.log('Connected to MongoDB') logger.info('Connected to MongoDB')
} catch (err) { } catch (err) {
console.error(err) logger.error(err)
process.exit(1) process.exit(1)
} }
@@ -53,7 +53,7 @@ async function main() {
'uniquelocal', 'uniquelocal',
...config.trustedProxies, ...config.trustedProxies,
]) ])
console.log('Trusted proxies:', config.trustedProxies) logger.info('Trusted proxies:', config.trustedProxies)
app.use(globalRateLimit) app.use(globalRateLimit)
app.use(sessionMiddleware) app.use(sessionMiddleware)
app.use(passport.session()) app.use(passport.session())
@@ -74,8 +74,8 @@ async function main() {
app.use(errorHandler) app.use(errorHandler)
server.listen(config.port, config.host, () => { server.listen(config.port, config.host, () => {
console.log(`Server listening at ${config.baseUrl}`) logger.info(`Server listening at ${config.baseUrl}`)
}) })
} }
main().catch((err) => console.log(err)) main().catch((err) => logger.error(err))
+3 -1
View File
@@ -1,3 +1,5 @@
import logger from '../utils/logger.js'
export function asyncHandler(fn) { export function asyncHandler(fn) {
return (req, res, next) => { return (req, res, next) => {
fn(req, res, next).catch(next) fn(req, res, next).catch(next)
@@ -6,6 +8,6 @@ export function asyncHandler(fn) {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
export default function errorHandler(err, req, res, next) { export default function errorHandler(err, req, res, next) {
console.error(err.stack) // Log error stack trace to the console logger.error(err) // Log error stack trace to the console
res.status(500).send({ message: 'Something broke!' }) // Send error response res.status(500).send({ message: 'Something broke!' }) // Send error response
} }
+3 -2
View File
@@ -1,6 +1,7 @@
import { Session, Quest, UserProfile } from '@linux-odyssey/models' import { Session, Quest, UserProfile } from '@linux-odyssey/models'
import { createContainer, deleteContainer } from '../containers/docker.js' import { createContainer, deleteContainer } from '../containers/docker.js'
import SessionHandler from '../game/sessionHandler.js' import SessionHandler from '../game/sessionHandler.js'
import logger from '../utils/logger.js'
async function deactivateSessions(user, quest) { async function deactivateSessions(user, quest) {
const sessions = await Session.find({ const sessions = await Session.find({
@@ -11,7 +12,7 @@ async function deactivateSessions(user, quest) {
return Promise.all( return Promise.all(
sessions.map((session) => { sessions.map((session) => {
return deleteContainer(session.containerId) return deleteContainer(session.containerId)
.catch(console.error) .catch((err) => logger.error('Failed to delete container', err))
.finally(() => { .finally(() => {
session.status = 'inactive' session.status = 'inactive'
session.containerId = null session.containerId = null
@@ -29,7 +30,7 @@ export async function createNewSession(user, questId) {
// deactivate all active sessions // deactivate all active sessions
deactivateSessions(user._id, quest._id).catch((err) => { deactivateSessions(user._id, quest._id).catch((err) => {
console.error(err) logger.error(err)
}) })
const container = await createContainer( const container = await createContainer(
+37
View File
@@ -0,0 +1,37 @@
import winston from 'winston'
import config from '../config.js'
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'backend', timestamp: new Date().toISOString() },
transports: [
//
// - Write all logs with importance level of `error` or less to `error.log`
// - Write all logs with importance level of `info` or less to `combined.log`
//
new winston.transports.File({
dirname: config.log.path,
filename: 'error.log',
level: 'error',
}),
new winston.transports.File({
dirname: config.log.path,
filename: 'combined.log',
}),
],
})
//
// If we're not in production then log to the `console` with the format:
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
//
if (process.env.NODE_ENV !== 'production') {
logger.add(
new winston.transports.Console({
format: winston.format.simple(),
})
)
}
export default logger
+2 -1
View File
@@ -3,6 +3,7 @@ import path from 'path'
import yaml from 'yaml' import yaml from 'yaml'
import { Quest } from '@linux-odyssey/models' import { Quest } from '@linux-odyssey/models'
import logger from './logger.js'
const questDirectory = path.join(process.cwd(), '..', 'quests') const questDirectory = path.join(process.cwd(), '..', 'quests')
@@ -45,7 +46,7 @@ export async function loadAndUpdateQuests() {
}) })
await quest.save() await quest.save()
} catch (error) { } catch (error) {
console.error(`Error parsing quest ${id}:`, error.message) logger.error(`Error parsing quest ${id}:`, error)
throw error throw error
} }
}) })
+4 -3
View File
@@ -1,9 +1,10 @@
import { User } from '@linux-odyssey/models' import { User } from '@linux-odyssey/models'
import config from '../config.js' import config from '../config.js'
import { createUser } from '../models/userManager.js' import { createUser } from '../models/userManager.js'
import logger from './logger.js'
export default async function setupTest() { export default async function setupTest() {
console.log('Setting up test account') logger.info('Setting up test account')
if (!(config.testing.username && config.testing.password)) { if (!(config.testing.username && config.testing.password)) {
throw new Error( throw new Error(
'No testing account found! Please set TESTING_USERNAME and TESTING_PASSWORD in .env' 'No testing account found! Please set TESTING_USERNAME and TESTING_PASSWORD in .env'
@@ -13,10 +14,10 @@ export default async function setupTest() {
const email = `${username}@example.com` const email = `${username}@example.com`
if (await User.exists({ username })) { if (await User.exists({ username })) {
console.log('Testing account already exists') logger.info('Testing account already exists')
return return
} }
await createUser(username, email, { password }) await createUser(username, email, { password })
console.log('Created testing account') logger.info('Created testing account')
} }
+1
View File
@@ -24,6 +24,7 @@ module.exports = defineConfig({
env: { env: {
defaultAccount: getOrFail('TESTING_USERNAME'), defaultAccount: getOrFail('TESTING_USERNAME'),
defaultPassword: getOrFail('TESTING_PASSWORD'), defaultPassword: getOrFail('TESTING_PASSWORD'),
isCHVersion: true,
// google_username: process.env.GOOGLE_USERNAME, // google_username: process.env.GOOGLE_USERNAME,
// google_password: process.env.GOOGLE_PASSWORD, // google_password: process.env.GOOGLE_PASSWORD,
// google_client_id: process.env.GOOGLE_CLIENT_ID, // google_client_id: process.env.GOOGLE_CLIENT_ID,
+59 -51
View File
@@ -3,9 +3,7 @@ export default function checkLoginUI() {
cy.get('h1.text-text-primary') cy.get('h1.text-text-primary')
.should('contain', 'Linux Odyssey') .should('contain', 'Linux Odyssey')
.and('be.visible') .and('be.visible')
cy.get('#password') cy.CheckPlaceholder('#password', '密碼', 'Password')
.invoke('attr', 'placeholder')
.should('contain', 'Password')
} }
describe('example helloworld app', () => { describe('example helloworld app', () => {
describe('Login tests', () => { describe('Login tests', () => {
@@ -14,51 +12,65 @@ describe('example helloworld app', () => {
}) })
it('Check Login Page Elements-Register', () => { it('Check Login Page Elements-Register', () => {
checkLoginUI() checkLoginUI()
cy.get('#username') cy.CheckPlaceholder('#username', '帳號名稱', 'Username')
.invoke('attr', 'placeholder')
.should('contain', 'Username')
cy.get('label.text-text-secondary').should('be.visible') // check the rules is displayed cy.get('label.text-text-secondary').should('be.visible') // check the rules is displayed
cy.get('#email').invoke('attr', 'placeholder').should('contain', 'Email') cy.CheckPlaceholder('#email', '電子郵件', 'Email')
cy.get('span').contains('Already have an account?').should('be.visible') cy.CheckTextElement(
cy.get('a.text-text-primary') '#CheckRegistered',
.should('contain', 'Log in') '已經有帳號了嗎?',
.and('be.visible') 'Already have an account?'
)
cy.CheckTextElement('#SwitchtoLogin', '登入', 'Log in')
}) })
it('Check Login Page Elements-Login', () => { it('Check Login Page Elements-Login', () => {
cy.get('a.text-text-primary') cy.get('#SwitchtoLogin').click()
.should('contain', 'Log in')
.and('be.visible')
.click()
checkLoginUI() checkLoginUI()
cy.get('#username') cy.CheckPlaceholder(
.invoke('attr', 'placeholder') '#username',
.should('contain', 'Email / Username') '電子郵件 / 帳號名稱',
cy.findByRole('button', { name: 'Log In' }).should('be.visible') 'Email / Username'
cy.get('span').contains("Don't have an account?").should('be.visible') )
cy.get('a.text-text-primary') cy.get('#LogInOrSignUp').should('be.visible')
.should('contain', 'Sign up') cy.CheckTextElement(
.and('be.visible') '#CheckRegistered',
'還沒有帳號嗎?',
"Don't have an account?"
)
cy.CheckTextElement('#SwitchtoRegister', '註冊', 'Sign up')
}) })
it('Check social account UI', () => { it('Check social account UI', () => {
cy.get('p').contains('or').should('be.visible') cy.CheckTextElement(
cy.findByRole('link', { name: 'Continue with Google' }).should( '#GoogleLogin',
'be.visible' '以 Google 繼續',
'Continue with Google'
) )
cy.findByRole('link', { name: 'Continue with GitHub' }).should( cy.CheckTextElement('#or', '或', 'or')
'be.visible' cy.CheckTextElement(
'#GitHubLogin',
'以 GitHub 繼續',
'Continue with GitHub'
) )
}) })
it('Check login fail', () => { it('Check login fail', () => {
cy.LoginWithPassword('dddd', '123456') cy.LoginWithPassword('dddd', '123456')
cy.get('p').contains('Wrong username or password.').should('be.visible') cy.CheckTextElement(
'#ErrorDisplay',
'錯誤的帳號名稱或密碼。',
'Wrong username or password.'
)
cy.LoginWithPassword(`ddd\``, '123456') cy.LoginWithPassword(`ddd\``, '123456')
cy.get('p').contains('Invalid username or password.').should('be.visible') cy.CheckTextElement(
'#ErrorDisplay',
'無效的帳號名稱或密碼。',
'Invalid username or password.'
)
}) })
it('Login-Password Already Register', () => { it('Login-Password Already Register', () => {
cy.LoginWithPassword( cy.LoginWithPassword(
Cypress.env('defaultAccount'), Cypress.env('defaultAccount'),
Cypress.env('defaultPassword') Cypress.env('defaultPassword')
) )
cy.url().should('include', '/map')
}) })
}) })
describe('Game Start Page UI', () => { describe('Game Start Page UI', () => {
@@ -66,33 +78,35 @@ describe('example helloworld app', () => {
cy.PrepareForGame() cy.PrepareForGame()
}) })
it('Check Header', () => { it('Check Header', () => {
cy.findByRole('button', { name: 'Hello, Linux World!' }).should( cy.get('#HeaderText')
'be.visible' .should('contain', 'Hello, Linux World!')
) .and('be.visible')
cy.findByRole('link', { name: 'Bug Report' }).should('be.visible') cy.findByRole('link', { name: 'Bug Report' }).should('be.visible')
cy.findByRole('button', { name: 'Sign Out' }).should('be.visible') cy.findByRole('button', { name: 'Sign Out' }).should('be.visible')
}) })
it('Check QuestInfo', () => { it('Check QuestInfo', () => {
cy.get('#topic').should('contain', 'Hello, Linux World!') cy.get('#topic').should('contain', 'Hello, Linux World!')
cy.get('#quest').find('p.text-text').should('have.length', 3) cy.get('#quest').find('p.text-text').should('have.length', 3)
cy.getQuestInfo('Tasks:').should('be.visible') cy.CheckTextElement('#tasks', '任務', 'Tasks:')
cy.getQuestInfo('輸入 `echo help` 來朗誦咒語').should('be.visible') cy.getQuestInfo('輸入 `echo help` 來朗誦咒語').should('be.visible')
}) })
it('Check Command Cheat Sheet', () => { it('Check Command Cheat Sheet', () => {
cy.get('#cmdlist').within(($cmdlist) => { cy.get('#cmdlist').within(($cmdlist) => {
cy.get($cmdlist).should('be.visible') cy.get($cmdlist).should('be.visible')
cy.get('svg[data-icon="list"]').should('be.visible') cy.get('svg[data-icon="list"]').should('be.visible')
cy.get('h1').should('contain', 'Command Cheatsheets').and('be.visible') cy.CheckTextElement('#CommandBook', '指令之書', 'Command Cheatsheets')
cy.get('#cheatsheets') cy.CheckTextElement(
.should('contain', '--Command List--') '#cheatsheets',
.and('be.visible') '-- --指令列表-- --',
'--Command List--'
)
}) })
}) })
it('Check Terminal', () => { it('Check Terminal', () => {
cy.get('#terminal', { timeout: 20000 }).within(($terminal) => { cy.get('#terminal', { timeout: 20000 }).within(($terminal) => {
cy.get($terminal).should('be.visible') cy.get($terminal).should('be.visible')
cy.get('svg[data-icon="terminal"]').should('be.visible') cy.get('svg[data-icon="terminal"]').should('be.visible')
cy.findByRole('button').should('contain', 'Terminal').and('be.visible') cy.CheckTextElement('#Terminal', '終端機', 'Terminal')
cy.get('@Terminaltextbox') cy.get('@Terminaltextbox')
.should('be.visible') .should('be.visible')
.and('contain', 'commander:~ $') .and('contain', 'commander:~ $')
@@ -102,19 +116,17 @@ describe('example helloworld app', () => {
cy.get('#hint', { timeout: 20000 }).within(($hint) => { cy.get('#hint', { timeout: 20000 }).within(($hint) => {
cy.get($hint).should('be.visible') cy.get($hint).should('be.visible')
cy.get('svg[data-icon="lightbulb"]').should('be.visible') cy.get('svg[data-icon="lightbulb"]').should('be.visible')
cy.get('h1').should('contain', 'Hint').and('be.visible')
}) })
cy.CheckTextElement('#hint', '提示', 'Hint')
}) })
it('Check File TreeChart', () => { it('Check File TreeChart', () => {
cy.get('#tree').should('be.visible') cy.get('#tree').should('be.visible')
cy.get('#tree').get('a').should('contain', '???').and('be.visible') cy.get('#tree').get('a').should('contain', '???').and('be.visible')
}) })
it('Check Fucnctional Buttons', () => { it('Check Fucnctional Buttons', () => {
cy.findByRole('button', { name: 'Solution' }, { timeout: 30000 }).should( cy.CheckTextElement('#solution', '解答', 'Solution')
'be.visible' cy.CheckTextElement('#reset', '重來', 'Reset')
) cy.CheckTextElement('#continue', '繼續', 'Continue')
cy.findByRole('button', { name: 'Reset' }).should('be.visible')
cy.findByRole('button', { name: 'Continue' }).should('be.visible')
}) })
}) })
describe('Game Play', () => { describe('Game Play', () => {
@@ -128,7 +140,7 @@ describe('example helloworld app', () => {
.should('contain', 'zsh: command not found: 12345') .should('contain', 'zsh: command not found: 12345')
.and('contain', '12345') .and('contain', '12345')
}) })
it.only('Complete the Game(relating UI)', () => { it('Complete the Game(relating UI)', () => {
// get answersheet // get answersheet
cy.get('@answers').then((answers) => { cy.get('@answers').then((answers) => {
const answerarr = answers.split('\n') const answerarr = answers.split('\n')
@@ -172,12 +184,8 @@ describe('example helloworld app', () => {
cy.checkPending() cy.checkPending()
cy.waitUntilActive() cy.waitUntilActive()
cy.getQuestInfo('✓ 解除封印').should('be.visible') cy.getQuestInfo('✓ 解除封印').should('be.visible')
cy.findByRole(
'heading',
{ name: 'Quest Completed!' },
{ timeout: 20000 }
).should('be.visible')
// Check survey dialog pop up // Check survey dialog pop up
cy.CheckTextElement('#QuestCompleted', '關卡完成!', 'Quest Completed!')
cy.get('div[class="modal"]').find('p').should('be.visible') cy.get('div[class="modal"]').find('p').should('be.visible')
cy.findByRole('link', { name: '填寫問卷' }).should('be.visible') cy.findByRole('link', { name: '填寫問卷' }).should('be.visible')
}) })
+23 -3
View File
@@ -5,8 +5,7 @@ Cypress.Commands.add('LoginWithPassword', (username, password) => {
cy.visit('/login') cy.visit('/login')
cy.get('#username').type(username) cy.get('#username').type(username)
cy.get('#password').type(password) cy.get('#password').type(password)
cy.findByRole('button', { name: 'Log In' }).click() cy.get('#LogInOrSignUp').click()
cy.url().should('include', '/game')
}) })
Cypress.Commands.add('PrepareForGame', () => { Cypress.Commands.add('PrepareForGame', () => {
cy.visit('/') cy.visit('/')
@@ -14,11 +13,12 @@ Cypress.Commands.add('PrepareForGame', () => {
Cypress.env('defaultAccount'), Cypress.env('defaultAccount'),
Cypress.env('defaultPassword') Cypress.env('defaultPassword')
) )
cy.url().should('include', '/map')
cy.visit('/game/helloworld') cy.visit('/game/helloworld')
cy.get('.xterm-screen', { timeout: 10000 }) cy.get('.xterm-screen', { timeout: 10000 })
.as('Terminaltextbox') .as('Terminaltextbox')
.should('be.visible') .should('be.visible')
cy.findByRole('button', { name: 'Reset' }).click() cy.CheckTextElement('#reset', '重來', 'Reset').click()
cy.typeInCommand('clear{enter}') cy.typeInCommand('clear{enter}')
cy.get('@Terminaltextbox', { timeout: 150000 }).should( cy.get('@Terminaltextbox', { timeout: 150000 }).should(
'contain', 'contain',
@@ -66,3 +66,23 @@ Cypress.Commands.add('checkPending', () => {
Cypress.Commands.add('Complete the Stage (only command)', () => { Cypress.Commands.add('Complete the Stage (only command)', () => {
cy.log('') cy.log('')
}) })
Cypress.Commands.add('CheckTextElement', (id, chText, enText) => {
cy.get(id).within(($element) => {
if (Cypress.env('isCHVersion')) {
cy.get($element).should('contain', chText).and('be.visible')
} else {
cy.get($element).should('contain', enText).and('be.visible')
}
})
})
Cypress.Commands.add('CheckPlaceholder', (id, chText, enText) => {
cy.get(id)
.invoke('attr', 'placeholder')
.then(($placeHolder) => {
if (Cypress.env('isCHVersion')) {
expect($placeHolder).eq(chText)
} else {
expect($placeHolder).eq(enText)
}
})
})
+1 -1
View File
@@ -9,8 +9,8 @@
"test:open": "cypress open" "test:open": "cypress open"
}, },
"dependencies": { "dependencies": {
"cypress": "^13.5.0",
"@testing-library/cypress": "^10.0.1", "@testing-library/cypress": "^10.0.1",
"cypress": "^13.5.1",
"dotenv": "^16.3.1" "dotenv": "^16.3.1"
} }
} }
+200 -31
View File
@@ -27,7 +27,15 @@
dependencies: dependencies:
"@babel/highlight" "^7.22.5" "@babel/highlight" "^7.22.5"
"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.13": "@babel/code-frame@^7.10.4":
version "7.23.4"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.4.tgz#03ae5af150be94392cb5c7ccd97db5a19a5da6aa"
integrity sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==
dependencies:
"@babel/highlight" "^7.23.4"
chalk "^2.4.2"
"@babel/code-frame@^7.22.13":
version "7.22.13" version "7.22.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
@@ -201,6 +209,15 @@
chalk "^2.0.0" chalk "^2.0.0"
js-tokens "^4.0.0" js-tokens "^4.0.0"
"@babel/highlight@^7.23.4":
version "7.23.4"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b"
integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==
dependencies:
"@babel/helper-validator-identifier" "^7.22.20"
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.15", "@babel/parser@^7.20.7", "@babel/parser@^7.21.3", "@babel/parser@^7.22.5", "@babel/parser@^7.22.7": "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.15", "@babel/parser@^7.20.7", "@babel/parser@^7.21.3", "@babel/parser@^7.22.5", "@babel/parser@^7.22.7":
version "7.22.7" version "7.22.7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae"
@@ -315,9 +332,9 @@
"@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5"
"@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6": "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6":
version "7.23.2" version "7.23.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.4.tgz#36fa1d2b36db873d25ec631dcc4923fdc1cf2e2e"
integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== integrity sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==
dependencies: dependencies:
regenerator-runtime "^0.14.0" regenerator-runtime "^0.14.0"
@@ -397,6 +414,11 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
"@colors/colors@1.6.0", "@colors/colors@^1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0"
integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==
"@cypress/request@^3.0.0": "@cypress/request@^3.0.0":
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.1.tgz#72d7d5425236a2413bd3d8bb66d02d9dc3168960" resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.1.tgz#72d7d5425236a2413bd3d8bb66d02d9dc3168960"
@@ -429,6 +451,15 @@
debug "^3.1.0" debug "^3.1.0"
lodash.once "^4.1.1" lodash.once "^4.1.1"
"@dabh/diagnostics@^2.0.2":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a"
integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==
dependencies:
colorspace "1.1.x"
enabled "2.0.x"
kuler "^2.0.0"
"@esbuild/android-arm64@0.18.12": "@esbuild/android-arm64@0.18.12":
version "0.18.12" version "0.18.12"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.12.tgz#d2b7408f2d2fe6ad93877d90ebb18e0d2648828c" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.12.tgz#d2b7408f2d2fe6ad93877d90ebb18e0d2648828c"
@@ -1078,9 +1109,11 @@
integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==
"@types/node@^18.17.5": "@types/node@^18.17.5":
version "18.17.19" version "18.18.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.19.tgz#80c9b8a89d3648d9e6098f4a7184e03833fee3c5" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.11.tgz#d43f6b92671eacfdd1fedb7a897388fb8880323f"
integrity sha512-+pMhShR3Or5GR0/sp4Da7FnhVmTalWm81M6MkEldbwjETSaPalw138Z4KdpQaistvqQxLB7Cy4xwYdxpbSOs9Q== integrity sha512-c1vku6qnTeujJneYH94/4aq73XrVcsJe35UPyAsSok1ijiKrkRzK+AxQPSpNMUnC03roWBBwJx/9I8V7lQoxmA==
dependencies:
undici-types "~5.26.4"
"@types/prettier@^2.1.5": "@types/prettier@^2.1.5":
version "2.7.3" version "2.7.3"
@@ -1093,15 +1126,20 @@
integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==
"@types/sizzle@^2.3.2": "@types/sizzle@^2.3.2":
version "2.3.3" version "2.3.7"
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.7.tgz#ab5744eceb30daea3e0c6a9c2d9e1a3817ef7656"
integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== integrity sha512-sU5MNKvSFdbT97qLSabU6jxceY9BfYQe9uF+b8E1rdTecpq9ROkkKoiHlblUlhgfaz9bdCByWjAI40Y3mHf1iQ==
"@types/stack-utils@^2.0.0": "@types/stack-utils@^2.0.0":
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
"@types/triple-beam@^1.3.2":
version "1.3.5"
resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c"
integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==
"@types/webidl-conversions@*": "@types/webidl-conversions@*":
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz#2b8e60e33906459219aa587e9d1a612ae994cfe7" resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz#2b8e60e33906459219aa587e9d1a612ae994cfe7"
@@ -1128,9 +1166,9 @@
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@types/yauzl@^2.9.1": "@types/yauzl@^2.9.1":
version "2.10.0" version "2.10.3"
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999"
integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
@@ -1463,9 +1501,14 @@ astral-regex@^2.0.0:
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
async@^3.2.0: async@^3.2.0:
version "3.2.4" version "3.2.5"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
async@^3.2.3:
version "3.2.5"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
asynckit@^0.4.0: asynckit@^0.4.0:
version "0.4.0" version "0.4.0"
@@ -1930,7 +1973,7 @@ collect-v8-coverage@^1.0.0:
resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9"
integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==
color-convert@^1.9.0: color-convert@^1.9.0, color-convert@^1.9.3:
version "1.9.3" version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
@@ -1949,21 +1992,45 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@~1.1.4: color-name@^1.0.0, color-name@~1.1.4:
version "1.1.4" version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^1.6.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
dependencies:
color-name "^1.0.0"
simple-swizzle "^0.2.2"
color-support@^1.1.2: color-support@^1.1.2:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
color@^3.1.3:
version "3.2.1"
resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164"
integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==
dependencies:
color-convert "^1.9.3"
color-string "^1.6.0"
colorette@^2.0.16: colorette@^2.0.16:
version "2.0.20" version "2.0.20"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
colorspace@1.1.x:
version "1.1.4"
resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243"
integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==
dependencies:
color "^3.1.3"
text-hex "1.0.x"
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
@@ -2098,10 +2165,10 @@ csstype@^3.1.1:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
cypress@^13.5.0: cypress@^13.5.1:
version "13.5.0" version "13.5.1"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.5.0.tgz#8c149074186130972f08b2cdce6ded41f014bacd" resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.5.1.tgz#8b19bf0b9f31ea43f78980b2479bd3f25197d5cc"
integrity sha512-oh6U7h9w8wwHfzNDJQ6wVcAeXu31DlIYlNOBvfd6U4CcB8oe4akawQmH+QJVOMZlM42eBoCne015+svVqdwdRQ== integrity sha512-yqLViT0D/lPI8Kkm7ciF/x/DCK/H/DnogdGyiTnQgX4OVR2aM30PtK+kvklTOD1u3TuItiD9wUQAF8EYWtyZug==
dependencies: dependencies:
"@cypress/request" "^3.0.0" "@cypress/request" "^3.0.0"
"@cypress/xvfb" "^1.2.4" "@cypress/xvfb" "^1.2.4"
@@ -2400,6 +2467,11 @@ emoji-regex@^9.2.2:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
enabled@2.0.x:
version "2.0.0"
resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==
encodeurl@~1.0.2: encodeurl@~1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
@@ -3023,6 +3095,11 @@ fd-slicer@~1.1.0:
dependencies: dependencies:
pend "~1.2.0" pend "~1.2.0"
fecha@^4.2.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd"
integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==
figures@^3.2.0: figures@^3.2.0:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -3086,6 +3163,11 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
fn.name@1.x.x:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
follow-redirects@^1.15.0: follow-redirects@^1.15.0:
version "1.15.2" version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
@@ -3618,6 +3700,11 @@ is-arrayish@^0.2.1:
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
is-arrayish@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
is-bigint@^1.0.1: is-bigint@^1.0.1:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
@@ -4408,6 +4495,11 @@ kruptein@^3.0.0:
dependencies: dependencies:
asn1.js "^5.4.1" asn1.js "^5.4.1"
kuler@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
lazy-ass@^1.6.0: lazy-ass@^1.6.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513"
@@ -4527,6 +4619,18 @@ log-update@^4.0.0:
slice-ansi "^4.0.0" slice-ansi "^4.0.0"
wrap-ansi "^6.2.0" wrap-ansi "^6.2.0"
logform@^2.3.2, logform@^2.4.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/logform/-/logform-2.6.0.tgz#8c82a983f05d6eaeb2d75e3decae7a768b2bf9b5"
integrity sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==
dependencies:
"@colors/colors" "1.6.0"
"@types/triple-beam" "^1.3.2"
fecha "^4.2.0"
ms "^2.1.1"
safe-stable-stringify "^2.3.1"
triple-beam "^1.3.0"
long-timeout@0.1.1: long-timeout@0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514"
@@ -5005,6 +5109,13 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
dependencies: dependencies:
wrappy "1" wrappy "1"
one-time@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45"
integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==
dependencies:
fn.name "1.x.x"
onetime@^5.1.0, onetime@^5.1.2: onetime@^5.1.0, onetime@^5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
@@ -5622,6 +5733,11 @@ safe-regex-test@^1.0.0:
get-intrinsic "^1.1.3" get-intrinsic "^1.1.3"
is-regex "^1.1.4" is-regex "^1.1.4"
safe-stable-stringify@^2.3.1:
version "2.4.3"
resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886"
integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@@ -5740,6 +5856,13 @@ signal-exit@^4.0.1:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
simple-swizzle@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==
dependencies:
is-arrayish "^0.3.1"
simple-update-notifier@^2.0.0: simple-update-notifier@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb"
@@ -5878,9 +6001,9 @@ ssh2@^1.11.0:
nan "^2.17.0" nan "^2.17.0"
sshpk@^1.14.1: sshpk@^1.14.1:
version "1.17.0" version "1.18.0"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028"
integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==
dependencies: dependencies:
asn1 "~0.2.3" asn1 "~0.2.3"
assert-plus "^1.0.0" assert-plus "^1.0.0"
@@ -5892,6 +6015,11 @@ sshpk@^1.14.1:
safer-buffer "^2.0.2" safer-buffer "^2.0.2"
tweetnacl "~0.14.0" tweetnacl "~0.14.0"
stack-trace@0.0.x:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==
stack-utils@^2.0.3: stack-utils@^2.0.3:
version "2.0.6" version "2.0.6"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"
@@ -6139,6 +6267,11 @@ test-exclude@^6.0.0:
glob "^7.1.4" glob "^7.1.4"
minimatch "^3.0.4" minimatch "^3.0.4"
text-hex@1.0.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==
text-table@^0.2.0: text-table@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@@ -6159,9 +6292,9 @@ thenify-all@^1.0.0:
any-promise "^1.0.0" any-promise "^1.0.0"
throttleit@^1.0.0: throttleit@^1.0.0:
version "1.0.0" version "1.0.1"
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.1.tgz#304ec51631c3b770c65c6c6f76938b384000f4d5"
integrity sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g== integrity sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==
through@^2.3.8: through@^2.3.8:
version "2.3.8" version "2.3.8"
@@ -6231,6 +6364,11 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
triple-beam@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984"
integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==
ts-interface-checker@^0.1.9: ts-interface-checker@^0.1.9:
version "0.1.13" version "0.1.13"
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
@@ -6355,15 +6493,20 @@ undefsafe@^2.0.5:
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
universalify@^0.2.0: universalify@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
universalify@^2.0.0: universalify@^2.0.0:
version "2.0.0" version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
unpipe@1.0.0, unpipe@~1.0.0: unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0" version "1.0.0"
@@ -6579,6 +6722,32 @@ wide-align@^1.1.2:
dependencies: dependencies:
string-width "^1.0.2 || 2 || 3 || 4" string-width "^1.0.2 || 2 || 3 || 4"
winston-transport@^4.5.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.6.0.tgz#f1c1a665ad1b366df72199e27892721832a19e1b"
integrity sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==
dependencies:
logform "^2.3.2"
readable-stream "^3.6.0"
triple-beam "^1.3.0"
winston@^3.11.0:
version "3.11.0"
resolved "https://registry.yarnpkg.com/winston/-/winston-3.11.0.tgz#2d50b0a695a2758bb1c95279f0a88e858163ed91"
integrity sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==
dependencies:
"@colors/colors" "^1.6.0"
"@dabh/diagnostics" "^2.0.2"
async "^3.2.3"
is-stream "^2.0.0"
logform "^2.4.0"
one-time "^1.0.0"
readable-stream "^3.4.0"
safe-stable-stringify "^2.3.1"
stack-trace "0.0.x"
triple-beam "^1.3.0"
winston-transport "^4.5.0"
wordwrap@^1.0.0: wordwrap@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"