diff --git a/.github/workflows/prettier-check.yml b/.github/workflows/prettier-check.yml new file mode 100644 index 0000000..c257d67 --- /dev/null +++ b/.github/workflows/prettier-check.yml @@ -0,0 +1,26 @@ +name: Prettier Check +on: + push: + branches: [master] + pull_request: + +jobs: + prettier: + name: "Checking code style" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Install Node.js, NPM and Yarn + uses: actions/setup-node@v1 + with: + node-version: 20.x + + - name: Enable Corepack + run: corepack enable + + - name: Install dependencies + run: yarn install --immutable + + - name: Run Prettier check + run: yarn run prettier:check diff --git a/.gitignore b/.gitignore index 0cfe994..3617eca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .env .idea .DS_Store +node_modules +.yarn diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..e37eb01 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +.idea +.yarn +deno.lock diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/README.md b/README.md index f8c6838..86672c6 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,23 @@ Run all OpenHotel stack locally with Docker. -| Service | Repo | URL | -| ------- | ------------------- | --------------------- | -| client | `openhotel/openhotel` | http://localhost:1994 | -| auth | `openhotel/auth` | http://localhost:2024 | -| web | `openhotel/web` | http://localhost:2025 | -| onet | `openhotel/onet` | http://localhost:9400 | +| Service | Repo | URL | +| ------------ | ------------------------ | --------------------- | +| client | `openhotel/openhotel` | http://localhost:1994 | +| auth | `openhotel/auth` | http://localhost:2024 | +| web | `openhotel/web` | http://localhost:2025 | +| onet | `openhotel/onet` | http://localhost:9400 | | asset-editor | `openhotel/asset-editor` | http://localhost:2030 | -| static | `openhotel/static` | http://localhost:1995 | -| s3 | SeaweedFS | http://localhost:9000 | +| static | `openhotel/static` | http://localhost:1995 | +| s3 | SeaweedFS | http://localhost:9000 | ## Requirements - Docker -- The service repos cloned next to this one (or set `REPOS_DIR` in `.env`) +- Deno +- Git + +The service repos are cloned next to this one (or in `REPOS_DIR` from `.env`): ``` openhotel/ @@ -31,7 +34,7 @@ openhotel/ ## Usage ```bash -cp .env.example .env +deno task setup # clone missing repos and create dev configs deno task up # everything (in background) deno task up auth client # some services deno task logs # logs of all services diff --git a/compose.yml b/compose.yml index 468dcc6..c736e4a 100644 --- a/compose.yml +++ b/compose.yml @@ -80,7 +80,6 @@ services: - /src/onet/node_modules - deno-cache:/root/.cache/deno - web: <<: *dev container_name: web diff --git a/configs/asset-editor.yml b/configs/asset-editor.yml new file mode 100644 index 0000000..3536354 --- /dev/null +++ b/configs/asset-editor.yml @@ -0,0 +1,3 @@ +version: development +auth: + url: http://localhost:2024 diff --git a/configs/auth.yml b/configs/auth.yml new file mode 100644 index 0000000..cc1b9c3 --- /dev/null +++ b/configs/auth.yml @@ -0,0 +1 @@ +version: development diff --git a/configs/client.yml b/configs/client.yml new file mode 100644 index 0000000..4196a7d --- /dev/null +++ b/configs/client.yml @@ -0,0 +1,7 @@ +version: development +auth: + api: http://localhost:2024 +onet: + api: http://localhost:9400 +phantom: + enabled: false diff --git a/configs/onet.yml b/configs/onet.yml new file mode 100644 index 0000000..93ea77a --- /dev/null +++ b/configs/onet.yml @@ -0,0 +1,2 @@ +auth: + api: http://localhost:2024/api/v3 diff --git a/configs/static.yml b/configs/static.yml new file mode 100644 index 0000000..cc1b9c3 --- /dev/null +++ b/configs/static.yml @@ -0,0 +1 @@ +version: development diff --git a/configs/web.yml b/configs/web.yml new file mode 100644 index 0000000..cc1b9c3 --- /dev/null +++ b/configs/web.yml @@ -0,0 +1 @@ +version: development diff --git a/deno.json b/deno.json index fdfae9e..2861012 100644 --- a/deno.json +++ b/deno.json @@ -1,7 +1,14 @@ { "tasks": { + "setup": "deno run --allow-read --allow-write --allow-run=git,docker setup.ts", "up": "docker compose up -d --build", "logs": "docker compose logs -f --tail=100", "down": "docker compose down" + }, + "imports": { + "@std/dotenv": "jsr:@std/dotenv@0.225.8", + "@std/fs": "jsr:@std/fs@1.0.24", + "@std/path": "jsr:@std/path@1.1.6", + "@std/yaml": "jsr:@std/yaml@1.3.0" } } diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..ca22dd6 --- /dev/null +++ b/deno.lock @@ -0,0 +1,48 @@ +{ + "version": "5", + "specifiers": { + "jsr:@std/dotenv@0.225.8": "0.225.8", + "jsr:@std/fs@1.0.24": "1.0.24", + "jsr:@std/internal@^1.0.14": "1.0.14", + "jsr:@std/path@1.1.6": "1.1.6", + "jsr:@std/path@^1.1.5": "1.1.6", + "jsr:@std/yaml@1.3.0": "1.3.0" + }, + "jsr": { + "@std/dotenv@0.225.8": { + "integrity": "27b8c1a9d00cbf938345a5924487c04d9b27bdd7e7bd1983f54bcf76d0a0174a" + }, + "@std/fs@1.0.24": { + "integrity": "f3061b45b81673a2bece689da041df32d174be064c89eb6397fb5718d3fb7877", + "dependencies": [ + "jsr:@std/internal", + "jsr:@std/path@^1.1.5" + ] + }, + "@std/internal@1.0.14": { + "integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7" + }, + "@std/path@1.1.6": { + "integrity": "c68485c2a4dfbb5ae3cc74fae4e8c4e5d874cf8a8ed12927917235c758b46cbe", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/yaml@1.3.0": { + "integrity": "b250103854378b8b36cbff0fe95aec017d3d9ce86b489c67605180c602fb4922" + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/dotenv@0.225.8", + "jsr:@std/fs@1.0.24", + "jsr:@std/path@1.1.6", + "jsr:@std/yaml@1.3.0" + ], + "packageJson": { + "dependencies": [ + "npm:prettier@3.9.5" + ] + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7eca655 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "prettier:check": "prettier -c ./", + "prettier:write": "prettier --write ." + }, + "devDependencies": { + "prettier": "3.9.5" + }, + "packageManager": "yarn@4.6.0" +} diff --git a/setup.ts b/setup.ts new file mode 100644 index 0000000..7b98c16 --- /dev/null +++ b/setup.ts @@ -0,0 +1,181 @@ +import { parse as parseEnv } from "@std/dotenv"; +import { parse as parseYaml } from "@std/yaml"; +import { exists } from "@std/fs"; +import { dirname, join, resolve } from "@std/path"; + +type Service = { + name: string; + repo: string; + config: string; +}; + +const SERVICES: Service[] = [ + { + name: "client", + repo: "openhotel", + config: "app/server/config.yml", + }, + { + name: "auth", + repo: "auth", + config: "app/server/config.yml", + }, + { + name: "onet", + repo: "onet", + config: "config.yml", + }, + { + name: "web", + repo: "web", + config: "app/server/config.yml", + }, + { + name: "asset-editor", + repo: "asset-editor", + config: "app/server/config.yml", + }, + { + name: "static", + repo: "static", + config: "app/server/config.yml", + }, +]; + +const ROOT = import.meta.dirname!; + +const ok = (message: string) => console.log(`%c✔ ${message}`, "color: green"); +const warn = (message: string) => + console.log(`%c⚠ ${message}`, "color: yellow"); +const fail = (message: string) => { + console.log(`%c✘ ${message}`, "color: red"); + Deno.exit(1); +}; + +const run = async (cmd: string, ...args: string[]): Promise => { + try { + const { success } = await new Deno.Command(cmd, { + args, + stdout: "null", + stderr: "inherit", + }).output(); + + return success; + } catch { + return false; + } +}; + +const checkRequirements = async () => { + if (!(await run("git", "--version"))) fail("git not found"); + if (!(await run("docker", "compose", "version"))) { + warn("docker compose not found, you will need it to run the stack"); + } +}; + +const loadEnv = async (): Promise> => { + const envPath = join(ROOT, ".env"); + + if (!(await exists(envPath))) { + await Deno.copyFile(join(ROOT, ".env.example"), envPath); + ok(".env created from .env.example"); + } + + return parseEnv(await Deno.readTextFile(envPath)); +}; + +const cloneRepos = async (reposDir: string) => { + for (const { repo } of SERVICES) { + const repoPath = join(reposDir, repo); + + if (await exists(repoPath)) { + ok(`${repo} already exists`); + continue; + } + + console.log(`Cloning ${repo}...`); + const cloned = await run( + "git", + "clone", + `https://github.com/openhotel/${repo}.git`, + repoPath, + ); + if (cloned) ok(`${repo} cloned`); + else warn(`${repo} could not be cloned!`); + } +}; + +const readYaml = async (path: string): Promise> => + parseYaml(await Deno.readTextFile(path)) ?? {}; + +const flatten = ( + object: Record, + prefix = "", +): Record => + Object.entries(object).reduce((flat, [key, value]) => { + const path = prefix ? `${prefix}.${key}` : key; + + return typeof value === "object" && value !== null && !Array.isArray(value) + ? { ...flat, ...flatten(value, path) } + : { ...flat, [path]: value }; + }, {}); + +const checkConfig = async ( + service: Service, + configPath: string, + templatePath: string, +) => { + let config: Record; + try { + config = flatten(await readYaml(configPath)); + } catch { + warn(`${service.name}: ${configPath} is not valid yaml`); + return; + } + + const template = flatten(await readYaml(templatePath)); + for (const [key, value] of Object.entries(template)) { + if (config[key] !== value) { + warn( + `${service.name}: '${key}' should be '${value}' (is '${config[key]}')`, + ); + } + } +}; + +const copyConfigs = async (reposDir: string) => { + for (const service of SERVICES) { + const repoPath = join(reposDir, service.repo); + if (!(await exists(repoPath))) continue; + + const configPath = join(repoPath, service.config); + const templatePath = join(ROOT, "configs", `${service.name}.yml`); + + if (await exists(configPath)) { + await checkConfig(service, configPath, templatePath); + continue; + } + + await Deno.mkdir(dirname(configPath), { recursive: true }); + await Deno.copyFile(templatePath, configPath); + + ok(`${service.name}: config created`); + } +}; + +await checkRequirements(); +const env = await loadEnv(); +const reposDir = resolve(ROOT, env.REPOS_DIR ?? ".."); + +await cloneRepos(reposDir); +await copyConfigs(reposDir); + +console.log(` +Ready! Next steps: + 1. deno task up auth + 2. Sign up at http://localhost:2024 and create a hotel + 3. Fill the tokens: + - openhotel/app/server/config.yml → auth.enabled: true, auth.licenseToken + - onet/config.yml → auth.token + 4. deno task up +`); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..c76ce27 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,23 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"prettier@npm:3.9.5": + version: 3.9.5 + resolution: "prettier@npm:3.9.5" + bin: + prettier: bin/prettier.cjs + checksum: 10c0/0349dd9e6d8010965de6f69e6b6ee2484b8caf78c66675afced6c75ad60ff24c3a2a4f41153fb8adeaee4d8f539641b48cebc71d283cec4d146e9962126c5ceb + languageName: node + linkType: hard + +"root-workspace-0b6124@workspace:.": + version: 0.0.0-use.local + resolution: "root-workspace-0b6124@workspace:." + dependencies: + prettier: "npm:3.9.5" + languageName: unknown + linkType: soft