From 8f0476e7cb8e650e30d67033f7169dcdcfed4172 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 7 Jun 2024 06:08:43 +0800 Subject: [PATCH] core: loader: support profile --- changelog | 3 +- packages/hydrooj/bin/commands.ts | 13 +----- packages/hydrooj/src/commands/addon.ts | 6 +-- packages/hydrooj/src/entry/cli.ts | 9 ++++- packages/hydrooj/src/entry/worker.ts | 3 +- packages/hydrooj/src/loader.ts | 55 +++++++++++--------------- packages/hydrooj/src/options.ts | 46 ++++++++++++++------- packages/hydrooj/src/service/db.ts | 4 +- packages/hydrooj/src/service/server.ts | 2 +- 9 files changed, 73 insertions(+), 68 deletions(-) diff --git a/changelog b/changelog index bbb462391..89673887b 100644 --- a/changelog +++ b/changelog @@ -1,4 +1,5 @@ 移除了 app/listen, app/load/${category} 钩子 移除了 ctx.app (请使用 ctx.root) 移除了 ctx.options (请使用 ctx.root.config) -serializer: 移除 showDisplayName 参数 \ No newline at end of file +serializer: 移除 showDisplayName 参数 +移除了 loader.addScript, loader.addon 函数 \ No newline at end of file diff --git a/packages/hydrooj/bin/commands.ts b/packages/hydrooj/bin/commands.ts index 6ae17d97e..372d5cfb2 100644 --- a/packages/hydrooj/bin/commands.ts +++ b/packages/hydrooj/bin/commands.ts @@ -1,21 +1,11 @@ /* eslint-disable import/no-dynamic-require */ -import os from 'os'; -import path from 'path'; import cac from 'cac'; -import fs from 'fs-extra'; +import { getAddons } from '../src/options'; const argv = cac().parse(); -const hydroPath = path.resolve(os.homedir(), '.hydro'); -fs.ensureDirSync(hydroPath); -const addonPath = path.resolve(hydroPath, 'addon.json'); -if (!fs.existsSync(addonPath)) fs.writeFileSync(addonPath, '[]'); -let addons = JSON.parse(fs.readFileSync(addonPath).toString()); - -addons = Array.from(new Set(addons)); if (!argv.args[0] || argv.args[0] === 'cli') { const hydro = require('../src/loader'); - for (const addon of addons) hydro.addon(addon); (argv.args[0] === 'cli' ? hydro.loadCli : hydro.load)().catch((e) => { console.error(e); process.exit(1); @@ -29,6 +19,7 @@ if (!argv.args[0] || argv.args[0] === 'cli') { cli.help(); cli.parse(); if (!cli.matchedCommand) { + const addons = getAddons(); for (const i of addons) { try { require(`${i}/command.ts`).apply(cli); diff --git a/packages/hydrooj/src/commands/addon.ts b/packages/hydrooj/src/commands/addon.ts index 296990340..36e821e95 100644 --- a/packages/hydrooj/src/commands/addon.ts +++ b/packages/hydrooj/src/commands/addon.ts @@ -4,9 +4,9 @@ import path from 'path'; import { CAC } from 'cac'; import fs from 'fs-extra'; import { Logger } from '@hydrooj/utils'; +import { getAddons, writeAddons } from '../options'; const logger = new Logger('addon'); -const addonPath = path.resolve(os.homedir(), '.hydro', 'addon.json'); const addonDir = path.resolve(os.homedir(), '.hydro', 'addons'); export function register(cli: CAC) { @@ -15,7 +15,7 @@ export function register(cli: CAC) { console.log('Unknown operation.'); return; } - let addons = JSON.parse(fs.readFileSync(addonPath).toString()); + let addons = getAddons(); if (operation === 'create') { const dir = `${addonDir}/${name || 'addon'}`; fs.mkdirSync(dir, { recursive: true }); @@ -48,6 +48,6 @@ export function register(cli: CAC) { addons = Array.from(new Set(addons)); logger.info('Current Addons: '); console.log(addons); - fs.writeFileSync(addonPath, JSON.stringify(addons, null, 2)); + writeAddons(addons); }); } diff --git a/packages/hydrooj/src/entry/cli.ts b/packages/hydrooj/src/entry/cli.ts index b89481b8b..0d8b852d7 100644 --- a/packages/hydrooj/src/entry/cli.ts +++ b/packages/hydrooj/src/entry/cli.ts @@ -1,5 +1,6 @@ /* eslint-disable import/no-dynamic-require */ /* eslint-disable no-await-in-loop */ +import os from 'os'; import path from 'path'; import cac from 'cac'; import fs from 'fs-extra'; @@ -11,6 +12,7 @@ import { } from './common'; const argv = cac().parse(); +const tmpdir = path.resolve(os.tmpdir(), 'hydro'); const COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; const ARR = /=>.*$/mg; function parseParameters(fn: Function) { @@ -92,16 +94,19 @@ async function cli() { } export async function load(ctx: Context) { - const pending = global.addons; - const fail = []; + fs.ensureDirSync(tmpdir); require('../lib/i18n'); require('../utils'); require('../error'); + require('../service/bus').apply(ctx); + const pending = global.addons; + const fail = []; await db.start(); await require('../settings').loadConfig(); await require('../model/system').runConfig(); await require('../service/storage').loadStorageService(); await ctx.root.start(); + await ctx.lifecycle.flush(); require('../lib/index'); await Promise.all([ lib(pending, fail, ctx), diff --git a/packages/hydrooj/src/entry/worker.ts b/packages/hydrooj/src/entry/worker.ts index 024ca0f18..eed061989 100644 --- a/packages/hydrooj/src/entry/worker.ts +++ b/packages/hydrooj/src/entry/worker.ts @@ -6,6 +6,7 @@ import cac from 'cac'; import fs from 'fs-extra'; import { Context } from '../context'; import { Logger } from '../logger'; +import { load } from '../options'; import db from '../service/db'; import { addon, builtinModel, handler, lib, locale, model, @@ -22,7 +23,7 @@ export async function apply(ctx: Context) { require('../utils'); require('../error'); require('../service/bus').apply(ctx); - const config = require('../options')(); + const config = load(); if (!process.env.CI && !config) { logger.info('Starting setup'); await require('./setup').load(); diff --git a/packages/hydrooj/src/loader.ts b/packages/hydrooj/src/loader.ts index 773e3195a..34b5ed16e 100644 --- a/packages/hydrooj/src/loader.ts +++ b/packages/hydrooj/src/loader.ts @@ -5,7 +5,6 @@ /* eslint-disable no-eval */ import './init'; import './interface'; -import Schema from 'schemastery'; import path from 'path'; import child from 'child_process'; // eslint-disable-next-line import/no-duplicates @@ -19,6 +18,7 @@ import { Context } from './context'; // eslint-disable-next-line import/no-duplicates import { sleep, unwrapExports } from './utils'; import { PRIV } from './model/builtin'; +import { getAddons } from './options'; const argv = cac().parse(); const logger = new Logger('loader'); @@ -135,45 +135,33 @@ export class Loader { } } -export function addon(addonPath: string, prepend = false) { - try { - // Is a npm package - const packagejson = require.resolve(`${addonPath}/package.json`); - // eslint-disable-next-line import/no-dynamic-require - const payload = require(packagejson); - const name = payload.name.startsWith('@hydrooj/') ? payload.name.split('@hydrooj/')[1] : payload.name; - global.Hydro.version[name] = payload.version; - const modulePath = path.dirname(packagejson); - global.addons[prepend ? 'unshift' : 'push'](modulePath); - } catch (e) { - logger.error(`Addon not found: ${addonPath}`); - logger.error(e); - app.injectUI('Notification', 'Addon not found: {0}', { args: [addonPath], type: 'warn' }, PRIV.PRIV_VIEW_SYSTEM_NOTIFICATION); - } -} - -/** @deprecated */ -export function addScript(name: string, description: string) { - if (global.Hydro.script[name]) throw new Error(`duplicate script ${name} registered.`); - return { - args: (validate: K) => ({ - action: (run: (args: ReturnType, report: any) => boolean | Promise) => { - global.Hydro.script[name] = { - description, validate, run, - }; - }, - }), - }; -} - const loader = new Loader(); app.provide('loader'); app.loader = loader; loader.app = app; app.state[Loader.Record] = Object.create(null); +function preload() { + for (const a of [path.resolve(__dirname, '..'), ...getAddons()]) { + try { + // Is a npm package + const packagejson = require.resolve(`${a}/package.json`); + // eslint-disable-next-line import/no-dynamic-require + const payload = require(packagejson); + const name = payload.name.startsWith('@hydrooj/') ? payload.name.split('@hydrooj/')[1] : payload.name; + global.Hydro.version[name] = payload.version; + const modulePath = path.dirname(packagejson); + global.addons.push(modulePath); + } catch (e) { + logger.error(`Addon not found: ${a}`); + logger.error(e); + app.injectUI('Notification', 'Addon not found: {0}', { args: [a], type: 'warn' }, PRIV.PRIV_VIEW_SYSTEM_NOTIFICATION); + } + } +} + export async function load() { - addon(path.resolve(__dirname, '..'), true); + preload(); Error.stackTraceLimit = 50; try { const { simpleGit } = require('simple-git') as typeof import('simple-git'); @@ -212,6 +200,7 @@ export async function load() { export async function loadCli() { process.env.HYDRO_CLI = 'true'; + preload(); await require('./entry/cli').load(app); setTimeout(() => process.exit(0), 300); } diff --git a/packages/hydrooj/src/options.ts b/packages/hydrooj/src/options.ts index 2833944d0..4d487c5c2 100644 --- a/packages/hydrooj/src/options.ts +++ b/packages/hydrooj/src/options.ts @@ -1,28 +1,46 @@ import os from 'os'; import path from 'path'; -import { existsSync, readFileSync } from 'fs-extra'; -import { findFileSync } from '@hydrooj/utils/lib/utils'; -import { Logger } from './logger'; +import fs from 'fs-extra'; +import { findFileSync, Logger } from '@hydrooj/utils/lib/utils'; const logger = new Logger('options'); -export = function load() { - const envFile = path.resolve(os.homedir(), '.hydro', 'env'); - if (existsSync(envFile)) { - const content = readFileSync(envFile).toString().replace(/\r/g, ''); - for (const line of content.split('\n')) { - if (!line.includes('=')) continue; - process.env[line.split('=')[0]] = line.split('=')[1].trim(); - } +function loadEnvFile(file: string) { + const content = fs.readFileSync(file).toString().replace(/\r/g, ''); + for (const line of content.split('\n')) { + if (!line.includes('=')) continue; + process.env[line.split('=')[0]] = line.split('=')[1].trim(); } - const f = findFileSync('config.json', false); +} + +const profile = process.env.HYDRO_PROFILE; +export const hydroPath = profile ? path.resolve(os.homedir(), '.hydro', 'profiles', profile) : path.resolve(os.homedir(), '.hydro'); +if (profile && !fs.existsSync(hydroPath)) { + logger.error('Profile %s not found', profile); + process.exit(1); +} else fs.ensureDirSync(hydroPath); +const addonPath = path.resolve(hydroPath, 'addon.json'); + +export function load() { + const envFile = path.resolve(hydroPath, 'env'); + if (fs.existsSync(envFile)) loadEnvFile(envFile); + const f = profile ? path.resolve(hydroPath, 'config.json') : findFileSync('config.json', false); if (!f) return null; let result: any = {}; try { - result = JSON.parse(readFileSync(f, 'utf-8')); + result = JSON.parse(fs.readFileSync(f, 'utf-8')); } catch (e) { logger.error('Cannot read config file %o', e); result = {}; } return result; -}; +} + +export function getAddons() { + if (!fs.existsSync(addonPath)) fs.writeFileSync(addonPath, '[]'); + return JSON.parse(fs.readFileSync(addonPath).toString()); +} + +export function writeAddons(addons: string[]) { + fs.writeFileSync(addonPath, JSON.stringify(addons, null, 2)); +} diff --git a/packages/hydrooj/src/service/db.ts b/packages/hydrooj/src/service/db.ts index fde7d8bb9..6e9768708 100644 --- a/packages/hydrooj/src/service/db.ts +++ b/packages/hydrooj/src/service/db.ts @@ -5,7 +5,7 @@ import { import mongoUri from 'mongodb-uri'; import { Time } from '@hydrooj/utils'; import { Logger } from '../logger'; -import options from '../options'; +import { load } from '../options'; import * as bus from './bus'; const logger = new Logger('mongo'); @@ -37,7 +37,7 @@ class MongoService { } async start() { - const opts = options() || {}; + const opts = load() || {}; let mongourl = MongoService.buildUrl(opts); const url = mongoUri.parse(mongourl); if (process.env.CI) { diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index a3c352aab..7fcb5aae1 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -127,7 +127,6 @@ export class ConnectionHandler extends ConnectionHandlerOriginal { } export async function apply(ctx: Context) { - if (process.env.HYDRO_CLI) return; ctx.plugin(require('@hydrooj/framework'), { keys: system.get('session.keys'), proxy: !!system.get('server.xproxy') || !!system.get('server.xff'), @@ -137,6 +136,7 @@ export async function apply(ctx: Context) { xff: system.get('server.xff'), xhost: system.get('server.xhost'), }); + if (process.env.HYDRO_CLI) return; ctx.inject(['server'], ({ server, on }) => { const proxyMiddleware = proxy('/fs', { target: builtinConfig.file.endPoint,