commit 9e1881d06ed721a19accd7e9b871fd405f974b15 Author: xion Date: Sun Nov 17 16:32:56 2024 +0800 从@abearxiong/config拷贝过来 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d743a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +dist + +.DS_Store + +app.config.json5 + diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..a4d9caf --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN} +@abearxiong:registry=https://npm.pkg.github.com +//registry.npmjs.org/:_authToken=${NPM_TOKEN} \ No newline at end of file diff --git a/config/app-schema.json b/config/app-schema.json new file mode 100644 index 0000000..925e4cc --- /dev/null +++ b/config/app-schema.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "App Configuration Schema", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "inline-app", + "micro-app" + ], + "$comment": "Type must be either 'inline-app' or 'micro-app'" + }, + "single": { + "type": "boolean", + "$comment": "是否单例模式,独立启动服务。" + }, + "port": { + "type": "integer", + "minimum": 0, + "maximum": 65535, + "$comment": "开发和单例启动服务的端口" + }, + "remote": { + "type": "object", + "properties": { + "host": { + "type": "string", + "format": "hostname" + }, + "path": { + "type": "string", + "pattern": "^/.*" + } + }, + "required": [ + "host", + "path" + ] + }, + "micro": { + "type": "object", + "properties": { + "serve": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "$comment": "服务名称" + }, + "port": { + "type": "integer", + "minimum": 0, + "maximum": 65535, + "$comment": "启动的服务端口" + } + }, + "required": [] + }, + "remote": { + "type": "object", + "properties": { + "host": { + "type": "string", + "format": "hostname" + }, + "port": { + "type": "integer", + "minimum": 0, + "maximum": 65535, + "$comment": "链接的远程地址的端口" + } + }, + "required": [ + "host", + "port" + ] + } + }, + "required": [] + } + }, + "required": [ + "type" + ] +} \ No newline at end of file diff --git a/config/demo/inline-app.json b/config/demo/inline-app.json new file mode 100644 index 0000000..a20b829 --- /dev/null +++ b/config/demo/inline-app.json @@ -0,0 +1,15 @@ +{ + "$schema": "../schema.json", + "app": { + "type": "inline-app", + "port": 14000, + "remote": { + "host": "localhost", + "path": "/api/router" + }, + "micro": { + "name": "micro-app", + "port": 3001 + } + } +} \ No newline at end of file diff --git a/config/demo/micro-app.json b/config/demo/micro-app.json new file mode 100644 index 0000000..7b8d23d --- /dev/null +++ b/config/demo/micro-app.json @@ -0,0 +1,17 @@ +{ + "$schema": "../schema.json", + "app": { + "type": "micro-app", + "port": 14000, + "remote": { + "host": "localhost", + "path": "/api/router" + }, + "micro": { + "remote": { + "host": "localhost", + "port": 3001 + } + } + } +} \ No newline at end of file diff --git a/config/schema.json b/config/schema.json new file mode 100644 index 0000000..7010054 --- /dev/null +++ b/config/schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Packages JSON Schema", + "type": "object", + "properties": { + "app": { + "$ref": "./app-schema.json" + } + }, + "required": [ + "app" + ] +} \ No newline at end of file diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 0000000..b821b07 --- /dev/null +++ b/demo/package.json @@ -0,0 +1,16 @@ +{ + "name": "demo", + "version": "0.0.1", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "abearxiong ", + "license": "MIT", + "type": "module", + "dependencies": { + "@kevisual/user-config": "link:.." + } +} diff --git a/demo/src/app/a.ts b/demo/src/app/a.ts new file mode 100644 index 0000000..d6efe4e --- /dev/null +++ b/demo/src/app/a.ts @@ -0,0 +1,5 @@ +import { getApps } from '@kevisual/user-config/pkgs'; + +const apps = getApps(); + +// apps.micro \ No newline at end of file diff --git a/demo/src/index.ts b/demo/src/index.ts new file mode 100644 index 0000000..a1dec1b --- /dev/null +++ b/demo/src/index.ts @@ -0,0 +1,4 @@ +import { useConfig } from '@kevisual/user-config'; + +// console.log(useConfig); +console.log(useConfig()); diff --git a/package.json b/package.json new file mode 100644 index 0000000..8c4a699 --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "@kevisual/user-config", + "version": "1.0.1", + "types": "dist/config.d.ts", + "scripts": { + "build": "npm run clean && rollup -c", + "watch": " rollup -c -w", + "clean": "rimraf dist" + }, + "files": [ + "dist", + "src", + "config" + ], + "keywords": [], + "author": "abearxiong ", + "license": "UNLICENSED", + "type": "module", + "devDependencies": { + "@rollup/plugin-alias": "^5.1.1", + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^15.3.0", + "@rollup/plugin-replace": "^6.0.1", + "@rollup/plugin-typescript": "^12.1.1", + "@types/node": "^22.9.0", + "chalk": "^5.3.0", + "commander": "^12.1.0", + "glob": "^11.0.0", + "json-schema-to-ts": "^3.1.1", + "json5": "^2.2.3", + "rollup": "^4.26.0", + "rollup-plugin-copy": "^3.5.0", + "rollup-plugin-dts": "^6.1.1", + "rollup-plugin-esbuild": "^6.1.1", + "rollup-plugin-inject": "^3.0.2", + "tslib": "^2.8.1", + "typescript": "^5.6.2" + }, + "exports": { + ".": { + "import": "./dist/config.mjs", + "types": "./dist/config.d.ts" + }, + "./pkgs": { + "import": "./dist/pkgs.mjs", + "types": "./dist/pkgs.d.ts" + } + } +} \ No newline at end of file diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 0000000..8f2566e --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,52 @@ +// rollup.config.js +import typescript from '@rollup/plugin-typescript'; +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import { dts } from 'rollup-plugin-dts'; +/** + * @type {import('rollup').RollupOptions} + */ +export default [ + { + input: 'src/config.ts', // TypeScript 入口文件 + output: { + file: 'dist/config.mjs', // 输出文件 + format: 'es', // 输出格式设置为 ES 模块 + }, + plugins: [ + resolve(), // 使用 @rollup/plugin-node-resolve 解析 node_modules 中的模块 + commonjs(), + typescript(), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件 + ], + external: [], + }, + { + input: 'src/config.ts', + output: { + file: 'dist/config.d.ts', + format: 'es', + }, + plugins: [dts()], + }, + { + input: 'src/pkgs.ts', // TypeScript 入口文件 + output: { + file: 'dist/pkgs.mjs', // 输出文件 + format: 'es', // 输出格式设置为 ES 模块 + }, + plugins: [ + resolve(), // 使用 @rollup/plugin-node-resolve 解析 node_modules 中的模块 + commonjs(), + typescript(), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件 + ], + external: [], + }, + { + input: 'src/pkgs.ts', + output: { + file: 'dist/pkgs.d.ts', + format: 'es', + }, + plugins: [dts()], + }, +]; diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..415bd39 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,139 @@ +import JSON5 from 'json5'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); // 当前模块的文件路径 +const __dirname = path.dirname(__filename); // 当前模块的目录路径 + +// 配置类型 +export type Config = { + port: number; + tokenSecret?: string; + redis?: { + host?: string; + port?: number; + password?: string; + version?: string; + [key: string]: any; + }; + posgtres?: { + host?: string; + port?: number; + user?: string; + password?: string; + database?: string; + [key: string]: any; + }; + minio?: { + endPoint?: string; + bucketName?: string; + useSSL?: boolean; + accessKey?: string; + secretKey?: string; + [key: string]: any; + }; + mongo?: { + uri?: string; + [key: string]: any; + }; + [key: string]: any; +}; +export const initConfig: Config = { + port: 3000, +}; +export const fileIsExist = (path: string) => { + try { + fs.accessSync(path, fs.constants.F_OK); + return true; + } catch (e) { + return false; + } +}; +export const getDirname = () => { + const isDev = getNODE_ENV() === 'development'; + if (isDev) { + return path.resolve(); + } + return __dirname; +}; +export const getConfigFile = (fileName = 'app.config.json5') => { + const dirname = getDirname(); + // 本级 + const benPath = dirname + '/' + fileName; + const ben = fileIsExist(benPath); + if (ben) return benPath; + // 上级 + const lastPath = path.join(dirname, '../' + fileName); + const last = fileIsExist(lastPath); + if (last) return lastPath; + // 上上级 + const lastLastPath = path.join(dirname, '../../' + fileName); + const lastLast = fileIsExist(lastLastPath); + if (lastLast) return lastLastPath; + return ''; +}; +// 初始化读取配置文件 +export const init = (initConfigBase?: any): Config => { + const dirname = getDirname(); + try { + // 配置读取路径,3级判断 + const filePath = getConfigFile(); + console.log('config pathname:', filePath); + if (!filePath) { + throw new Error('未找到配置文件'); + } + const configString = fs.readFileSync(filePath, { + encoding: 'utf-8', + }); + const value = JSON5.parse(configString); + return value; + } catch (e) { + const root = dirname + '/app.config.json5'; + if (!fileIsExist(root)) { + fs.writeFileSync(root, JSON5.stringify(initConfigBase || initConfig, null, 2), { + encoding: 'utf8', + }); + } + console.error('未找到配置文件,初始化配置', root); + // console.error('error', e); + return initConfig; + } +}; + +/** + * 从全局获取 + * @param initConfig 在全局未找到配置时,初始化配置的内容 + * + * @returns Config + */ +export const useConfig = (initConfig?: any): Config & T => { + const config = (global as any).config; + const _config = config || init(initConfig); + !config && ((global as any)['config'] = _config); + return _config; +}; + +export const useContext = (key: string, value: any): any => { + const _context = global as any; + if (key && value) { + _context[key] = value; + return _context; + } + if (key) { + return _context[key]; + } + return _context; +}; +export const deleteContext = (key: string): any => { + const _context = global as any; + if (key && _context[key]) { + delete _context[key]; + return _context; + } + return _context; +}; + +export const getNODE_ENV = (): string => { + return process?.env?.NODE_ENV || 'production'; +}; diff --git a/src/pkgs.ts b/src/pkgs.ts new file mode 100644 index 0000000..585a023 --- /dev/null +++ b/src/pkgs.ts @@ -0,0 +1,23 @@ +import path from 'path'; +import fs from 'fs'; +import type { App } from './read-app-schema.ts'; +import { fileURLToPath } from 'url'; +import { getConfigFile } from './config.ts'; + +const __filename = fileURLToPath(import.meta.url); // 当前模块的文件路径 +const __dirname = path.dirname(__filename); // 当前模块的目录路径 + +export const getPkgs = () => { + const configFile = getConfigFile(); + if (!configFile) { + console.error('配置文件不存在'); + return {}; + } + const config = JSON.parse(fs.readFileSync(configFile, 'utf-8')); + return config; +}; + +export const getApps = (): App => { + const config = getPkgs(); + return config.apps || {}; +}; diff --git a/src/read-app-schema.ts b/src/read-app-schema.ts new file mode 100644 index 0000000..786fe30 --- /dev/null +++ b/src/read-app-schema.ts @@ -0,0 +1,79 @@ +import { FromSchema } from 'json-schema-to-ts'; +const App = { + $schema: 'http://json-schema.org/draft-07/schema#', + title: 'App Configuration Schema', + type: 'object', + properties: { + type: { + type: 'string', + enum: ['inline-app', 'micro-app'], + $comment: "Type must be either 'inline-app' or 'micro-app'", + }, + single: { + type: 'boolean', + $comment: '是否单例模式,独立启动服务。', + }, + port: { + type: 'integer', + minimum: 0, + maximum: 65535, + $comment: '开发和单例启动服务的端口', + }, + remote: { + type: 'object', + properties: { + host: { + type: 'string', + format: 'hostname', + }, + path: { + type: 'string', + pattern: '^/.*', + }, + }, + required: ['host', 'path'], + }, + micro: { + type: 'object', + properties: { + serve: { + type: 'object', + properties: { + name: { + type: 'string', + pattern: '^[a-z0-9-]+$', + $comment: '服务名称', + }, + port: { + type: 'integer', + minimum: 0, + maximum: 65535, + $comment: '启动的服务端口', + }, + }, + required: [], + }, + remote: { + type: 'object', + properties: { + host: { + type: 'string', + format: 'hostname', + }, + port: { + type: 'integer', + minimum: 0, + maximum: 65535, + $comment: '链接的远程地址的端口', + }, + }, + required: ['host', 'port'], + }, + }, + required: [], + }, + }, + required: ['type'], +} as const; + +export type App = FromSchema; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..918f972 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "module": "NodeNext", + "target": "esnext", + "noImplicitAny": false, + "outDir": "./dist", + "sourceMap": false, + "allowJs": true, + "newLine": "LF", + "baseUrl": "./", + "typeRoots": [ + "node_modules/@types", + ], + "declaration": false, + "noEmit": true, + "allowImportingTsExtensions": true, + "moduleResolution": "NodeNext", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "paths": { + "@/*": [ + "src/*" + ], + } + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules", + "rollup.config.js", + ] +} \ No newline at end of file