mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
feat: parse quest using zod, basic
This commit is contained in:
1 parent
963655c1f7
commit
ed4d04b658
22 files changed
+390
-95
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export * from './quest.js'
|
||||
export * from './quest/index.js'
|
||||
export * from './session.js'
|
||||
|
||||
export const passwordPolicy = {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export interface QuestDetailResponse {
|
||||
_id: string
|
||||
title: string
|
||||
instruction: string
|
||||
requirements: string[]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod'
|
||||
import { conditionSchema } from './condition.js'
|
||||
import { responseSchema } from './response.js'
|
||||
|
||||
export const commandResponderSchema = z.object({
|
||||
condition: conditionSchema.optional(),
|
||||
responses: z.array(responseSchema),
|
||||
hints: z.array(z.string()),
|
||||
})
|
||||
|
||||
export type CommandResponder = z.infer<typeof commandResponderSchema>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from 'zod'
|
||||
import { fileConditionSchema } from './file-condition.js'
|
||||
|
||||
export const conditionSchema = z.object({
|
||||
command: z.array(z.string()).optional(),
|
||||
output: z.array(z.string()).optional(),
|
||||
error: z.array(z.string()).optional(),
|
||||
pwd: z.array(z.string()).optional(),
|
||||
files: z.array(fileConditionSchema).optional(),
|
||||
})
|
||||
|
||||
export type Condition = z.infer<typeof conditionSchema>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const fileConditionSchema = z.object({
|
||||
path: z.string(),
|
||||
type: z.string().optional(),
|
||||
exists: z.boolean().default(true),
|
||||
})
|
||||
|
||||
export type FileCondition = z.infer<typeof fileConditionSchema>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod'
|
||||
import { conditionSchema } from './condition.js'
|
||||
import { responseSchema } from './response.js'
|
||||
|
||||
export const globalExceptionSchema = z.object({
|
||||
condition: conditionSchema,
|
||||
responses: z.array(responseSchema),
|
||||
requirements: z.array(z.string()),
|
||||
})
|
||||
|
||||
export type GlobalException = z.infer<typeof globalExceptionSchema>
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const hintSchema = z.array(z.string())
|
||||
|
||||
export type Hint = z.infer<typeof hintSchema>
|
||||
@@ -0,0 +1,11 @@
|
||||
export * from './requirement.js'
|
||||
export * from './hint.js'
|
||||
export * from './file-condition.js'
|
||||
export * from './condition.js'
|
||||
export * from './response.js'
|
||||
export * from './command-responder.js'
|
||||
export * from './stage-exception.js'
|
||||
export * from './stage.js'
|
||||
export * from './global-exception.js'
|
||||
export * from './quest.js'
|
||||
export * from './quest-detail-response.js'
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const questDetailResponseSchema = z.object({
|
||||
_id: z.string(),
|
||||
title: z.string(),
|
||||
instruction: z.string(),
|
||||
requirements: z.array(z.string()),
|
||||
})
|
||||
|
||||
export type QuestDetailResponse = z.infer<typeof questDetailResponseSchema>
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod'
|
||||
import { stageSchema } from './stage.js'
|
||||
import { globalExceptionSchema } from './global-exception.js'
|
||||
import { requirementSchema } from './requirement.js'
|
||||
|
||||
export const questSchema = z.object({
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
image: z.string(),
|
||||
instruction: z.string(),
|
||||
requirements: requirementSchema,
|
||||
stages: z.array(stageSchema),
|
||||
exceptions: z.array(globalExceptionSchema).default([]),
|
||||
})
|
||||
|
||||
export type Quest = z.infer<typeof questSchema>
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const requirementSchema = z.array(z.string()).default([])
|
||||
|
||||
export type Requirement = z.infer<typeof requirementSchema>
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const responseSchema = z.object({
|
||||
type: z.enum(['narrative', 'dialogue']).default('narrative'),
|
||||
content: z.string(),
|
||||
})
|
||||
|
||||
export type Response = z.infer<typeof responseSchema>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod'
|
||||
import { conditionSchema } from './condition.js'
|
||||
import { responseSchema } from './response.js'
|
||||
|
||||
export const stageExceptionSchema = z.object({
|
||||
condition: conditionSchema,
|
||||
responses: z.array(responseSchema),
|
||||
})
|
||||
|
||||
export type StageException = z.infer<typeof stageExceptionSchema>
|
||||
@@ -0,0 +1,18 @@
|
||||
import { z } from 'zod'
|
||||
import { stageExceptionSchema } from './stage-exception.js'
|
||||
import { conditionSchema } from './condition.js'
|
||||
import { responseSchema } from './response.js'
|
||||
import { hintSchema } from './hint.js'
|
||||
import { requirementSchema } from './requirement.js'
|
||||
|
||||
export const stageSchema = z.object({
|
||||
id: z.string(),
|
||||
task: z.string(),
|
||||
exceptions: z.array(stageExceptionSchema).default([]),
|
||||
requirements: requirementSchema,
|
||||
condition: conditionSchema,
|
||||
responses: z.array(responseSchema),
|
||||
hints: hintSchema.default([]),
|
||||
})
|
||||
|
||||
export type Stage = z.infer<typeof stageSchema>
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, it, expect } from '@jest/globals'
|
||||
import { conditionSchema } from '../src/index.js'
|
||||
|
||||
describe('parsing condition', () => {
|
||||
it('should parse condition', () => {
|
||||
const condition = {
|
||||
command: ['^echo start$'],
|
||||
output: ['^start$'],
|
||||
}
|
||||
const result = conditionSchema.safeParse(condition)
|
||||
expect(result.error).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,93 @@
|
||||
import { describe, it, expect } from '@jest/globals'
|
||||
import { questSchema } from '../src/index.js'
|
||||
|
||||
describe('Test quest schema', () => {
|
||||
it('should parse quest schema', () => {
|
||||
const sampleQuest = {
|
||||
id: 'get-started',
|
||||
image: 'base',
|
||||
title: '終端機的基本知識',
|
||||
instruction: `歡迎來到 Linux Odyssey!
|
||||
在這個關卡中,你將會學到:
|
||||
1. 如何使用這個網站
|
||||
2. 如何使用終端機`,
|
||||
stages: [
|
||||
{
|
||||
id: 'start',
|
||||
task: '按下 `開始` ����開始',
|
||||
condition: {
|
||||
command: ['^echo start$'],
|
||||
},
|
||||
responses: [
|
||||
{
|
||||
content: `在畫面的中下方的區��是終端機,你可以在終端機��輸入指令來操作電��。
|
||||
在終端機中,你所看到的 \`commander:~ $\` 叫做命令提示字串。
|
||||
��令提示字串最後的��字號 \`$\` 叫命令提示符,你必須將指令輸入在它的後方。
|
||||
��你輸入完指令並按下 Enter 之後,終端機會��行你輸入的指令,並且會從終端機的下一行開始輸出指令的��行結果。
|
||||
直到你看到新的 \`commander:~ $\` 出現時,代表你可以輸入下一個指令了。
|
||||
��在請你輸入 \`echo hello\` 來看看會發生什麼事。`,
|
||||
},
|
||||
],
|
||||
hints: ['�� 在 `commander:~ $` ��面輸入 `echo hello` ��按下 Enter'],
|
||||
},
|
||||
{
|
||||
id: 'echo-hello',
|
||||
task: '在終端機輸入 `echo hello`',
|
||||
requirements: ['start'],
|
||||
condition: {
|
||||
command: ['^echo hello$'],
|
||||
},
|
||||
responses: [
|
||||
{
|
||||
content: `你應��可以看到終端機上��示了以下內容:
|
||||
\`\`\`
|
||||
commander:~ $ echo hello
|
||||
hello
|
||||
\`\`\`
|
||||
第一行的 \`commander:~ $\` 是命令提示字串,接在後方的 \`echo hello\` 是你輸入的指令。
|
||||
第二行的 \`hello\` ��是終端機的輸出結果。
|
||||
你現在知道了如何在終端機上輸入指令,以及查看終端機輸出的位置。
|
||||
��在請輸入 \`echo next\` 來��續下一個步��。`,
|
||||
},
|
||||
],
|
||||
hints: ['�� ���入 `echo next` ���續下一個步��'],
|
||||
},
|
||||
{
|
||||
id: 'echo-next',
|
||||
task: '在終端機輸入 `echo next`',
|
||||
requirements: ['echo-hello'],
|
||||
condition: {
|
||||
command: ['^echo next$'],
|
||||
},
|
||||
responses: [
|
||||
{
|
||||
content: `\`Ctrl + C\` 在終端機中是一個很重要的快捷鍵,它可以用來中��正在��行的指令,並產生一個新的命令提示字串,讓你可以��續輸入指令。
|
||||
如果指令��行很久沒有反應,或你因為其他原因想要中��指令,你可以按下 \`Ctrl + C\` 來中��指令。
|
||||
又或者你輸入了錯��的指令,����得慢慢��除,你也可以按下 \`Ctrl + C\` 來產生新的命令提示字串重新輸入指令。
|
||||
如果你已經理解了��才的說明,請輸入 \`echo finish\` 來結束這一關。`,
|
||||
},
|
||||
],
|
||||
hints: [
|
||||
'�� ��下 `Ctrl + C` 能��中��正在��行的指令',
|
||||
'�� ��下 `Ctrl + C` 能��產生新的命令提示字串',
|
||||
'�� ���入 `echo finish` 來結束這一關',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'END',
|
||||
task: '結束關卡',
|
||||
requirements: ['echo-next'],
|
||||
condition: {
|
||||
command: ['^echo finish$'],
|
||||
},
|
||||
responses: [
|
||||
{
|
||||
content: '恭喜你完成了這個關卡!',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
expect(questSchema.safeParse(sampleQuest).error).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, it, expect } from '@jest/globals'
|
||||
import { responseSchema } from '../src/quest/response.js'
|
||||
|
||||
describe('parsing response', () => {
|
||||
it('should parse valid response', () => {
|
||||
const response = {
|
||||
type: 'narrative',
|
||||
content: 'This is a response content',
|
||||
}
|
||||
const result = responseSchema.safeParse(response)
|
||||
expect(result.error).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should fail to parse invalid response', () => {
|
||||
const response = {
|
||||
type: 'dialogue',
|
||||
content: 'This is a response content',
|
||||
}
|
||||
const result = responseSchema.safeParse(response)
|
||||
expect(result.error).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import { describe, it, expect } from '@jest/globals'
|
||||
import { stageSchema } from '../src/quest/stage.js'
|
||||
|
||||
describe('parsing stage', () => {
|
||||
it('should parse valid stage', () => {
|
||||
const stage = {
|
||||
id: 'start',
|
||||
task: '按下 `開始` ����開始',
|
||||
requirements: ['start'],
|
||||
condition: {
|
||||
command: ['^echo start$'],
|
||||
},
|
||||
responses: [
|
||||
{
|
||||
type: 'text',
|
||||
content: `在畫面的中下方的區��是終端機,你可以在終端機��輸入指令來操作電��。
|
||||
在終端機中,你所看到的 \`commander:~ $\` 叫做命令提示字串。
|
||||
��令提示字串最後的��字號 \`$\` 叫命令提示符,你必須將指令輸入在它的後方。
|
||||
��你輸入完指令並按下 Enter 之後,終端機會��行你輸入的指令,並且會從終端機的下一行開始輸出指令的��行結果。
|
||||
直到你看到新的 \`commander:~ $\` 出現時,代表你可以輸入下一個指令了。
|
||||
��在請你輸入 \`echo hello\` 來看看會發生什麼事。`,
|
||||
},
|
||||
],
|
||||
hints: ['�� 在 `commander:~ $` ��面輸入 `echo hello` ��按下 Enter'],
|
||||
}
|
||||
const result = stageSchema.safeParse(stage)
|
||||
expect(result.error).toBeUndefined()
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user