From 3ca06cfe9f40229d816ffe7c3a5c212a6053d714 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sun, 9 Feb 2025 00:12:03 +0800 Subject: [PATCH] feat: allow to set sourceRegistry by CNPMCORE_CONFIG_SOURCE_REGISTRY --- app/common/EnvUtil.ts | 37 +++++++++++++ config/config.default.ts | 114 ++++++++++++++++++++------------------- config/database.ts | 34 ++++++------ 3 files changed, 114 insertions(+), 71 deletions(-) create mode 100644 app/common/EnvUtil.ts diff --git a/app/common/EnvUtil.ts b/app/common/EnvUtil.ts new file mode 100644 index 00000000..5b04e379 --- /dev/null +++ b/app/common/EnvUtil.ts @@ -0,0 +1,37 @@ +export type ValueType = 'string' | 'boolean' | 'number'; + +export function env(key: string, valueType: ValueType, defaultValue: string): string; +export function env(key: string, valueType: ValueType, defaultValue: boolean): boolean; +export function env(key: string, valueType: ValueType, defaultValue: number): number; +export function env(key: string, valueType: ValueType, defaultValue: string | boolean | number): string | boolean | number { + const value = process.env[key]; + if (value === undefined) { + return defaultValue; + } + + if (valueType === 'string') { + return value; + } + + if (valueType === 'boolean') { + let booleanValue = false; + if (value === 'true' || value === '1') { + booleanValue = true; + } else if (value === 'false' || value === '0') { + booleanValue = false; + } else { + throw new TypeError(`Invalid boolean value: ${value} on process.env.${key}`); + } + return booleanValue; + } + + if (valueType === 'number') { + const numberValue = Number(value); + if (isNaN(numberValue)) { + throw new TypeError(`Invalid number value: ${value} on process.env.${key}`); + } + return numberValue; + } + + throw new TypeError(`Invalid value type: ${valueType}`); +} diff --git a/config/config.default.ts b/config/config.default.ts index d807ef38..1dcc29ea 100644 --- a/config/config.default.ts +++ b/config/config.default.ts @@ -1,10 +1,11 @@ import { strict as assert } from 'node:assert'; import { randomUUID } from 'node:crypto'; import { join } from 'node:path'; -import { EggAppConfig, PowerPartial } from 'egg'; +import { EggAppConfig, PowerPartial, Context } from 'egg'; import OSSClient from 'oss-cnpm'; import { patchAjv } from '../app/port/typebox'; import { ChangesStreamMode, NOT_IMPLEMENTED_PATH, SyncDeleteMode, SyncMode } from '../app/common/constants'; +import { env } from '../app/common/EnvUtil'; import type { CnpmcoreConfig } from '../app/port/config'; import { database } from './database'; @@ -12,8 +13,8 @@ export const cnpmcoreConfig: CnpmcoreConfig = { name: 'cnpm', hookEnable: false, hooksLimit: 20, - sourceRegistry: 'https://registry.npmjs.org', - sourceRegistryIsCNpm: false, + sourceRegistry: env('CNPMCORE_CONFIG_SOURCE_REGISTRY', 'string', 'https://registry.npmjs.org'), + sourceRegistryIsCNpm: env('CNPMCORE_CONFIG_SOURCE_REGISTRY_IS_CNPM', 'boolean', false), syncUpstreamFirst: false, sourceRegistrySyncTimeout: 180000, taskQueueHighWaterSize: 100, @@ -33,7 +34,7 @@ export const cnpmcoreConfig: CnpmcoreConfig = { checkChangesStreamInterval: 500, changesStreamRegistry: 'https://replicate.npmjs.com', changesStreamRegistryMode: ChangesStreamMode.streaming, - registry: process.env.CNPMCORE_CONFIG_REGISTRY || 'http://localhost:7001', + registry: env('CNPMCORE_CONFIG_REGISTRY', 'string', 'http://localhost:7001'), alwaysAuth: false, allowScopes: [ '@cnpm', @@ -45,7 +46,7 @@ export const cnpmcoreConfig: CnpmcoreConfig = { admins: { cnpmcore_admin: 'admin@cnpmjs.org', }, - enableWebAuthn: !!process.env.CNPMCORE_CONFIG_ENABLE_WEB_AUTHN, + enableWebAuthn: env('CNPMCORE_CONFIG_ENABLE_WEB_AUTHN', 'boolean', false), enableCDN: false, cdnCacheControlHeader: 'public, max-age=300', cdnVaryHeader: 'Accept, Accept-Encoding', @@ -57,7 +58,7 @@ export const cnpmcoreConfig: CnpmcoreConfig = { enableSyncUnpkgFiles: true, enableSyncUnpkgFilesWhiteList: false, strictSyncSpecivicVersion: false, - enableElasticsearch: !!process.env.CNPMCORE_CONFIG_ENABLE_ES, + enableElasticsearch: env('CNPMCORE_CONFIG_ENABLE_ES', 'boolean', false), elasticsearchIndex: 'cnpmcore_packages', strictValidateTarballPkg: false, strictValidatePackageDeps: false, @@ -69,11 +70,11 @@ export const cnpmcoreConfig: CnpmcoreConfig = { export default (appInfo: EggAppConfig) => { const config = {} as PowerPartial; - config.keys = process.env.CNPMCORE_EGG_KEYS || randomUUID(); + config.keys = env('CNPMCORE_EGG_KEYS', 'string', randomUUID()); config.cnpmcore = cnpmcoreConfig; // override config from framework / plugin - config.dataDir = process.env.CNPMCORE_DATA_DIR || join(appInfo.root, '.cnpmcore'); + config.dataDir = env('CNPMCORE_DATA_DIR', 'string', join(appInfo.root, '.cnpmcore')); config.orm = { ...database, database: database.name ?? 'cnpmcore', @@ -90,10 +91,10 @@ export default (appInfo: EggAppConfig) => { config.redis = { client: { - port: Number(process.env.CNPMCORE_REDIS_PORT || 6379), - host: process.env.CNPMCORE_REDIS_HOST || '127.0.0.1', - password: process.env.CNPMCORE_REDIS_PASSWORD || '', - db: Number(process.env.CNPMCORE_REDIS_DB || 0), + port: env('CNPMCORE_REDIS_PORT', 'number', 6379), + host: env('CNPMCORE_REDIS_HOST', 'string', '127.0.0.1'), + password: env('CNPMCORE_REDIS_PASSWORD', 'string', ''), + db: env('CNPMCORE_REDIS_DB', 'number', 0), }, }; @@ -105,7 +106,7 @@ export default (appInfo: EggAppConfig) => { config.cors = { // allow all domains - origin: (ctx): string => { + origin: (ctx: Context): string => { return ctx.get('Origin'); }, credentials: true, @@ -115,59 +116,61 @@ export default (appInfo: EggAppConfig) => { config.nfs = { client: null, - dir: process.env.CNPMCORE_NFS_DIR || join(config.dataDir, 'nfs'), + dir: env('CNPMCORE_NFS_DIR', 'string', join(config.dataDir, 'nfs')), }; /* c8 ignore next 17 */ // enable oss nfs store by env values - if (process.env.CNPMCORE_NFS_TYPE === 'oss') { - assert(process.env.CNPMCORE_NFS_OSS_BUCKET, 'require env CNPMCORE_NFS_OSS_BUCKET'); - assert(process.env.CNPMCORE_NFS_OSS_ENDPOINT, 'require env CNPMCORE_NFS_OSS_ENDPOINT'); - assert(process.env.CNPMCORE_NFS_OSS_ID, 'require env CNPMCORE_NFS_OSS_ID'); - assert(process.env.CNPMCORE_NFS_OSS_SECRET, 'require env CNPMCORE_NFS_OSS_SECRET'); - config.nfs.client = new OSSClient({ - cdnBaseUrl: process.env.CNPMCORE_NFS_OSS_CDN, - endpoint: process.env.CNPMCORE_NFS_OSS_ENDPOINT, - bucket: process.env.CNPMCORE_NFS_OSS_BUCKET, - accessKeyId: process.env.CNPMCORE_NFS_OSS_ID, - accessKeySecret: process.env.CNPMCORE_NFS_OSS_SECRET, + const nfsType = env('CNPMCORE_NFS_TYPE', 'string', ''); + if (nfsType === 'oss') { + const ossConfig = { + cdnBaseUrl: env('CNPMCORE_NFS_OSS_CDN', 'string', ''), + endpoint: env('CNPMCORE_NFS_OSS_ENDPOINT', 'string', ''), + bucket: env('CNPMCORE_NFS_OSS_BUCKET', 'string', ''), + accessKeyId: env('CNPMCORE_NFS_OSS_ID', 'string', ''), + accessKeySecret: env('CNPMCORE_NFS_OSS_SECRET', 'string', ''), defaultHeaders: { 'Cache-Control': 'max-age=0, s-maxage=60', }, - }); - } else if (process.env.CNPMCORE_NFS_TYPE === 's3') { - assert(process.env.CNPMCORE_NFS_S3_CLIENT_ENDPOINT, 'require env CNPMCORE_NFS_S3_CLIENT_ENDPOINT'); - assert(process.env.CNPMCORE_NFS_S3_CLIENT_ID, 'require env CNPMCORE_NFS_S3_CLIENT_ID'); - assert(process.env.CNPMCORE_NFS_S3_CLIENT_SECRET, 'require env CNPMCORE_NFS_S3_CLIENT_SECRET'); - assert(process.env.CNPMCORE_NFS_S3_CLIENT_BUCKET, 'require env CNPMCORE_NFS_S3_CLIENT_BUCKET'); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const S3Client = require('s3-cnpmcore'); - config.nfs.client = new S3Client({ - region: process.env.CNPMCORE_NFS_S3_CLIENT_REGION || 'default', - endpoint: process.env.CNPMCORE_NFS_S3_CLIENT_ENDPOINT, + }; + assert(ossConfig.cdnBaseUrl, 'require env CNPMCORE_NFS_OSS_BUCKET'); + assert(ossConfig.endpoint, 'require env CNPMCORE_NFS_OSS_ENDPOINT'); + assert(ossConfig.accessKeyId, 'require env CNPMCORE_NFS_OSS_ID'); + assert(ossConfig.accessKeySecret, 'require env CNPMCORE_NFS_OSS_SECRET'); + config.nfs.client = new OSSClient(ossConfig); + } else if (nfsType === 's3') { + const s3Config = { + region: env('CNPMCORE_NFS_S3_CLIENT_REGION', 'string', 'default'), + endpoint: env('CNPMCORE_NFS_S3_CLIENT_ENDPOINT', 'string', ''), credentials: { - accessKeyId: process.env.CNPMCORE_NFS_S3_CLIENT_ID, - secretAccessKey: process.env.CNPMCORE_NFS_S3_CLIENT_SECRET, + accessKeyId: env('CNPMCORE_NFS_S3_CLIENT_ID', 'string', ''), + secretAccessKey: env('CNPMCORE_NFS_S3_CLIENT_SECRET', 'string', ''), }, - bucket: process.env.CNPMCORE_NFS_S3_CLIENT_BUCKET, - forcePathStyle: !!process.env.CNPMCORE_NFS_S3_CLIENT_FORCE_PATH_STYLE, - disableURL: !!process.env.CNPMCORE_NFS_S3_CLIENT_DISABLE_URL, - }); + bucket: env('CNPMCORE_NFS_S3_CLIENT_BUCKET', 'string', ''), + forcePathStyle: env('CNPMCORE_NFS_S3_CLIENT_FORCE_PATH_STYLE', 'boolean', false), + disableURL: env('CNPMCORE_NFS_S3_CLIENT_DISABLE_URL', 'boolean', false), + }; + assert(s3Config.endpoint, 'require env CNPMCORE_NFS_S3_CLIENT_ENDPOINT'); + assert(s3Config.credentials.accessKeyId, 'require env CNPMCORE_NFS_S3_CLIENT_ID'); + assert(s3Config.credentials.secretAccessKey, 'require env CNPMCORE_NFS_S3_CLIENT_SECRET'); + assert(s3Config.bucket, 'require env CNPMCORE_NFS_S3_CLIENT_BUCKET'); + // TODO(@fengmk2): should change to use import to support esm + // eslint-disable-next-line @typescript-eslint/no-var-requires + const S3Client = require('s3-cnpmcore'); + config.nfs.client = new S3Client(s3Config); } config.logger = { enablePerformanceTimer: true, enableFastContextLogger: true, - appLogName: process.env.CNPMCORE_APP_LOG_NAME || `${appInfo.name}-web.log`, - coreLogName: process.env.CNPMCORE_CORE_LOG_NAME || 'egg-web.log', - agentLogName: process.env.CNPMCORE_AGENT_LOG_NAME || 'egg-agent.log', - errorLogName: process.env.CNPMCORE_ERROR_LOG_NAME || 'common-error.log', - outputJSON: Boolean(process.env.CNPMCORE_LOG_JSON_OUTPUT || false), + appLogName: env('CNPMCORE_APP_LOG_NAME', 'string', `${appInfo.name}-web.log`), + coreLogName: env('CNPMCORE_CORE_LOG_NAME', 'string', 'egg-web.log'), + agentLogName: env('CNPMCORE_AGENT_LOG_NAME', 'string', 'egg-agent.log'), + errorLogName: env('CNPMCORE_ERROR_LOG_NAME', 'string', 'common-error.log'), + outputJSON: env('CNPMCORE_LOG_JSON_OUTPUT', 'boolean', false), }; - if (process.env.CNPMCORE_LOG_DIR) { - config.logger.dir = process.env.CNPMCORE_LOG_DIR; - } - if (process.env.CNPMCORE_LOG_JSON_OUTPUT) { - config.logger.outputJSON = Boolean(process.env.CNPMCORE_LOG_JSON_OUTPUT); + const logDir = env('CNPMCORE_LOG_DIR', 'string', ''); + if (logDir) { + config.logger.dir = logDir; } config.logrotator = { @@ -207,10 +210,10 @@ export default (appInfo: EggAppConfig) => { if (config.cnpmcore.enableElasticsearch) { config.elasticsearch = { client: { - node: process.env.CNPMCORE_CONFIG_ES_CLIENT_NODE, + node: env('CNPMCORE_CONFIG_ES_CLIENT_NODE', 'string', ''), auth: { - username: process.env.CNPMCORE_CONFIG_ES_CLIENT_AUTH_USERNAME as string, - password: process.env.CNPMCORE_CONFIG_ES_CLIENT_AUTH_PASSWORD as string, + username: env('CNPMCORE_CONFIG_ES_CLIENT_AUTH_USERNAME', 'string', ''), + password: env('CNPMCORE_CONFIG_ES_CLIENT_AUTH_PASSWORD', 'string', ''), }, }, }; @@ -218,3 +221,4 @@ export default (appInfo: EggAppConfig) => { return config; }; + diff --git a/config/database.ts b/config/database.ts index bc6a61c9..2ae2e41b 100644 --- a/config/database.ts +++ b/config/database.ts @@ -1,33 +1,35 @@ +import { env } from "../app/common/EnvUtil"; + export enum DATABASE_TYPE { MySQL = 'MySQL', PostgreSQL = 'PostgreSQL', SQLite = 'SQLite', } -const dbType = process.env.CNPMCORE_DATABASE_TYPE ?? DATABASE_TYPE.MySQL; -let dbName = process.env.CNPMCORE_DATABASE_NAME; -let dbHost = process.env.CNPMCORE_DATABASE_HOST; -let dbPort = process.env.CNPMCORE_DATABASE_PORT; -let dbUser = process.env.CNPMCORE_DATABASE_USER; -let dbPassword = process.env.CNPMCORE_DATABASE_PASSWORD; +const dbType = env('CNPMCORE_DATABASE_TYPE', 'string', DATABASE_TYPE.MySQL); +let dbName = env('CNPMCORE_DATABASE_NAME', 'string', ''); +let dbHost = env('CNPMCORE_DATABASE_HOST', 'string', ''); +let dbPort = env('CNPMCORE_DATABASE_PORT', 'number', 0); +let dbUser = env('CNPMCORE_DATABASE_USER', 'string', ''); +let dbPassword = env('CNPMCORE_DATABASE_PASSWORD', 'string', ''); let dialect = 'mysql'; let dbClient = 'mysql2'; if (dbType === DATABASE_TYPE.MySQL) { // Compatible mysql configurations - dbName = dbName ?? process.env.CNPMCORE_MYSQL_DATABASE ?? process.env.MYSQL_DATABASE; - dbHost = dbHost ?? process.env.CNPMCORE_MYSQL_HOST ?? process.env.MYSQL_HOST ?? '127.0.0.1'; - dbPort = dbPort ?? process.env.CNPMCORE_MYSQL_PORT ?? process.env.MYSQL_PORT ?? '3306'; - dbUser = dbUser ?? process.env.CNPMCORE_MYSQL_USER ?? process.env.MYSQL_USER ?? 'root'; - dbPassword = dbPassword ?? process.env.CNPMCORE_MYSQL_PASSWORD ?? process.env.MYSQL_PASSWORD; + dbName = dbName || env('CNPMCORE_MYSQL_DATABASE', 'string', '') || env('MYSQL_DATABASE', 'string', '') || ''; + dbHost = dbHost || env('CNPMCORE_MYSQL_HOST', 'string', '') || env('MYSQL_HOST', 'string', '') || '127.0.0.1'; + dbPort = dbPort || env('CNPMCORE_MYSQL_PORT', 'number', 0) || env('MYSQL_PORT', 'number', 0) || 3306; + dbUser = dbUser || env('CNPMCORE_MYSQL_USER', 'string', '') || env('MYSQL_USER', 'string', '') || 'root'; + dbPassword = dbPassword || env('CNPMCORE_MYSQL_PASSWORD', 'string', '') || env('MYSQL_PASSWORD', 'string', ''); } else if (dbType === DATABASE_TYPE.PostgreSQL) { dbClient = 'pg'; dialect = 'postgres'; - dbHost = dbHost ?? process.env.CNPMCORE_POSTGRES_HOST ?? process.env.POSTGRES_HOST; - dbPort = dbPort ?? process.env.CNPMCORE_POSTGRES_PORT ?? process.env.POSTGRES_PORT ?? '5432'; - dbUser = dbUser ?? process.env.CNPMCORE_POSTGRES_USER ?? process.env.POSTGRES_USER; - dbPassword = dbPassword ?? process.env.CNPMCORE_POSTGRES_PASSWORD ?? process.env.POSTGRES_PASSWORD; + dbHost = dbHost || env('CNPMCORE_POSTGRES_HOST', 'string', '') || env('POSTGRES_HOST', 'string', '') || ''; + dbPort = dbPort || env('CNPMCORE_POSTGRES_PORT', 'number', 0) || env('POSTGRES_PORT', 'number', 0) || 5432; + dbUser = dbUser || env('CNPMCORE_POSTGRES_USER', 'string', '') || env('POSTGRES_USER', 'string', '') || ''; + dbPassword = dbPassword || env('CNPMCORE_POSTGRES_PASSWORD', 'string', '') || env('POSTGRES_PASSWORD', 'string', ''); } else if (dbType === DATABASE_TYPE.SQLite) { - // TODO + // TODO: Implement SQLite dbClient = 'sqlite'; dialect = 'sqlite'; }