Compare commits

...
Author SHA1 Message Date
Justin Lin e449f9987b Bun migration (#218) 2026-10-11 17:57:47 +11:00
Justin Lin 419440a14f doc: remove NodeJS installation setup in Windows setup 2026-10-11 17:51:56 +11:00
Justin Lin 54225528b3 refactor: replace Jest with bun 2026-10-11 17:43:22 +11:00
Justin Lin 634dc8f987 refactor: use bun in Dockerfile and CI workflow 2026-10-11 17:24:12 +11:00
Justin Lin 77577d9b8d refactor: use bun to execute server and analytics 2026-10-11 17:04:46 +11:00
Justin Lin 2feedc329c refactor: use bun for package management
+ add package.json to each packages
+ update import path
2026-10-11 16:50:01 +11:00
Justin Lin fb7dc0eb73 Fix/execute permission (#217)
* fix: add execute permission to dog script in execute/en

* fix: add execute permission for lucky
2026-10-10 14:31:48 +11:00
Justin Lin f515228db6 deps: patch node-tar and form-data (#216) 2026-10-10 14:31:25 +11:00
Justin Lin 5e5cb38c70 deps: patch node-tar and form-data 2026-10-10 14:26:20 +11:00
87 changed files with 2511 additions and 10603 deletions

No files matched your search

+2 -1
View File
@@ -2,4 +2,5 @@
.git .git
**/dist **/dist
**/tsconfig.tsbuildinfo **/tsconfig.tsbuildinfo
data data
postgres-data
+2 -1
View File
@@ -2,4 +2,5 @@ vite.config.js
.gitignore .gitignore
data data
mongo-init.js mongo-init.js
dist dist
postgres-data
+3 -1
View File
@@ -2,7 +2,9 @@ module.exports = {
root: true, root: true,
env: { env: {
node: true, node: true,
jest: true, },
globals: {
Bun: 'readonly',
}, },
plugins: ['@typescript-eslint', 'cypress', 'vue'], plugins: ['@typescript-eslint', 'cypress', 'vue'],
extends: [ extends: [
+9 -6
View File
@@ -11,18 +11,21 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: oven-sh/setup-bun@v2
with: with:
node-version: 20 bun-version: 1.3.14
- name: Install dependencies - name: Install dependencies
run: yarn install run: bun install --frozen-lockfile
- name: Type check
run: bun run type-check
- name: Build - name: Build
run: yarn build run: bun run build
- name: Lint - name: Lint
run: yarn lint run: bun run lint
- name: Test - name: Test
run: yarn test run: bun run test
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
run: docker-compose -f docker-compose.testing.yml build base backend app-test cypress run: docker-compose -f docker-compose.testing.yml build base backend app-test cypress
- name: Build Test Quests - name: Build Test Quests
run: docker-compose -f docker-compose.testing.yml run --rm backend yarn build:quests run: docker-compose -f docker-compose.testing.yml run --rm backend bun run build:quests
- name: Run Cypress Tests - name: Run Cypress Tests
env: env:
+4 -4
View File
@@ -4,10 +4,10 @@
{ {
"type": "node", "type": "node",
"request": "launch", "request": "launch",
"name": "File Graph Yarn Jest", "name": "File Graph Tests",
"runtimeExecutable": "yarn", "runtimeExecutable": "bun",
"args": ["test", "--runInBand"], "args": ["test", "packages/file-graph"],
"cwd": "${workspaceFolder}/packages/file-graph", "cwd": "${workspaceFolder}",
"console": "integratedTerminal", "console": "integratedTerminal",
"internalConsoleOptions": "neverOpen" "internalConsoleOptions": "neverOpen"
} }
+11 -12
View File
@@ -5,16 +5,15 @@
## TLDR ## TLDR
yarn install bun install
cp .env.sample .env cp .env.sample .env
docker compose up -d docker compose up -d
yarn dev bun run dev
## Requirements ## Requirements
- NodeJS v20+ - Bun: v1.3+
- Docker - Docker
- Yarn: v1.22
## Known Issues ## Known Issues
@@ -26,7 +25,7 @@ Windows user please refer to [docs/windows-setup.md](docs/windows-setup.md) for
git clone https://github.com/lancatlin/linux-odyssey.git git clone https://github.com/lancatlin/linux-odyssey.git
cd linux-odyssey cd linux-odyssey
yarn install bun install
## Project Structures ## Project Structures
@@ -104,7 +103,7 @@ Customized `entrypoint.sh` for each quest is not yet supported.
To build all quests: To build all quests:
yarn build:quests bun run build:quests
The base image is built with docker-compose, so you can use `docker compose build base` to rebuild it. Once you rebuild the base image, you have to rebuild all quests. The base image is built with docker-compose, so you can use `docker compose build base` to rebuild it. Once you rebuild the base image, you have to rebuild all quests.
@@ -134,11 +133,11 @@ Copy `.env.sample` to `.env` and customize it for your needs.
Run dev server (frontend, backend): Run dev server (frontend, backend):
yarn dev bun run dev
To develop analytics dashboard: To develop analytics dashboard:
yarn analytics bun run analytics
The default username and password is `alex` and `Alex1234`. The default username and password is `alex` and `Alex1234`.
@@ -146,12 +145,12 @@ The default username and password is `alex` and `Alex1234`.
Unit tests: Unit tests:
yarn test:unit bun run test
Run Cypress locally, using the current development containers: Run Cypress locally, using the current development containers:
docker compose up -d docker compose up -d
yarn test:e2e bun run test:e2e
Run Cypress in container, create brand-new containers along with it: Run Cypress in container, create brand-new containers along with it:
@@ -183,7 +182,7 @@ Start the service
# Server side # Server side
docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml pull
yarn build:quests bun run build:quests
docker compose -f docker-compose.prod.yml up -d docker compose -f docker-compose.prod.yml up -d
### Necessary Configuration ### Necessary Configuration
@@ -197,4 +196,4 @@ You can copy from `.env.sample`
You can generate a random key by running You can generate a random key by running
yarn setup bun run setup
+11 -4
View File
@@ -1,17 +1,24 @@
# Base stage # Base stage
FROM node:20-alpine AS build FROM oven/bun:1.3.14-alpine AS build
WORKDIR /app WORKDIR /app
COPY package.json yarn.lock ./ COPY package.json bun.lock ./
COPY app/package.json ./app/package.json COPY app/package.json ./app/package.json
COPY server/package.json ./server/package.json COPY server/package.json ./server/package.json
RUN yarn install --frozen-lockfile COPY services/analytics/package.json ./services/analytics/package.json
COPY tests/package.json ./tests/package.json
COPY packages/constants/package.json ./packages/constants/package.json
COPY packages/file-graph/package.json ./packages/file-graph/package.json
COPY packages/game/package.json ./packages/game/package.json
COPY packages/models/package.json ./packages/models/package.json
COPY packages/utils/package.json ./packages/utils/package.json
RUN bun install --frozen-lockfile --filter app
COPY tsconfig.json ./tsconfig.json COPY tsconfig.json ./tsconfig.json
COPY server ./server COPY server ./server
COPY packages ./packages COPY packages ./packages
COPY app ./app COPY app ./app
RUN yarn workspace app build RUN bun run --filter app build
# Production stage # Production stage
FROM nginx:stable-alpine AS prod FROM nginx:stable-alpine AS prod
+4
View File
@@ -16,6 +16,10 @@
"@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/vue-fontawesome": "^3.0.3", "@fortawesome/vue-fontawesome": "^3.0.3",
"@odyssey/constants": "workspace:*",
"@odyssey/file-graph": "workspace:*",
"@odyssey/game": "workspace:*",
"@odyssey/utils": "workspace:*",
"@trpc/client": "^11.1.0", "@trpc/client": "^11.1.0",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"axios": "^1.20.0", "axios": "^1.20.0",
+1 -1
View File
@@ -2,7 +2,7 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { ref, onMounted, watch, computed } from 'vue' import { ref, onMounted, watch, computed } from 'vue'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
import { DAG } from '../../../packages/utils' import { DAG } from '@odyssey/utils'
import { trpc } from '../utils/trpc' import { trpc } from '../utils/trpc'
import useUserProfile from '../store/userProfile' import useUserProfile from '../store/userProfile'
import QuestIntro from './QuestIntro.vue' import QuestIntro from './QuestIntro.vue'
+1 -1
View File
@@ -2,10 +2,10 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
import type { QuestLocale } from '@odyssey/constants'
import { useSession } from '../../store/session' import { useSession } from '../../store/session'
import { openBugReport, openQuestSurvey } from '../../utils/formbricks' import { openBugReport, openQuestSurvey } from '../../utils/formbricks'
import { trpc } from '../../utils/trpc' import { trpc } from '../../utils/trpc'
import type { QuestLocale } from '../../../../packages/constants'
const { t, locale } = useI18n() const { t, locale } = useI18n()
const toast = useToast() const toast = useToast()
@@ -1,11 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import type { QuestLocale } from '@odyssey/constants'
import { SUPPORTED_LOCALES, LOCALE_NAME_MAP } from '@odyssey/constants'
import { setLocale } from '../../i18n' import { setLocale } from '../../i18n'
import type { QuestLocale } from '../../../../packages/constants'
import {
SUPPORTED_LOCALES,
LOCALE_NAME_MAP,
} from '../../../../packages/constants'
const { t, locale } = useI18n({ useScope: 'global' }) const { t, locale } = useI18n({ useScope: 'global' })
+2 -2
View File
@@ -1,6 +1,6 @@
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n'
import type { QuestLocale } from '../../../packages/constants' import type { QuestLocale } from '@odyssey/constants'
import { SUPPORTED_LOCALES } from '../../../packages/constants' import { SUPPORTED_LOCALES } from '@odyssey/constants'
import en from './locales/en.json' import en from './locales/en.json'
import zhTW from './locales/zh-TW.json' import zhTW from './locales/zh-TW.json'
+3 -6
View File
@@ -1,10 +1,7 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { QuestLocale } from '../../../packages/constants' import type { QuestLocale } from '@odyssey/constants'
import { import { FileGraph, type FileGraphUpdateEvent } from '@odyssey/file-graph'
FileGraph, import type { IResponse, ITask } from '@odyssey/game'
type FileGraphUpdateEvent,
} from '../../../packages/file-graph'
import type { IResponse, ITask } from '../../../packages/game'
import type { SessionDetail } from '../../../server/src/routers/sessionRouter' import type { SessionDetail } from '../../../server/src/routers/sessionRouter'
import Socket from '../utils/socket' import Socket from '../utils/socket'
import SocketTerminal from '../utils/terminal' import SocketTerminal from '../utils/terminal'
+2 -2
View File
@@ -1,5 +1,5 @@
import type { FileGraph } from '../../packages/file-graph' import type { FileGraph } from '@odyssey/file-graph'
import type { IResponse, ITask } from '../../packages/game' import type { IResponse, ITask } from '@odyssey/game'
export interface Task { export interface Task {
id: number id: number
+1 -1
View File
@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { isValidUsername } from '../../../packages/utils' import { isValidUsername } from '@odyssey/utils'
import AuthForm from '../components/AuthForm.vue' import AuthForm from '../components/AuthForm.vue'
import DynamicBackground from '../components/DynamicBackground.vue' import DynamicBackground from '../components/DynamicBackground.vue'
import HeaderPart from '../components/header/HeaderPart.vue' import HeaderPart from '../components/header/HeaderPart.vue'
+2 -2
View File
@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import validator from 'validator' import validator from 'validator'
import { isValidUsername } from '../../../packages/utils' import { isValidUsername } from '@odyssey/utils'
import { passwordPolicy } from '../../../packages/constants' import { passwordPolicy } from '@odyssey/constants'
import AuthForm from '../components/AuthForm.vue' import AuthForm from '../components/AuthForm.vue'
import DynamicBackground from '../components/DynamicBackground.vue' import DynamicBackground from '../components/DynamicBackground.vue'
import HeaderPart from '../components/header/HeaderPart.vue' import HeaderPart from '../components/header/HeaderPart.vue'
-10
View File
@@ -1,19 +1,9 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
resolve: {
alias: {
'@game': path.resolve(__dirname, '../packages/game'),
'@models': path.resolve(__dirname, '../packages/models'),
'@utils': path.resolve(__dirname, '../packages/utils'),
'@constants': path.resolve(__dirname, '../packages/constants'),
'@file-graph': path.resolve(__dirname, '../packages/file-graph'),
},
},
server: { server: {
host: '0.0.0.0', host: '0.0.0.0',
proxy: { proxy: {
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript',
],
}
+2167
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -0,0 +1,3 @@
[test]
coverage = true
coverageDir = "coverage"
+9 -15
View File
@@ -74,29 +74,23 @@ sudo apt update
sudo apt install -y git curl ca-certificates build-essential sudo apt install -y git curl ca-certificates build-essential
``` ```
### Install Node.js 20 ### Install Bun
```bash ```bash
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - curl -fsSL https://bun.sh/install | bash
sudo apt install -y nodejs
``` ```
### Install Yarn 1.22 Restart your terminal (or run `source ~/.bashrc`) so `bun` is on your `PATH`.
```bash
sudo npm install -g yarn@1.22.22
```
Verify the installations: Verify the installations:
```bash ```bash
git --version git --version
node --version node --version
npm --version bun --version
yarn --version
``` ```
The Node.js version should be `20.x` or later, and the Yarn version should be `1.22.x`. The Node.js version should be `20.x` or later, and the Bun version should be `1.3` or later.
## 4. Clone the Project ## 4. Clone the Project
@@ -130,10 +124,10 @@ code .
From the project root, run: From the project root, run:
```bash ```bash
yarn install bun install
``` ```
Warnings during installation may be harmless. The installation succeeded if Yarn finishes with a `Done` message and does not display `error Command failed`. Warnings during installation may be harmless. The installation succeeded if Bun finishes with an `installed` message and does not display an `error:` message.
If a `gyp` error appears, confirm that it belongs to an optional dependency before ignoring it. If a `gyp` error appears, confirm that it belongs to an optional dependency before ignoring it.
@@ -180,7 +174,7 @@ docker compose down
Start the frontend and backend development servers: Start the frontend and backend development servers:
```bash ```bash
yarn dev bun run dev
``` ```
Keep the terminal running and open the following address in your browser: Keep the terminal running and open the following address in your browser:
@@ -194,5 +188,5 @@ To stop the development servers, press `Ctrl+C`.
You can verify that the project builds successfully by running: You can verify that the project builds successfully by running:
```bash ```bash
yarn build bun run build
``` ```
-201
View File
@@ -1,201 +0,0 @@
/**
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/configuration
*/
import type { Config } from 'jest'
const config: Config = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/tmp/jest_rs",
// Automatically clear mock calls, instances, contexts and results before every test
// clearMocks: false,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: 'v8',
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// The default configuration for fake timers
// fakeTimers: {
// "enableGlobally": false
// },
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "mjs",
// "cjs",
// "jsx",
// "ts",
// "tsx",
// "json",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1', // Redirect .js imports to .ts files
},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
// testEnvironment: "jest-environment-node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
}
export default config
+19 -25
View File
@@ -13,31 +13,27 @@
"tests" "tests"
], ],
"scripts": { "scripts": {
"app": "yarn workspace app dev", "app": "bun run --filter app dev",
"build": "yarn workspace server build && yarn workspace app build && yarn workspace analytics build", "build": "bun run --filter app build",
"dev": "concurrently -n \"App,Server\" -c \"bgBlue.bold,bgGreen.bold\" \"yarn workspace app dev\" \"yarn workspace server dev\"", "dev": "concurrently -n \"App,Server\" -c \"bgBlue.bold,bgGreen.bold\" \"bun run --filter app dev\" \"bun run --filter server dev\"",
"server": "yarn workspace server dev", "server": "bun run --filter server dev",
"lint": "eslint .", "lint": "eslint .",
"format": "prettier --ignore-path .prettierignore --write app server", "format": "prettier --ignore-path .prettierignore --write app server",
"test": "yarn node --experimental-vm-modules --no-warnings $(yarn bin jest)", "test": "bun test",
"test:e2e": "yarn workspace tests test", "test:e2e": "bun run --filter tests test",
"test:open": "yarn workspace tests test:open", "test:open": "bun run --filter tests test:open",
"analytics": "yarn workspace analytics dev", "analytics": "bun run --filter analytics dev",
"setup": "yarn workspace server setup", "setup": "bun run --filter server setup",
"clean": "yarn workspace server clean", "clean": "bun run --filter server clean",
"build:quests": "yarn workspace server build:quests" "build:quests": "bun run --filter server build:quests",
"type-check": "bun run --filter server --filter analytics --filter app type-check"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.29.6", "@types/bun": "^1.4.2",
"@babel/preset-env": "^7.24.0",
"@babel/preset-typescript": "^7.23.3",
"@jest/globals": "^29.7.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.10.4", "@types/node": "^20.10.4",
"@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0", "@typescript-eslint/parser": "^6.21.0",
"@vue/eslint-config-prettier": "^7.1.0", "@vue/eslint-config-prettier": "^7.1.0",
"babel-jest": "^29.7.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
@@ -48,15 +44,13 @@
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.15.1", "eslint-plugin-vue": "^9.15.1",
"globals": "^16.0.0", "globals": "^16.0.0",
"jest": "^29.7.0",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"ts-node": "^10.9.2",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vue-eslint-parser": "^9.4.2", "vue-eslint-parser": "^9.4.2"
"zod-to-json-schema": "^3.24.5"
}, },
"dependencies": { "packageManager": "bun@1.3.14",
"latest": "^0.2.0" "overrides": {
}, "@mapbox/node-pre-gyp/tar": "^7.5.19",
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" "@cypress/request/form-data": "^2.5.4"
}
} }
+13
View File
@@ -0,0 +1,13 @@
{
"name": "@odyssey/constants",
"version": "0.0.0",
"private": true,
"main": "index.ts",
"types": "index.ts",
"exports": {
".": "./index.ts"
},
"dependencies": {
"zod": "^3.24.2"
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "@odyssey/file-graph",
"version": "0.0.0",
"private": true,
"main": "index.ts",
"types": "index.ts",
"exports": {
".": "./index.ts"
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, test, expect } from '@jest/globals' import { describe, test, expect, beforeEach } from 'bun:test'
import File from '../file.js' import File from '../file.js'
describe('File', () => { describe('File', () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, test, expect } from '@jest/globals' import { describe, test, expect, beforeEach } from 'bun:test'
import FileGraph from '../fileGraph.js' import FileGraph from '../fileGraph.js'
import FileNode from '../fileNode.js' import FileNode from '../fileNode.js'
import { FileObject } from '../file.js' import { FileObject } from '../file.js'
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, test, expect } from '@jest/globals' import { describe, test, expect, beforeEach } from 'bun:test'
import { ParentNotExistsError } from '../errors.js' import { ParentNotExistsError } from '../errors.js'
import FileNode, { IFileNode } from '../fileNode.js' import FileNode, { IFileNode } from '../fileNode.js'
import { FileObject } from '../file.js' import { FileObject } from '../file.js'
+1 -1
View File
@@ -1,4 +1,4 @@
import { FileGraph, IFileNode } from '../file-graph' import { FileGraph, IFileNode } from '@odyssey/file-graph'
import { Quest } from './Quest' import { Quest } from './Quest'
import { import {
IQuest, IQuest,
+15
View File
@@ -0,0 +1,15 @@
{
"name": "@odyssey/game",
"version": "0.0.0",
"private": true,
"main": "index.ts",
"types": "index.ts",
"exports": {
".": "./index.ts"
},
"dependencies": {
"@odyssey/file-graph": "workspace:*",
"zod": "^3.24.2",
"zod-to-json-schema": "^3.24.5"
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { IFileNode } from '../../file-graph' import { IFileNode } from '@odyssey/file-graph'
export * from './condition.js' export * from './condition.js'
export * from './stage.js' export * from './stage.js'
+2 -23
View File
@@ -1,28 +1,7 @@
import { test, expect } from '@jest/globals' import { test, expect } from 'bun:test'
import { IFileExistenceChecker, IFileInput } from '../schema'
import { checkFiles } from '../condition/FileMatcher.js' import { checkFiles } from '../condition/FileMatcher.js'
import { Condition } from '../condition/Condition.js' import { Condition } from '../condition/Condition.js'
import { MockFileChecker } from './mockFileChecker'
export class MockFileChecker implements IFileExistenceChecker {
private files: IFileInput[] = [
{
path: '/home/user/hello.txt',
type: 'file',
},
{
path: '/home/user/world.txt',
type: 'file',
},
{
path: '/home/user/Downloads',
type: 'directory',
},
]
async exists(file: IFileInput): Promise<boolean> {
return this.files.some((f) => f.path === file.path && f.type === file.type)
}
}
test('file matcher', async () => { test('file matcher', async () => {
const checker = new MockFileChecker() const checker = new MockFileChecker()
+2 -2
View File
@@ -1,11 +1,11 @@
import { test, expect } from '@jest/globals' import { it, test, expect } from 'bun:test'
import { Condition } from '../condition/Condition' import { Condition } from '../condition/Condition'
import { OutputMatcher } from '../condition/OutputMatcher' import { OutputMatcher } from '../condition/OutputMatcher'
import { PwdMatcher } from '../condition/PwdMatcher' import { PwdMatcher } from '../condition/PwdMatcher'
import { CommandMatcher } from '../condition/CommandMatcher' import { CommandMatcher } from '../condition/CommandMatcher'
import { ErrorMatcher } from '../condition/ErrorMatcher' import { ErrorMatcher } from '../condition/ErrorMatcher'
import { conditionSchema } from '../schema' import { conditionSchema } from '../schema'
import { MockFileChecker } from './asyncCondition.test' import { MockFileChecker } from './mockFileChecker'
test('command match', () => { test('command match', () => {
const matcher = new CommandMatcher('^echo start$') const matcher = new CommandMatcher('^echo start$')
+2 -2
View File
@@ -1,7 +1,7 @@
import { expect, it, describe } from '@jest/globals' import { describe, it, expect } from 'bun:test'
import { IQuest } from '../schema' import { IQuest } from '../schema'
import { GameSession } from '../Session' import { GameSession } from '../Session'
import { MockFileChecker } from './asyncCondition.test' import { MockFileChecker } from './mockFileChecker'
const quest: IQuest = { const quest: IQuest = {
id: 'quest1', id: 'quest1',
+22
View File
@@ -0,0 +1,22 @@
import type { IFileExistenceChecker, IFileInput } from '../schema'
export class MockFileChecker implements IFileExistenceChecker {
private files: IFileInput[] = [
{
path: '/home/user/hello.txt',
type: 'file',
},
{
path: '/home/user/world.txt',
type: 'file',
},
{
path: '/home/user/Downloads',
type: 'directory',
},
]
async exists(file: IFileInput): Promise<boolean> {
return this.files.some((f) => f.path === file.path && f.type === file.type)
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { expect, describe, it } from '@jest/globals' import { describe, it, expect } from 'bun:test'
import { IQuest } from '../schema' import { IQuest } from '../schema'
import { GameSession } from '../Session' import { GameSession } from '../Session'
import { MockFileChecker } from './asyncCondition.test' import { MockFileChecker } from './mockFileChecker'
const quest: IQuest = { const quest: IQuest = {
id: 'quest1', id: 'quest1',
+2 -2
View File
@@ -1,6 +1,6 @@
import { test, expect } from '@jest/globals' import { test, expect } from 'bun:test'
import { Stage } from '../Stage' import { Stage } from '../Stage'
import { MockFileChecker } from './asyncCondition.test' import { MockFileChecker } from './mockFileChecker'
const checker = new MockFileChecker() const checker = new MockFileChecker()
+9 -5
View File
@@ -1,10 +1,14 @@
import mongoose from 'mongoose' import mongoose from 'mongoose'
export { User, IUser } from './user.js' export { User, type IUser } from './user.js'
export { Session, ISession } from './session.js' export { Session, type ISession } from './session.js'
export { Command, ICommand } from './command.js' export { Command, type ICommand } from './command.js'
export { UserProfile, IUserProfile, IProgress } from './userProfile.js' export {
export { ILoginAttempt, LoginAttempt } from './loginAttempt.js' UserProfile,
type IUserProfile,
type IProgress,
} from './userProfile.js'
export { type ILoginAttempt, LoginAttempt } from './loginAttempt.js'
export async function connectDB(uri: string) { export async function connectDB(uri: string) {
const db = await mongoose.connect(uri) const db = await mongoose.connect(uri)
+14
View File
@@ -0,0 +1,14 @@
{
"name": "@odyssey/models",
"version": "0.0.0",
"private": true,
"main": "index.ts",
"types": "index.ts",
"exports": {
".": "./index.ts"
},
"dependencies": {
"@odyssey/constants": "workspace:*",
"mongoose": "^8.24.1"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { model, Schema, Types } from 'mongoose' import { model, Schema, Types } from 'mongoose'
import type { QuestLocale } from '../constants' import type { QuestLocale } from '@odyssey/constants'
import { SUPPORTED_LOCALES } from '../constants' import { SUPPORTED_LOCALES } from '@odyssey/constants'
export interface INode { export interface INode {
path: string path: string
+10
View File
@@ -0,0 +1,10 @@
{
"name": "@odyssey/utils",
"version": "0.0.0",
"private": true,
"main": "index.ts",
"types": "index.ts",
"exports": {
".": "./index.ts"
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { test, expect } from '@jest/globals' import { test, expect } from 'bun:test'
import { isValidEmail, isValidUsername } from '../auth' import { isValidEmail, isValidUsername } from '../auth'
test('check valid username', () => { test('check valid username', () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, it, expect } from '@jest/globals' import { describe, it, expect } from 'bun:test'
import { buildFileCheckCmd, File } from '../commands' import { buildFileCheckCmd, File } from '../commands'
describe('buildFileCheckCmd function', () => { describe('buildFileCheckCmd function', () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, it, expect } from '@jest/globals' import { describe, it, expect } from 'bun:test'
import DAG from '../dag' import DAG from '../dag'
describe('dag layers', () => { describe('dag layers', () => {
View File
File mode changed.
Regular → Executable
View File
File mode changed.
+16 -15
View File
@@ -1,27 +1,28 @@
FROM node:20-alpine AS build FROM oven/bun:1.3.14-alpine AS prod
WORKDIR /app WORKDIR /app
COPY ./package.json yarn.lock ./ COPY package.json bun.lock ./
COPY app/package.json ./app/package.json
COPY server/package.json ./server/package.json COPY server/package.json ./server/package.json
RUN yarn install COPY services/analytics/package.json ./services/analytics/package.json
COPY tests/package.json ./tests/package.json
COPY packages/constants/package.json ./packages/constants/package.json
COPY packages/file-graph/package.json ./packages/file-graph/package.json
COPY packages/game/package.json ./packages/game/package.json
COPY packages/models/package.json ./packages/models/package.json
COPY packages/utils/package.json ./packages/utils/package.json
RUN bun install --frozen-lockfile --production --filter server
# Install docker cli # Install docker cli
COPY server ./server
COPY packages ./packages
COPY tsconfig.json ./tsconfig.json
RUN yarn workspace server build
# Production
FROM node:20-alpine AS prod
WORKDIR /app
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/ COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
COPY --from=build /app/node_modules ./node_modules COPY tsconfig.json ./tsconfig.json
COPY --from=build /app/server ./server COPY packages ./packages
COPY server ./server
COPY ./quests ./quests COPY ./quests ./quests
ENV NODE_ENV=production ENV NODE_ENV=production
WORKDIR /app/server WORKDIR /app/server
CMD [ "yarn", "start" ] CMD [ "bun", "src/index.ts" ]
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
ENV PORT=3000 ENV PORT=3000
+1 -1
View File
@@ -1,4 +1,4 @@
import { connectDB, User } from '../../packages/models' import { connectDB, User } from '@odyssey/models'
async function migrate() { async function migrate() {
await connectDB( await connectDB(
+10 -16
View File
@@ -3,15 +3,18 @@
"version": "0.0.0", "version": "0.0.0",
"description": "Linux Odyssey Server", "description": "Linux Odyssey Server",
"scripts": { "scripts": {
"start": "node dist/server/src/index.js", "start": "bun src/index.ts",
"setup": "tsx src/cli/setup.ts", "setup": "bun src/cli/setup.ts",
"clean": "tsx src/cli/clean.ts", "clean": "bun src/cli/clean.ts",
"build": "tsc",
"type-check": "tsc --noEmit", "type-check": "tsc --noEmit",
"dev": "tsx watch --include \"../quests/**/*.yml\" src/index.ts", "dev": "bun scripts/dev.ts",
"build:quests": "tsx src/cli/build.ts" "build:quests": "bun src/cli/build.ts"
}, },
"dependencies": { "dependencies": {
"@odyssey/constants": "workspace:*",
"@odyssey/game": "workspace:*",
"@odyssey/models": "workspace:*",
"@odyssey/utils": "workspace:*",
"@trpc/server": "^11.8.0", "@trpc/server": "^11.8.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"commander": "^11.0.0", "commander": "^11.0.0",
@@ -36,20 +39,17 @@
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"socket.io": "^4.7.1", "socket.io": "^4.7.1",
"ssh2": "^1.16.0",
"swagger-ui-express": "^5.0.0", "swagger-ui-express": "^5.0.0",
"tsx": "^4.19.3",
"validator": "^13.15.22", "validator": "^13.15.22",
"winston": "^3.11.0", "winston": "^3.11.0",
"yaml": "^2.8.3", "yaml": "^2.8.3",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"@swc/core": "^1.4.2",
"@swc/helpers": "^0.5.6",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"@types/dockerode": "^3.3.23", "@types/dockerode": "^3.3.23",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/express-serve-static-core": "^5.1.3",
"@types/express-session": "^1.17.10", "@types/express-session": "^1.17.10",
"@types/jsonwebtoken": "^9.0.5", "@types/jsonwebtoken": "^9.0.5",
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
@@ -59,14 +59,8 @@
"@types/passport-jwt": "^4.0.1", "@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38", "@types/passport-local": "^1.0.38",
"@types/passport-oauth2": "^1.4.15", "@types/passport-oauth2": "^1.4.15",
"@types/source-map-support": "^0",
"@types/ssh2": "^1.15.1",
"@types/swagger-ui-express": "^4.1.6", "@types/swagger-ui-express": "^4.1.6",
"@types/validator": "^13.11.9", "@types/validator": "^13.11.9",
"concurrently": "^8.2.2",
"jest": "^29.6.1",
"regenerator-runtime": "^0.14.1",
"source-map-support": "^0.5.21",
"typescript": "^5.3.3" "typescript": "^5.3.3"
} }
} }
+27
View File
@@ -0,0 +1,27 @@
// Dev server with auto-restart. `bun --watch` only tracks imported modules,
// but quests are read from YAML at startup, so rewrite the entry file (same
// contents) whenever a quest file changes to trigger a restart. Bun ignores
// timestamp-only changes, so `touch` would not work.
import { watch, readFileSync, writeFileSync } from 'node:fs'
import path from 'node:path'
const entry = path.join(import.meta.dir, '../src/index.ts')
const questsDir = path.join(import.meta.dir, '../../quests')
const server = Bun.spawn(['bun', '--watch', entry], {
stdio: ['inherit', 'inherit', 'inherit'],
})
let timer: ReturnType<typeof setTimeout> | undefined
watch(questsDir, { recursive: true }, (_event, filename) => {
if (!filename?.endsWith('.yml')) return
clearTimeout(timer)
timer = setTimeout(() => {
writeFileSync(entry, readFileSync(entry))
}, 100)
})
const stop = () => server.kill()
process.on('SIGINT', stop)
process.on('SIGTERM', stop)
process.exit(await server.exited)
@@ -1,6 +1,6 @@
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { GameSession, commandSchema } from '../../../../packages/game' import { GameSession, commandSchema } from '@odyssey/game'
import { Command, Session } from '../../../../packages/models' import { Command, Session } from '@odyssey/models'
import { pushToSession } from '../socket' import { pushToSession } from '../socket'
import { finishSession } from '../../models/sessionManager' import { finishSession } from '../../models/sessionManager'
import { asyncHandler } from '../../middleware/error' import { asyncHandler } from '../../middleware/error'
+1 -1
View File
@@ -1,7 +1,7 @@
import { Router, Request, Response, Express } from 'express' import { Router, Request, Response, Express } from 'express'
import passport from 'passport' import passport from 'passport'
import { NextFunction } from 'express-serve-static-core' import { NextFunction } from 'express-serve-static-core'
import { LoginAttempt } from '../../../../packages/models' import { LoginAttempt } from '@odyssey/models'
import enabledMethods from '../../auth/passport.js' import enabledMethods from '../../auth/passport.js'
import { import {
-2
View File
@@ -1,8 +1,6 @@
import { Router } from 'express' import { Router } from 'express'
import passport from 'passport'
import commands from './commandRouter.js' import commands from './commandRouter.js'
import authRouter from './authRouter.js' import authRouter from './authRouter.js'
import authRequired from '../../middleware/authRequired.js'
import config from '../../config.js' import config from '../../config.js'
import leaderboardController from '../controllers/leaderboardController.js' import leaderboardController from '../controllers/leaderboardController.js'
+1 -1
View File
@@ -2,7 +2,7 @@ import { Server } from 'socket.io'
import type { Socket } from 'socket.io' import type { Socket } from 'socket.io'
import validator from 'validator' import validator from 'validator'
import type { Server as HttpServer } from 'http' import type { Server as HttpServer } from 'http'
import { Session } from '../../../packages/models' import { Session } from '@odyssey/models'
import SessionMiddleware from '../middleware/session.js' import SessionMiddleware from '../middleware/session.js'
import logger from '../utils/logger.js' import logger from '../utils/logger.js'
+3 -3
View File
@@ -1,8 +1,8 @@
import { check } from 'express-validator' import { check } from 'express-validator'
import { isValidUsername, isValidEmail } from '../../../../packages/utils' import { isValidUsername, isValidEmail } from '@odyssey/utils'
import { User } from '../../../../packages/models' import { User } from '@odyssey/models'
import { passwordPolicy } from '../../../../packages/constants' import { passwordPolicy } from '@odyssey/constants'
import { noError } from '../../middleware/validator.js' import { noError } from '../../middleware/validator.js'
const checkUsername = () => const checkUsername = () =>
+1 -1
View File
@@ -1,6 +1,6 @@
import type { Profile } from 'passport' import type { Profile } from 'passport'
import type { VerifyCallback } from 'passport-oauth2' import type { VerifyCallback } from 'passport-oauth2'
import { User } from '../../../packages/models' import { User } from '@odyssey/models'
import logger from '../utils/logger.js' import logger from '../utils/logger.js'
export default async function oauthVerify( export default async function oauthVerify(
+1 -1
View File
@@ -1,6 +1,6 @@
import passport from 'passport' import passport from 'passport'
import type { Express } from 'express' import type { Express } from 'express'
import { UserProfile } from '../../../packages/models' import { UserProfile } from '@odyssey/models'
import passwordStrategy from './passwordStrategy.js' 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'
+1 -1
View File
@@ -3,7 +3,7 @@ import bcrypt from 'bcrypt'
import { Strategy as LocalStrategy } from 'passport-local' import { Strategy as LocalStrategy } from 'passport-local'
import type { IVerifyOptions } from 'passport-local' import type { IVerifyOptions } from 'passport-local'
import type { Express } from 'express' import type { Express } from 'express'
import { User } from '../../../packages/models' import { User } from '@odyssey/models'
async function verifyPassword( async function verifyPassword(
username: string, username: string,
+1 -1
View File
@@ -1,4 +1,4 @@
import { connectDB } from '../../../packages/models' import { connectDB } from '@odyssey/models'
import { removeExpired } from '../containers/expiryChecker' import { removeExpired } from '../containers/expiryChecker'
import config from '../config' import config from '../config'
import logger from '../utils/logger' import logger from '../utils/logger'
+1 -1
View File
@@ -94,7 +94,7 @@ function createConfig() {
function getProjectRoot(): string { function getProjectRoot(): string {
// Start from the current file's directory // Start from the current file's directory
let currentDir = __dirname let currentDir = import.meta.dir
// Keep going up until we find the root package.json with workspaces // Keep going up until we find the root package.json with workspaces
while (currentDir !== path.parse(currentDir).root) { while (currentDir !== path.parse(currentDir).root) {
+2 -2
View File
@@ -1,7 +1,7 @@
import { execFile } from 'child_process' import { execFile } from 'child_process'
import { buildFileCheckCmd } from '../../../packages/utils' import { buildFileCheckCmd } from '@odyssey/utils'
import type { IFileExistenceChecker, IFileInput } from '../../../packages/game' import type { IFileExistenceChecker, IFileInput } from '@odyssey/game'
function sanitizeId(id: string) { function sanitizeId(id: string) {
return id.replace(/[^a-zA-Z0-9]/g, '') return id.replace(/[^a-zA-Z0-9]/g, '')
+1 -1
View File
@@ -1,6 +1,6 @@
import fs from 'fs/promises' import fs from 'fs/promises'
import Docker from 'dockerode' import Docker from 'dockerode'
import type { QuestLocale } from '../../../packages/constants' import type { QuestLocale } from '@odyssey/constants'
import config, { getQuestImage } from '../config.js' import config, { getQuestImage } from '../config.js'
import logger from '../utils/logger.js' import logger from '../utils/logger.js'
+2 -2
View File
@@ -1,7 +1,7 @@
import schedule from 'node-schedule' import schedule from 'node-schedule'
import type { HydratedDocument } from 'mongoose' import type { HydratedDocument } from 'mongoose'
import { Session } from '../../../packages/models' import { Session } from '@odyssey/models'
import type { ISession } from '../../../packages/models' import type { ISession } from '@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' import logger from '../utils/logger.js'
+2 -2
View File
@@ -5,7 +5,7 @@ import YAML from 'yaml'
import swaggerUI from 'swagger-ui-express' import swaggerUI from 'swagger-ui-express'
import passport from 'passport' import passport from 'passport'
import * as trpcExpress from '@trpc/server/adapters/express' import * as trpcExpress from '@trpc/server/adapters/express'
import { connectDB } from '../../packages/models' import { connectDB } from '@odyssey/models'
import './auth/passport.js' import './auth/passport.js'
import socketServer from './api/socket.js' import socketServer from './api/socket.js'
@@ -24,7 +24,7 @@ import { createContext } from './trpc.js'
async function main() { async function main() {
if (!config.secret) { if (!config.secret) {
logger.error( logger.error(
'No SECRET_KEY found in .env! To set up a persistent key, please run `yarn setup`:' 'No SECRET_KEY found in .env! To set up a persistent key, please run `bun run setup`:'
) )
process.exit(1) process.exit(1)
} }
+1 -1
View File
@@ -1,8 +1,8 @@
import rateLimit from 'express-rate-limit' import rateLimit from 'express-rate-limit'
import type { Options } from 'express-rate-limit' import type { Options } from 'express-rate-limit'
import type { Request, Response, NextFunction } from 'express' import type { Request, Response, NextFunction } from 'express'
import { LoginAttempt } from '@odyssey/models'
import config from '../config.js' import config from '../config.js'
import { LoginAttempt } from '../../../packages/models'
import logger from '../utils/logger.js' import logger from '../utils/logger.js'
function createRateLimiter(options: Partial<Options>) { function createRateLimiter(options: Partial<Options>) {
+3 -7
View File
@@ -3,13 +3,9 @@ import fs from 'fs/promises'
import path from 'path' import path from 'path'
import yaml from 'yaml' import yaml from 'yaml'
import type { QuestLocale } from '../../../packages/constants' import type { QuestLocale } from '@odyssey/constants'
import { SUPPORTED_LOCALES } from '../../../packages/constants' import { SUPPORTED_LOCALES } from '@odyssey/constants'
import { import { questSchema, globalExceptionSchema, IQuest } from '@odyssey/game'
questSchema,
globalExceptionSchema,
IQuest,
} from '../../../packages/game'
import logger from '../utils/logger.js' import logger from '../utils/logger.js'
import config from '../config.js' import config from '../config.js'
+4 -4
View File
@@ -1,8 +1,8 @@
import { HydratedDocument } from 'mongoose' import { HydratedDocument } from 'mongoose'
import type { QuestLocale } from '../../../packages/constants' import type { QuestLocale } from '@odyssey/constants'
import { LOCALE_NAME_MAP } from '../../../packages/constants' import { LOCALE_NAME_MAP } from '@odyssey/constants'
import { Session, UserProfile } from '../../../packages/models' import { Session, UserProfile } from '@odyssey/models'
import type { ISession, IUser } from '../../../packages/models' import type { ISession, IUser } from '@odyssey/models'
import { import {
createContainer, createContainer,
deleteContainer, deleteContainer,
+1 -1
View File
@@ -1,6 +1,6 @@
import { Express } from 'express' import { Express } from 'express'
import { format } from 'date-fns' import { format } from 'date-fns'
import { User, UserProfile } from '../../../packages/models' import { User, UserProfile } from '@odyssey/models'
import { hashPassword } from '../utils/auth.js' import { hashPassword } from '../utils/auth.js'
export async function createUser( export async function createUser(
+1 -1
View File
@@ -1,8 +1,8 @@
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { z } from 'zod' import { z } from 'zod'
import { SUPPORTED_LOCALES } from '@odyssey/constants'
import { router, publicProcedure } from '../trpc.js' import { router, publicProcedure } from '../trpc.js'
import { questManager } from '../models/quest.js' import { questManager } from '../models/quest.js'
import { SUPPORTED_LOCALES } from '../../../packages/constants'
export const questRouter = router({ export const questRouter = router({
getQuests: publicProcedure getQuests: publicProcedure
+3 -3
View File
@@ -1,8 +1,8 @@
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { z } from 'zod' import { z } from 'zod'
import { SUPPORTED_LOCALES } from '../../../packages/constants' import { SUPPORTED_LOCALES } from '@odyssey/constants'
import { ISession, Session } from '../../../packages/models' import { ISession, Session } from '@odyssey/models'
import { GameSession, VoidFileExistenceChecker } from '../../../packages/game' import { GameSession, VoidFileExistenceChecker } from '@odyssey/game'
import { createNewSession, isQuestUnlocked } from '../models/sessionManager.js' import { createNewSession, isQuestUnlocked } from '../models/sessionManager.js'
import config from '../config.js' import config from '../config.js'
import { protectedProcedure, router } from '../trpc.js' import { protectedProcedure, router } from '../trpc.js'
+1 -1
View File
@@ -1,5 +1,5 @@
import { TRPCError } from '@trpc/server' import { TRPCError } from '@trpc/server'
import { UserProfile } from '../../../packages/models' import { UserProfile } from '@odyssey/models'
import { protectedProcedure, router } from '../trpc.js' import { protectedProcedure, router } from '../trpc.js'
export const userRouter = router({ export const userRouter = router({
+1 -1
View File
@@ -1,4 +1,4 @@
import { User } from '../../../packages/models' import { User } from '@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' import logger from './logger.js'
+7 -9
View File
@@ -1,14 +1,12 @@
{ {
"extends": "../tsconfig.json", "extends": "../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"module": "NodeNext", "module": "Preserve",
"moduleResolution": "nodenext", "moduleResolution": "bundler",
"esModuleInterop": true, "target": "ESNext",
"outDir": "./dist" "types": ["bun", "node"],
"noEmit": true
}, },
"include": ["src"], "include": ["src", "scripts", "migrations"],
"exclude": ["node_modules", "src/*.test.js", "dist"], "exclude": ["node_modules", "src/*.test.js"]
"ts-node": {
"swc": true
}
} }
+12 -13
View File
@@ -1,27 +1,26 @@
FROM node:20-alpine AS build FROM oven/bun:1.3.14-alpine AS prod
WORKDIR /app WORKDIR /app
COPY ./package.json yarn.lock ./ COPY package.json bun.lock ./
COPY app/package.json ./app/package.json
COPY server/package.json ./server/package.json
COPY services/analytics/package.json ./services/analytics/package.json COPY services/analytics/package.json ./services/analytics/package.json
RUN yarn install --frozen-lockfile COPY tests/package.json ./tests/package.json
COPY packages/constants/package.json ./packages/constants/package.json
COPY packages/file-graph/package.json ./packages/file-graph/package.json
COPY packages/game/package.json ./packages/game/package.json
COPY packages/models/package.json ./packages/models/package.json
COPY packages/utils/package.json ./packages/utils/package.json
RUN bun install --frozen-lockfile --production --filter analytics
COPY tsconfig.json ./tsconfig.json COPY tsconfig.json ./tsconfig.json
COPY packages ./packages COPY packages ./packages
COPY services/analytics ./services/analytics COPY services/analytics ./services/analytics
RUN yarn workspace analytics build
# Production stage
FROM node:20-alpine AS prod
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/services/analytics ./services/analytics
ENV NODE_ENV=production ENV NODE_ENV=production
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
ENV PORT=3001 ENV PORT=3001
EXPOSE 3001 EXPOSE 3001
WORKDIR /app/services/analytics WORKDIR /app/services/analytics
CMD [ "yarn", "start" ] CMD [ "bun", "src/index.ts" ]
+4 -9
View File
@@ -1,16 +1,16 @@
{ {
"name": "analytics", "name": "analytics",
"version": "1.0.0", "version": "1.0.0",
"main": "src/index.js", "main": "src/index.ts",
"private": true, "private": true,
"license": "GPL", "license": "GPL",
"scripts": { "scripts": {
"start": "node dist/services/analytics/src/index.js", "start": "bun src/index.ts",
"build": "tsc",
"type-check": "tsc --noEmit", "type-check": "tsc --noEmit",
"dev": "tsx watch src/index.ts" "dev": "bun --watch src/index.ts"
}, },
"dependencies": { "dependencies": {
"@odyssey/models": "workspace:*",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.20.0", "express": "^4.20.0",
"express-basic-auth": "^1.2.1", "express-basic-auth": "^1.2.1",
@@ -19,12 +19,7 @@
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"@swc/core": "^1.4.2",
"@swc/helpers": "^0.5.6",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/node": "^20.11.20",
"regenerator-runtime": "^0.14.1",
"tsx": "^4.19.3",
"typescript": "^5.3.3" "typescript": "^5.3.3"
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
import express from 'express' import express from 'express'
import { engine } from 'express-handlebars' import { engine } from 'express-handlebars'
import basicAuth from 'express-basic-auth' import basicAuth from 'express-basic-auth'
import { connectDB } from '../../../packages/models' import { connectDB } from '@odyssey/models'
import config from './config' import config from './config'
import router from './router' import router from './router'
+1 -1
View File
@@ -1,4 +1,4 @@
import { Command } from '../../../../packages/models' import { Command } from '@odyssey/models'
import Pagination from './pagination' import Pagination from './pagination'
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
+1 -1
View File
@@ -1,4 +1,4 @@
import { UserProfile } from '../../../../packages/models' import { UserProfile } from '@odyssey/models'
export interface QuestProgress { export interface QuestProgress {
_id: string _id: string
+1 -1
View File
@@ -1,4 +1,4 @@
import { Command, Session } from '../../../../packages/models' import { Command, Session } from '@odyssey/models'
import Pagination from './pagination' import Pagination from './pagination'
import { import {
CommandObject, CommandObject,
+1 -1
View File
@@ -1,5 +1,5 @@
import { HydratedDocument, Types } from 'mongoose' import { HydratedDocument, Types } from 'mongoose'
import { Session, User, IUser, ISession } from '../../../../packages/models' import { Session, User, IUser, ISession } from '@odyssey/models'
import Pagination from './pagination' import Pagination from './pagination'
import { UserSessionDetail } from '../interface' import { UserSessionDetail } from '../interface'
@@ -1,3 +1,4 @@
import { describe, it, expect } from 'bun:test'
import mongoose from 'mongoose' import mongoose from 'mongoose'
import Pagination, { ASC, DESC } from '../src/models/pagination.js' import Pagination, { ASC, DESC } from '../src/models/pagination.js'
+6 -5
View File
@@ -1,10 +1,11 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./dist" "module": "Preserve",
"moduleResolution": "bundler",
"target": "ESNext",
"types": ["bun", "node"],
"noEmit": true
}, },
"include": ["src", "../models"], "include": ["src"]
"ts-node": {
"swc": true
}
} }
+13 -3
View File
@@ -1,12 +1,22 @@
FROM cypress/browsers FROM cypress/browsers
COPY --from=oven/bun:1.3.14 /usr/local/bin/bun /usr/local/bin/bun
WORKDIR /e2e WORKDIR /e2e
COPY package.json yarn.lock ./ COPY package.json bun.lock ./
COPY app/package.json ./app/package.json
COPY server/package.json ./server/package.json
COPY services/analytics/package.json ./services/analytics/package.json
COPY tests/package.json ./tests/package.json COPY tests/package.json ./tests/package.json
RUN yarn install --frozen-lockfile COPY packages/constants/package.json ./packages/constants/package.json
COPY packages/file-graph/package.json ./packages/file-graph/package.json
COPY packages/game/package.json ./packages/game/package.json
COPY packages/models/package.json ./packages/models/package.json
COPY packages/utils/package.json ./packages/utils/package.json
RUN bun install --frozen-lockfile
COPY quests ./quests COPY quests ./quests
COPY tests ./tests COPY tests ./tests
WORKDIR /e2e/tests WORKDIR /e2e/tests
CMD [ "yarn", "test" ] CMD [ "bun", "run", "test" ]
+2 -2
View File
@@ -6,8 +6,8 @@
"type": "commonjs", "type": "commonjs",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"test": "yarn cypress run", "test": "cypress run",
"test:open": "yarn cypress open" "test:open": "cypress open"
}, },
"dependencies": { "dependencies": {
"@testing-library/cypress": "^10.0.1", "@testing-library/cypress": "^10.0.1",
+1
View File
@@ -4,6 +4,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"isolatedModules": true,
"sourceMap": true, "sourceMap": true,
"baseUrl": "." "baseUrl": "."
}, },
-10132
View File
File diff suppressed because it is too large. Load diff