feat: add run command

This commit is contained in:
Justin Lin committed 2025-04-18 15:04:28 +08:00
1 parent e622011b21
commit dae9e841ca
3 files changed
+77 -9

No files matched your search

+20 -1
View File
@@ -1,11 +1,16 @@
/* eslint-disable no-await-in-loop */
import { IQuest, IStage } from './schema' import { IQuest, IStage } from './schema'
import { Stage } from './Stage' import { Stage } from './Stage'
import { ICommand, IFileExistenceChecker } from './types'
export class Quest { export class Quest {
private stages: Stage[] private stages: Stage[]
private quest: IQuest private quest: IQuest
constructor(quest: IQuest) { constructor(
quest: IQuest,
private checker: IFileExistenceChecker
) {
this.quest = quest this.quest = quest
this.stages = quest.stages.map((stage: IStage) => new Stage(stage)) this.stages = quest.stages.map((stage: IStage) => new Stage(stage))
} }
@@ -13,4 +18,18 @@ export class Quest {
getActiveStages(completed: string[]) { getActiveStages(completed: string[]) {
return this.stages.filter((stage) => stage.active(completed)) return this.stages.filter((stage) => stage.active(completed))
} }
async findSatisfiedStage(
command: ICommand,
completed: string[]
): Promise<string | null> {
const stages = this.getActiveStages(completed)
for (const stage of stages) {
const satisfies = await stage.satisfies(command, this.checker)
if (satisfies) {
return stage.id
}
}
return null
}
} }
+15 -3
View File
@@ -1,14 +1,18 @@
import { Quest } from './Quest' import { Quest } from './Quest'
import { IQuest } from './schema' import { IQuest } from './schema'
import { ISession } from './types' import { ICommand, ISession, IFileExistenceChecker } from './types'
export class Session implements ISession { export class Session implements ISession {
private completed: string[] = [] private completed: string[] = []
private quest: Quest private quest: Quest
constructor({ completedStages }: ISession, quest: IQuest) { constructor(
{ completedStages }: ISession,
quest: IQuest,
checker: IFileExistenceChecker
) {
this.completed = completedStages this.completed = completedStages
this.quest = new Quest(quest) this.quest = new Quest(quest, checker)
} }
get completedStages() { get completedStages() {
@@ -22,4 +26,12 @@ export class Session implements ISession {
getActiveStages(): string[] { getActiveStages(): string[] {
return this.quest.getActiveStages(this.completed).map((stage) => stage.id) return this.quest.getActiveStages(this.completed).map((stage) => stage.id)
} }
async runCommand(command: ICommand) {
const stageId = await this.quest.findSatisfiedStage(command, this.completed)
if (stageId) {
this.complete(stageId)
}
return stageId
}
} }
+42 -5
View File
@@ -1,6 +1,7 @@
import { expect, describe, it } from '@jest/globals' import { expect, describe, it } from '@jest/globals'
import { IQuest } from '../src/schema' import { IQuest } from '../src/schema'
import { Session } from '../src/Session' import { Session } from '../src/Session'
import { MockFileChecker } from './asyncCondition.test'
const quest: IQuest = { const quest: IQuest = {
id: 'quest1', id: 'quest1',
@@ -12,25 +13,33 @@ const quest: IQuest = {
{ {
id: 'stage1', id: 'stage1',
requirements: [], requirements: [],
condition: {}, condition: {
command: 'echo start',
},
response: { type: 'narrative', content: 'Narrative 1' }, response: { type: 'narrative', content: 'Narrative 1' },
}, },
{ {
id: 'stage2', id: 'stage2',
requirements: ['stage1'], requirements: ['stage1'],
condition: {}, condition: {
command: 'ls',
},
response: { type: 'narrative', content: 'Narrative 2' }, response: { type: 'narrative', content: 'Narrative 2' },
}, },
{ {
id: 'stage3', id: 'stage3',
requirements: ['stage1'], requirements: ['stage1'],
condition: {}, condition: {
command: 'mkdir hello',
},
response: { type: 'narrative', content: 'Narrative 3' }, response: { type: 'narrative', content: 'Narrative 3' },
}, },
{ {
id: 'stage4', id: 'stage4',
requirements: ['stage2', 'stage3'], requirements: ['stage2', 'stage3'],
condition: {}, condition: {
command: 'echo finish',
},
response: { type: 'narrative', content: 'Narrative 4' }, response: { type: 'narrative', content: 'Narrative 4' },
}, },
], ],
@@ -42,7 +51,8 @@ describe('Session', () => {
{ {
completedStages: [], completedStages: [],
}, },
quest quest,
new MockFileChecker()
) )
expect(session.getActiveStages()).toEqual(['stage1']) expect(session.getActiveStages()).toEqual(['stage1'])
@@ -55,4 +65,31 @@ describe('Session', () => {
session.complete('stage3') session.complete('stage3')
expect(session.getActiveStages()).toEqual(['stage4']) expect(session.getActiveStages()).toEqual(['stage4'])
}) })
it('should run command', async () => {
const session = new Session(
{
completedStages: [],
},
quest,
new MockFileChecker()
)
expect(await session.runCommand({ command: 'hi' })).toBeNull()
expect(await session.runCommand({ command: 'echo start' })).toEqual(
'stage1'
)
expect(await session.runCommand({ command: 'echo start' })).toBeNull()
expect(await session.runCommand({ command: 'ls' })).toEqual('stage2')
expect(await session.runCommand({ command: 'echo finish' })).toBeNull()
expect(await session.runCommand({ command: 'mkdir hello' })).toEqual(
'stage3'
)
expect(await session.runCommand({ command: 'echo finish' })).toEqual(
'stage4'
)
})
}) })