mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
Feat Translation Chinese (Mandarin) (#68)
- Translate Component to Mandarin - Relative size of map node and font - Relative size of header title - Fix Hint Text changing direction - Modifying cypress tests for translates - Add a parameter to check the language version - Add commands for checking different version elements and placeholder - Fix docker parse JSON
This commit is contained in:
26 files changed
+395
-165
No files matched your search
@@ -2,6 +2,15 @@ name: CI Checks
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
GOOGLE_CLIENT_ID:
|
||||
required: true
|
||||
GOOGLE_CLIENT_SECRET:
|
||||
required: true
|
||||
GH_CLIENT_ID:
|
||||
required: true
|
||||
GH_CLIENT_SECRET:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
@@ -40,6 +49,6 @@ jobs:
|
||||
env:
|
||||
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
|
||||
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
|
||||
GITHUB_CLIENT_ID: ${{ secrets.GITHUB_CLIENT_ID }}
|
||||
GITHUB_CLIENT_SECRET: ${{ secrets.GITHUB_CLIENT_SECRET }}
|
||||
GITHUB_CLIENT_ID: ${{ secrets.GH_CLIENT_ID }}
|
||||
GITHUB_CLIENT_SECRET: ${{ secrets.GH_CLIENT_SECRET }}
|
||||
run: docker-compose -f docker-compose.testing.yml run cypress
|
||||
@@ -12,6 +12,7 @@ concurrency:
|
||||
jobs:
|
||||
ci-checks:
|
||||
uses: ./.github/workflows/ci-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
build:
|
||||
needs: ci-checks
|
||||
|
||||
@@ -10,6 +10,7 @@ concurrency:
|
||||
jobs:
|
||||
ci-checks:
|
||||
uses: ./.github/workflows/ci-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
code_review:
|
||||
permissions:
|
||||
|
||||
@@ -12,6 +12,7 @@ concurrency:
|
||||
jobs:
|
||||
ci-checks:
|
||||
uses: ./.github/workflows/ci-checks.yml
|
||||
secrets: inherit
|
||||
|
||||
build:
|
||||
needs: ci-checks
|
||||
|
||||
@@ -41,7 +41,7 @@ const hasSocialLogins = computed(() => {
|
||||
})
|
||||
|
||||
const success = () => {
|
||||
router.push({ name: 'game', params: { questId: 'helloworld' } })
|
||||
router.push({ name: 'map' })
|
||||
}
|
||||
|
||||
const error = (err) => {
|
||||
@@ -100,6 +100,7 @@ const handleChange = () => {
|
||||
<form @submit.prevent="handleSubmit()" class="w-full">
|
||||
<div v-if="socialLogin && hasSocialLogins">
|
||||
<a
|
||||
id="GoogleLogin"
|
||||
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"
|
||||
href="/api/v1/auth/google"
|
||||
@@ -108,9 +109,13 @@ const handleChange = () => {
|
||||
:icon="['fab', 'google']"
|
||||
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
|
||||
id="GitHubLogin"
|
||||
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"
|
||||
href="/api/v1/auth/github"
|
||||
@@ -119,17 +124,22 @@ const handleChange = () => {
|
||||
:icon="['fab', 'github']"
|
||||
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>
|
||||
<p
|
||||
id="or"
|
||||
class="text-text-secondary flex justify-center mt-3"
|
||||
v-if="type === 'login' || type === 'register'"
|
||||
>
|
||||
or
|
||||
<!-- or -->
|
||||
或
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<input
|
||||
<!-- <input
|
||||
type="text"
|
||||
id="username"
|
||||
ref="usernameInput"
|
||||
@@ -139,6 +149,17 @@ const handleChange = () => {
|
||||
required
|
||||
@input="handleChange()"
|
||||
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
|
||||
class="text-sm font-normal text-text-secondary"
|
||||
@@ -149,7 +170,7 @@ const handleChange = () => {
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-6" v-if="type === 'register'">
|
||||
<input
|
||||
<!-- <input
|
||||
type="email"
|
||||
id="email"
|
||||
ref="emailInput"
|
||||
@@ -159,10 +180,21 @@ const handleChange = () => {
|
||||
required
|
||||
@input="handleChange()"
|
||||
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 class="mb-6" v-if="type === 'login' || type === 'register'">
|
||||
<input
|
||||
<!-- <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"
|
||||
@@ -170,30 +202,66 @@ const handleChange = () => {
|
||||
v-model="password"
|
||||
required
|
||||
@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>
|
||||
<p class="text-error flex justify-center" v-if="errorMessage">
|
||||
<p
|
||||
id="ErrorDisplay"
|
||||
class="text-error flex justify-center"
|
||||
v-if="errorMessage"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<button
|
||||
id="LogInOrSignUp"
|
||||
class="inline-flex justify-center rounded-lg font-black py-2 bg-text-primary text-bg w-full"
|
||||
type="submit"
|
||||
>
|
||||
<span v-if="type === 'login'">Log In</span>
|
||||
<span v-else>Sign Up</span>
|
||||
<span v-if="type === 'login'">
|
||||
<!-- Log In -->
|
||||
登入
|
||||
</span>
|
||||
<span v-else>
|
||||
<!-- Sign Up -->
|
||||
註冊
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<p class="text-text flex justify-center mt-3">
|
||||
<span v-if="type === 'register'"
|
||||
>Already have an account?
|
||||
<RouterLink class="text-text-primary font-bold" to="/login">
|
||||
<u>Log in</u>
|
||||
<p id="SwitchLoginRegister" class="text-text flex justify-center mt-3">
|
||||
<span id="CheckRegistered" v-if="type === 'register'">
|
||||
<!-- Already have an account? -->
|
||||
已經有帳號了嗎?
|
||||
<RouterLink
|
||||
id="SwitchtoLogin"
|
||||
class="text-text-primary font-bold"
|
||||
to="/login"
|
||||
>
|
||||
<u>
|
||||
<!-- Log in -->
|
||||
登入
|
||||
</u>
|
||||
</RouterLink></span
|
||||
>
|
||||
<span v-else-if="type === 'login'"
|
||||
>Don't have an account?
|
||||
<RouterLink class="text-text-primary font-bold" to="/register">
|
||||
<u>Sign up</u>
|
||||
<span id="CheckRegistered" v-else-if="type === 'login'">
|
||||
<!-- Don't have an account? -->
|
||||
還沒有帳號嗎?
|
||||
<RouterLink
|
||||
id="SwitchtoRegister"
|
||||
class="text-text-primary font-bold"
|
||||
to="/register"
|
||||
>
|
||||
<u>
|
||||
<!-- Sign up -->
|
||||
註冊
|
||||
</u>
|
||||
</RouterLink></span
|
||||
>
|
||||
</p>
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
|
||||
<template>
|
||||
<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">
|
||||
<p class="text-text-secondary">--Command List--</p>
|
||||
<p class="text-text-secondary">
|
||||
<!-- --Command List-- -->
|
||||
-- --指令列表-- --
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -18,14 +18,19 @@ setTimeout(() => {
|
||||
<div v-if="showModal" class="modal">
|
||||
<div class="modal-content bg-bg-secondary rounded-lg">
|
||||
<span class="close text-text" @click="closeModal">×</span>
|
||||
<h2 class="text-2xl text-text mb-5 w-full inline-flex justify-center">
|
||||
Quest Completed!
|
||||
<h2
|
||||
id="QuestCompleted"
|
||||
class="text-2xl text-text mb-5 w-full inline-flex justify-center"
|
||||
>
|
||||
<!-- Quest Completed! -->
|
||||
關卡完成!
|
||||
</h2>
|
||||
<p class="text-base text-text">
|
||||
您已成功完成挑戰,真的非常感謝您的參與!<br />
|
||||
如果您能撥冗填寫這份問卷,我們將不勝感激,這對我們非常重要!
|
||||
</p>
|
||||
<a
|
||||
id="SurveyButton"
|
||||
:href="surveyUrl"
|
||||
target="_blank"
|
||||
class="inline-flex justify-center rounded-lg text-base font-black py-2 mt-3 bg-text-primary w-full"
|
||||
|
||||
@@ -5,30 +5,55 @@
|
||||
:icon="['far', 'circle-question']"
|
||||
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 id="reset" class="bg-bg-secondary rounded-lg p-2" @click="reset">
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'arrow-rotate-left']"
|
||||
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 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
|
||||
: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>
|
||||
</section>
|
||||
</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>
|
||||
@@ -15,7 +15,17 @@ const handleLogout = async () => {
|
||||
reset()
|
||||
router.push({ name: 'login' })
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
@@ -38,7 +48,9 @@ const handleLogout = async () => {
|
||||
/>
|
||||
</button> -->
|
||||
<p
|
||||
id="HeaderText"
|
||||
class="text-text-primary inline-block font-bold whitespace-nowrap"
|
||||
style="font-size: 2vh"
|
||||
v-if="sessionStore.quest"
|
||||
>
|
||||
{{ sessionStore.quest.title }}
|
||||
@@ -63,6 +75,12 @@ const handleLogout = async () => {
|
||||
class="text-text-primary h-full w-full"
|
||||
/>
|
||||
</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
|
||||
title="Bug Report"
|
||||
:href="bugReportUrl"
|
||||
|
||||
@@ -30,8 +30,11 @@ const right = () => {
|
||||
:icon="['far', 'lightbulb']"
|
||||
class="text-yellow-200 p-2 content-center"
|
||||
/>
|
||||
<h1 class="inline text-text font-xl p-2 m-1">Hint</h1>
|
||||
<div v-if="current !== -1" class="w-full flex justify-end">
|
||||
<h1 class="inline text-text w-1/2 font-xl p-2 m-1">
|
||||
<!-- Hint -->
|
||||
提示
|
||||
</h1>
|
||||
<div v-if="current !== -1" class="flex w-full justify-end items-end">
|
||||
<button
|
||||
class="p-2 m-1 w-1/8"
|
||||
id="Lbutton"
|
||||
|
||||
@@ -14,6 +14,7 @@ const marginX = 100
|
||||
const marginY = 65
|
||||
const chartContainer = ref(null)
|
||||
let chartInstance = null
|
||||
const fullwidth = window.screen.width
|
||||
|
||||
use([GraphChart, SVGRenderer, TitleComponent, TooltipComponent])
|
||||
|
||||
@@ -49,11 +50,11 @@ const genOption = (nodes, edges) => ({
|
||||
symbol: () => {
|
||||
return NodeImage
|
||||
},
|
||||
symbolSize: [200, 100],
|
||||
roam: true,
|
||||
symbolSize: [fullwidth / 10, fullwidth / 20],
|
||||
roam: false,
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 22,
|
||||
fontSize: fullwidth / 90,
|
||||
},
|
||||
edgeSymbol: ['pin', 'arrow'],
|
||||
edgeSymbolSize: [2, 15],
|
||||
@@ -126,7 +127,8 @@ function initChart(option) {
|
||||
if (unlocked) {
|
||||
router.push({ name: 'game', params: { questId: id } })
|
||||
} 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"
|
||||
/>
|
||||
<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>
|
||||
<div class="flex flex-wrap absolute w-full h-full z-1">
|
||||
<div ref="chartContainer" class="w-full h-full flex flex-wrap"></div>
|
||||
|
||||
@@ -12,7 +12,10 @@ import sessionStore from '../store/session'
|
||||
<div v-if="sessionStore.quest" class="whitespace-pre-wrap">
|
||||
<p class="text-text">{{ sessionStore.quest.instruction }}</p>
|
||||
<br />
|
||||
<p class="text-text">Tasks:</p>
|
||||
<p id="tasks" class="text-text">
|
||||
<!-- Tasks: -->
|
||||
任務:
|
||||
</p>
|
||||
<ul v-if="sessionStore.session">
|
||||
<li v-for="task in sessionStore.session.tasks" :key="task.id">
|
||||
<p v-if="task.completed" class="text-text-primary">
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<font-awesome-icon :icon="['fas', 'terminal']" class="text-text p-1" />
|
||||
<button class="text-text h-[5%]">Terminal</button>
|
||||
<button id="Terminal" class="text-text h-[5%]">
|
||||
<!-- Terminal -->
|
||||
終端機
|
||||
</button>
|
||||
<div
|
||||
id="terminal"
|
||||
ref="terminal"
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
faArrowRight,
|
||||
faBug,
|
||||
faCircleExclamation,
|
||||
faMap,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
faLightbulb,
|
||||
@@ -36,6 +37,7 @@ library.add(
|
||||
faArrowRightFromBracket,
|
||||
faArrowRight,
|
||||
faBug,
|
||||
faMap,
|
||||
faCircleExclamation,
|
||||
faLightbulb,
|
||||
faCircleQuestion,
|
||||
|
||||
+4
-9
@@ -10,11 +10,9 @@ const routes = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: {
|
||||
name: 'game',
|
||||
params: {
|
||||
questId: 'helloworld',
|
||||
},
|
||||
name: 'register',
|
||||
},
|
||||
meta: { requiresGuest: true },
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
@@ -66,13 +64,10 @@ router.beforeEach(async (to, from, next) => {
|
||||
})
|
||||
} else if (to.meta.requiresGuest && loggedIn) {
|
||||
next({
|
||||
name: 'game', // Or any default authenticated user route
|
||||
params: {
|
||||
questId: 'helloworld',
|
||||
},
|
||||
name: 'map',
|
||||
})
|
||||
} else {
|
||||
next() // Always call next() at the end
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -8,21 +8,24 @@ import { checkUsername, chooseUsername } from '../utils/auth'
|
||||
async function check({ username, error }) {
|
||||
if (username) {
|
||||
if (!isValidUsername(username)) {
|
||||
error('Invalid username.')
|
||||
// error('Invalid username.')
|
||||
error('無效的帳號名稱')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await checkUsername(username)
|
||||
} catch (err) {
|
||||
if (err instanceof TooManyRequestsError) {
|
||||
error('Too many requests. Try again in 2 minutes.')
|
||||
// error('Too many requests. Try again in 2 minutes.')
|
||||
error('太多請求,兩分鐘後再試一次。')
|
||||
return
|
||||
}
|
||||
if (err instanceof ValidationError) {
|
||||
error(err.message)
|
||||
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()
|
||||
} catch (err) {
|
||||
if (err instanceof TooManyRequestsError) {
|
||||
error('Too many requests. Try again in 2 minutes.')
|
||||
// error('Too many requests. Try again in 2 minutes.')
|
||||
error('太多請求,兩分鐘後再試一次。')
|
||||
return
|
||||
}
|
||||
if (err instanceof ValidationError) {
|
||||
|
||||
@@ -30,13 +30,15 @@ onMounted(async () => {
|
||||
await init(props.questId)
|
||||
} catch (err) {
|
||||
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
|
||||
}
|
||||
if (err instanceof LoadSessionError) {
|
||||
toast.error(
|
||||
`Failed to create session for quest: ${err.questId}. Please re-login and try again.`
|
||||
)
|
||||
// toast.error(
|
||||
// `Failed to create session for quest: ${err.questId}. Please re-login and try again.`
|
||||
// )
|
||||
toast.error(`無法建立工作階段: ${err.questId},請重新登入再試一次。`)
|
||||
return
|
||||
}
|
||||
console.error(err)
|
||||
|
||||
@@ -15,19 +15,23 @@ const handleLogin = async ({ username, password, success, error }) => {
|
||||
if (isSuccess) success()
|
||||
} catch (err) {
|
||||
if (err instanceof TooManyRequestsError) {
|
||||
error('Too many requests. Try again in 2 minutes.')
|
||||
// error('Too many requests. Try again in 2 minutes.')
|
||||
error('太多請求,兩分鐘後再試一次。')
|
||||
return
|
||||
}
|
||||
if (err instanceof UnauthorizedError) {
|
||||
error('Wrong username or password.')
|
||||
// error('Wrong username or password.')
|
||||
error('錯誤的帳號名稱或密碼。')
|
||||
return
|
||||
}
|
||||
if (err instanceof ValidationError) {
|
||||
error('Invalid username or password.')
|
||||
// error('Invalid username or password.')
|
||||
error('無效的帳號名稱或密碼。')
|
||||
return
|
||||
}
|
||||
console.error(err)
|
||||
error('Something went wrong.')
|
||||
// error('Something went wrong.')
|
||||
error('出了點問題。')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,10 +7,11 @@ import GameHeaderPart from '../components/GameHeaderPart.vue'
|
||||
<div class="h-[6vh] w-full">
|
||||
<GameHeaderPart />
|
||||
<p
|
||||
class="text-text-primary absolute z-1 font-bold inline-block"
|
||||
style="left: 1.2%; top: 1.5%; height: 100%"
|
||||
class="text-text-primary absolute z-1 font-bold inline-block tracking-widest"
|
||||
style="left: 1.2%; top: 1.5%; height: 100%; font-size: 2vh"
|
||||
>
|
||||
Quest Map
|
||||
<!-- Quest Map -->
|
||||
Unix編年史
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center items-center w-full h-[94vh]">
|
||||
|
||||
@@ -17,11 +17,13 @@ function handleRegister({ username, email, password, success, error }) {
|
||||
.then(success)
|
||||
.catch((err) => {
|
||||
if (err instanceof TooManyRequestsError) {
|
||||
error('Too many requests. Try again in 2 minutes.')
|
||||
// error('Too many requests. Try again in 2 minutes.')
|
||||
error('太多請求,兩分鐘後再試一次。')
|
||||
return
|
||||
}
|
||||
if (err instanceof UnauthorizedError) {
|
||||
error('Invalid username or password.')
|
||||
// error('Invalid username or password.')
|
||||
error('無效的帳號名稱或密碼。')
|
||||
return
|
||||
}
|
||||
if (err instanceof ValidationError) {
|
||||
@@ -29,23 +31,27 @@ function handleRegister({ username, email, password, success, error }) {
|
||||
return
|
||||
}
|
||||
console.error(err)
|
||||
error('Something went wrong.')
|
||||
// error('Something went wrong.')
|
||||
error('出了點問題。')
|
||||
})
|
||||
}
|
||||
|
||||
async function check({ username, email, password, error }) {
|
||||
if (username && !isValidUsername(username)) {
|
||||
error('Invalid username.')
|
||||
// error('Invalid username.')
|
||||
error('無效的帳號名稱')
|
||||
return
|
||||
}
|
||||
if (email && !validator.isEmail(email)) {
|
||||
error('Invalid email.')
|
||||
// error('Invalid email.')
|
||||
error('無效的電子郵件')
|
||||
return
|
||||
}
|
||||
if (password && !validator.isStrongPassword(password, passwordPolicy)) {
|
||||
error(
|
||||
'Your password must be 8+ characters with at least one number, one upper and one lower case letter.'
|
||||
)
|
||||
// error(
|
||||
// 'Your password must be 8+ characters with at least one number, one upper and one lower case letter.'
|
||||
// )
|
||||
error('密碼必須超過8個字元,至少包含一個數字、一個大寫及一個小寫。')
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -56,11 +62,13 @@ async function check({ username, email, password, error }) {
|
||||
return
|
||||
}
|
||||
if (err instanceof TooManyRequestsError) {
|
||||
error('Too many requests. Try again in 2 minutes.')
|
||||
// error('Too many requests. Try again in 2 minutes.')
|
||||
error('太多請求,兩分鐘後再試一次。')
|
||||
return
|
||||
}
|
||||
console.error(err)
|
||||
error('Something went wrong.')
|
||||
// error('Something went wrong.')
|
||||
error('出了點問題。')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -80,6 +80,16 @@ export async function deleteContainer(id) {
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
engine.buildImage(
|
||||
@@ -98,12 +108,16 @@ export function buildQuestImage(questPath, questId) {
|
||||
}
|
||||
response.on('data', (data) => {
|
||||
// Process the data (this could be Docker build output)
|
||||
const { stream, error } = JSON.parse(data.toString())
|
||||
if (stream) {
|
||||
logger.info(stream)
|
||||
}
|
||||
if (error) {
|
||||
logger.error(error)
|
||||
try {
|
||||
data
|
||||
.toString()
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line !== '')
|
||||
.forEach((line) => {
|
||||
parseJSONOutput(line)
|
||||
})
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -24,6 +24,7 @@ module.exports = defineConfig({
|
||||
env: {
|
||||
defaultAccount: getOrFail('TESTING_USERNAME'),
|
||||
defaultPassword: getOrFail('TESTING_PASSWORD'),
|
||||
isCHVersion: true,
|
||||
// google_username: process.env.GOOGLE_USERNAME,
|
||||
// google_password: process.env.GOOGLE_PASSWORD,
|
||||
// google_client_id: process.env.GOOGLE_CLIENT_ID,
|
||||
|
||||
@@ -3,9 +3,7 @@ export default function checkLoginUI() {
|
||||
cy.get('h1.text-text-primary')
|
||||
.should('contain', 'Linux Odyssey')
|
||||
.and('be.visible')
|
||||
cy.get('#password')
|
||||
.invoke('attr', 'placeholder')
|
||||
.should('contain', 'Password')
|
||||
cy.CheckPlaceholder('#password', '密碼', 'Password')
|
||||
}
|
||||
describe('example helloworld app', () => {
|
||||
describe('Login tests', () => {
|
||||
@@ -14,51 +12,65 @@ describe('example helloworld app', () => {
|
||||
})
|
||||
it('Check Login Page Elements-Register', () => {
|
||||
checkLoginUI()
|
||||
cy.get('#username')
|
||||
.invoke('attr', 'placeholder')
|
||||
.should('contain', 'Username')
|
||||
cy.CheckPlaceholder('#username', '帳號名稱', 'Username')
|
||||
cy.get('label.text-text-secondary').should('be.visible') // check the rules is displayed
|
||||
cy.get('#email').invoke('attr', 'placeholder').should('contain', 'Email')
|
||||
cy.get('span').contains('Already have an account?').should('be.visible')
|
||||
cy.get('a.text-text-primary')
|
||||
.should('contain', 'Log in')
|
||||
.and('be.visible')
|
||||
cy.CheckPlaceholder('#email', '電子郵件', 'Email')
|
||||
cy.CheckTextElement(
|
||||
'#CheckRegistered',
|
||||
'已經有帳號了嗎?',
|
||||
'Already have an account?'
|
||||
)
|
||||
cy.CheckTextElement('#SwitchtoLogin', '登入', 'Log in')
|
||||
})
|
||||
it('Check Login Page Elements-Login', () => {
|
||||
cy.get('a.text-text-primary')
|
||||
.should('contain', 'Log in')
|
||||
.and('be.visible')
|
||||
.click()
|
||||
cy.get('#SwitchtoLogin').click()
|
||||
checkLoginUI()
|
||||
cy.get('#username')
|
||||
.invoke('attr', 'placeholder')
|
||||
.should('contain', 'Email / Username')
|
||||
cy.findByRole('button', { name: 'Log In' }).should('be.visible')
|
||||
cy.get('span').contains("Don't have an account?").should('be.visible')
|
||||
cy.get('a.text-text-primary')
|
||||
.should('contain', 'Sign up')
|
||||
.and('be.visible')
|
||||
cy.CheckPlaceholder(
|
||||
'#username',
|
||||
'電子郵件 / 帳號名稱',
|
||||
'Email / Username'
|
||||
)
|
||||
cy.get('#LogInOrSignUp').should('be.visible')
|
||||
cy.CheckTextElement(
|
||||
'#CheckRegistered',
|
||||
'還沒有帳號嗎?',
|
||||
"Don't have an account?"
|
||||
)
|
||||
cy.CheckTextElement('#SwitchtoRegister', '註冊', 'Sign up')
|
||||
})
|
||||
it('Check social account UI', () => {
|
||||
cy.get('p').contains('or').should('be.visible')
|
||||
cy.findByRole('link', { name: 'Continue with Google' }).should(
|
||||
'be.visible'
|
||||
cy.CheckTextElement(
|
||||
'#GoogleLogin',
|
||||
'以 Google 繼續',
|
||||
'Continue with Google'
|
||||
)
|
||||
cy.findByRole('link', { name: 'Continue with GitHub' }).should(
|
||||
'be.visible'
|
||||
cy.CheckTextElement('#or', '或', 'or')
|
||||
cy.CheckTextElement(
|
||||
'#GitHubLogin',
|
||||
'以 GitHub 繼續',
|
||||
'Continue with GitHub'
|
||||
)
|
||||
})
|
||||
it('Check login fail', () => {
|
||||
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.get('p').contains('Invalid username or password.').should('be.visible')
|
||||
cy.CheckTextElement(
|
||||
'#ErrorDisplay',
|
||||
'無效的帳號名稱或密碼。',
|
||||
'Invalid username or password.'
|
||||
)
|
||||
})
|
||||
it('Login-Password Already Register', () => {
|
||||
cy.LoginWithPassword(
|
||||
Cypress.env('defaultAccount'),
|
||||
Cypress.env('defaultPassword')
|
||||
)
|
||||
cy.url().should('include', '/map')
|
||||
})
|
||||
})
|
||||
describe('Game Start Page UI', () => {
|
||||
@@ -66,33 +78,35 @@ describe('example helloworld app', () => {
|
||||
cy.PrepareForGame()
|
||||
})
|
||||
it('Check Header', () => {
|
||||
cy.findByRole('button', { name: 'Hello, Linux World!' }).should(
|
||||
'be.visible'
|
||||
)
|
||||
cy.get('#HeaderText')
|
||||
.should('contain', 'Hello, Linux World!')
|
||||
.and('be.visible')
|
||||
cy.findByRole('link', { name: 'Bug Report' }).should('be.visible')
|
||||
cy.findByRole('button', { name: 'Sign Out' }).should('be.visible')
|
||||
})
|
||||
it('Check QuestInfo', () => {
|
||||
cy.get('#topic').should('contain', 'Hello, Linux World!')
|
||||
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')
|
||||
})
|
||||
it('Check Command Cheat Sheet', () => {
|
||||
cy.get('#cmdlist').within(($cmdlist) => {
|
||||
cy.get($cmdlist).should('be.visible')
|
||||
cy.get('svg[data-icon="list"]').should('be.visible')
|
||||
cy.get('h1').should('contain', 'Command Cheatsheets').and('be.visible')
|
||||
cy.get('#cheatsheets')
|
||||
.should('contain', '--Command List--')
|
||||
.and('be.visible')
|
||||
cy.CheckTextElement('#CommandBook', '指令之書', 'Command Cheatsheets')
|
||||
cy.CheckTextElement(
|
||||
'#cheatsheets',
|
||||
'-- --指令列表-- --',
|
||||
'--Command List--'
|
||||
)
|
||||
})
|
||||
})
|
||||
it('Check Terminal', () => {
|
||||
cy.get('#terminal', { timeout: 20000 }).within(($terminal) => {
|
||||
cy.get($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')
|
||||
.should('be.visible')
|
||||
.and('contain', 'commander:~ $')
|
||||
@@ -102,19 +116,17 @@ describe('example helloworld app', () => {
|
||||
cy.get('#hint', { timeout: 20000 }).within(($hint) => {
|
||||
cy.get($hint).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', () => {
|
||||
cy.get('#tree').should('be.visible')
|
||||
cy.get('#tree').get('a').should('contain', '???').and('be.visible')
|
||||
})
|
||||
it('Check Fucnctional Buttons', () => {
|
||||
cy.findByRole('button', { name: 'Solution' }, { timeout: 30000 }).should(
|
||||
'be.visible'
|
||||
)
|
||||
cy.findByRole('button', { name: 'Reset' }).should('be.visible')
|
||||
cy.findByRole('button', { name: 'Continue' }).should('be.visible')
|
||||
cy.CheckTextElement('#solution', '解答', 'Solution')
|
||||
cy.CheckTextElement('#reset', '重來', 'Reset')
|
||||
cy.CheckTextElement('#continue', '繼續', 'Continue')
|
||||
})
|
||||
})
|
||||
describe('Game Play', () => {
|
||||
@@ -128,7 +140,7 @@ describe('example helloworld app', () => {
|
||||
.should('contain', 'zsh: command not found: 12345')
|
||||
.and('contain', '12345')
|
||||
})
|
||||
it.only('Complete the Game(relating UI)', () => {
|
||||
it('Complete the Game(relating UI)', () => {
|
||||
// get answersheet
|
||||
cy.get('@answers').then((answers) => {
|
||||
const answerarr = answers.split('\n')
|
||||
@@ -172,12 +184,8 @@ describe('example helloworld app', () => {
|
||||
cy.checkPending()
|
||||
cy.waitUntilActive()
|
||||
cy.getQuestInfo('✓ 解除封印').should('be.visible')
|
||||
cy.findByRole(
|
||||
'heading',
|
||||
{ name: 'Quest Completed!' },
|
||||
{ timeout: 20000 }
|
||||
).should('be.visible')
|
||||
// Check survey dialog pop up
|
||||
cy.CheckTextElement('#QuestCompleted', '關卡完成!', 'Quest Completed!')
|
||||
cy.get('div[class="modal"]').find('p').should('be.visible')
|
||||
cy.findByRole('link', { name: '填寫問卷' }).should('be.visible')
|
||||
})
|
||||
|
||||
@@ -5,8 +5,7 @@ Cypress.Commands.add('LoginWithPassword', (username, password) => {
|
||||
cy.visit('/login')
|
||||
cy.get('#username').type(username)
|
||||
cy.get('#password').type(password)
|
||||
cy.findByRole('button', { name: 'Log In' }).click()
|
||||
cy.url().should('include', '/game')
|
||||
cy.get('#LogInOrSignUp').click()
|
||||
})
|
||||
Cypress.Commands.add('PrepareForGame', () => {
|
||||
cy.visit('/')
|
||||
@@ -14,11 +13,12 @@ Cypress.Commands.add('PrepareForGame', () => {
|
||||
Cypress.env('defaultAccount'),
|
||||
Cypress.env('defaultPassword')
|
||||
)
|
||||
cy.url().should('include', '/map')
|
||||
cy.visit('/game/helloworld')
|
||||
cy.get('.xterm-screen', { timeout: 10000 })
|
||||
.as('Terminaltextbox')
|
||||
.should('be.visible')
|
||||
cy.findByRole('button', { name: 'Reset' }).click()
|
||||
cy.CheckTextElement('#reset', '重來', 'Reset').click()
|
||||
cy.typeInCommand('clear{enter}')
|
||||
cy.get('@Terminaltextbox', { timeout: 150000 }).should(
|
||||
'contain',
|
||||
@@ -66,3 +66,23 @@ Cypress.Commands.add('checkPending', () => {
|
||||
Cypress.Commands.add('Complete the Stage (only command)', () => {
|
||||
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
@@ -9,8 +9,8 @@
|
||||
"test:open": "cypress open"
|
||||
},
|
||||
"dependencies": {
|
||||
"cypress": "^13.5.0",
|
||||
"@testing-library/cypress": "^10.0.1",
|
||||
"cypress": "^13.5.1",
|
||||
"dotenv": "^16.3.1"
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,15 @@
|
||||
dependencies:
|
||||
"@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"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
|
||||
integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
|
||||
@@ -201,6 +209,15 @@
|
||||
chalk "^2.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":
|
||||
version "7.22.7"
|
||||
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/runtime@^7.12.5", "@babel/runtime@^7.14.6":
|
||||
version "7.23.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885"
|
||||
integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==
|
||||
version "7.23.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.4.tgz#36fa1d2b36db873d25ec631dcc4923fdc1cf2e2e"
|
||||
integrity sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
@@ -1092,9 +1109,11 @@
|
||||
integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==
|
||||
|
||||
"@types/node@^18.17.5":
|
||||
version "18.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.19.tgz#80c9b8a89d3648d9e6098f4a7184e03833fee3c5"
|
||||
integrity sha512-+pMhShR3Or5GR0/sp4Da7FnhVmTalWm81M6MkEldbwjETSaPalw138Z4KdpQaistvqQxLB7Cy4xwYdxpbSOs9Q==
|
||||
version "18.18.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.11.tgz#d43f6b92671eacfdd1fedb7a897388fb8880323f"
|
||||
integrity sha512-c1vku6qnTeujJneYH94/4aq73XrVcsJe35UPyAsSok1ijiKrkRzK+AxQPSpNMUnC03roWBBwJx/9I8V7lQoxmA==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/prettier@^2.1.5":
|
||||
version "2.7.3"
|
||||
@@ -1107,9 +1126,9 @@
|
||||
integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==
|
||||
|
||||
"@types/sizzle@^2.3.2":
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef"
|
||||
integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==
|
||||
version "2.3.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.7.tgz#ab5744eceb30daea3e0c6a9c2d9e1a3817ef7656"
|
||||
integrity sha512-sU5MNKvSFdbT97qLSabU6jxceY9BfYQe9uF+b8E1rdTecpq9ROkkKoiHlblUlhgfaz9bdCByWjAI40Y3mHf1iQ==
|
||||
|
||||
"@types/stack-utils@^2.0.0":
|
||||
version "2.0.1"
|
||||
@@ -1147,9 +1166,9 @@
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
|
||||
integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==
|
||||
version "2.10.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999"
|
||||
integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
@@ -1482,9 +1501,9 @@ astral-regex@^2.0.0:
|
||||
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
|
||||
|
||||
async@^3.2.0:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
|
||||
integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
|
||||
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
|
||||
|
||||
async@^3.2.3:
|
||||
version "3.2.5"
|
||||
@@ -2146,10 +2165,10 @@ csstype@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
|
||||
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
|
||||
|
||||
cypress@^13.5.0:
|
||||
version "13.5.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.5.0.tgz#8c149074186130972f08b2cdce6ded41f014bacd"
|
||||
integrity sha512-oh6U7h9w8wwHfzNDJQ6wVcAeXu31DlIYlNOBvfd6U4CcB8oe4akawQmH+QJVOMZlM42eBoCne015+svVqdwdRQ==
|
||||
cypress@^13.5.1:
|
||||
version "13.5.1"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.5.1.tgz#8b19bf0b9f31ea43f78980b2479bd3f25197d5cc"
|
||||
integrity sha512-yqLViT0D/lPI8Kkm7ciF/x/DCK/H/DnogdGyiTnQgX4OVR2aM30PtK+kvklTOD1u3TuItiD9wUQAF8EYWtyZug==
|
||||
dependencies:
|
||||
"@cypress/request" "^3.0.0"
|
||||
"@cypress/xvfb" "^1.2.4"
|
||||
@@ -5982,9 +6001,9 @@ ssh2@^1.11.0:
|
||||
nan "^2.17.0"
|
||||
|
||||
sshpk@^1.14.1:
|
||||
version "1.17.0"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5"
|
||||
integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==
|
||||
version "1.18.0"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028"
|
||||
integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==
|
||||
dependencies:
|
||||
asn1 "~0.2.3"
|
||||
assert-plus "^1.0.0"
|
||||
@@ -6273,9 +6292,9 @@ thenify-all@^1.0.0:
|
||||
any-promise "^1.0.0"
|
||||
|
||||
throttleit@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c"
|
||||
integrity sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.1.tgz#304ec51631c3b770c65c6c6f76938b384000f4d5"
|
||||
integrity sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==
|
||||
|
||||
through@^2.3.8:
|
||||
version "2.3.8"
|
||||
@@ -6474,15 +6493,20 @@ undefsafe@^2.0.5:
|
||||
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
|
||||
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:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
|
||||
|
||||
universalify@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
|
||||
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
||||
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
||||
|
||||
unpipe@1.0.0, unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
|
||||
Reference in new issue
Block a user