Skip to content

Commit 9f4f1f1

Browse files
authored
feat: allow to set sourceRegistry by CNPMCORE_CONFIG_SOURCE_REGISTRY (#753)
Improve the local development process based on docker-compose <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a centralized configuration utility that validates environment variable types and provides fallback defaults. - **Refactor** - Standardized environment variable handling across configuration files, improving maintainability and consistency in system setup. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent aba2b36 commit 9f4f1f1

11 files changed

+253
-97
lines changed

.env.example

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# CNPMCORE_DATABASE_TYPE=MySQL
2+
# CNPMCORE_DATABASE_USER=root
3+
# CNPMCORE_DATABASE_PASSWORD=
4+
# CNPMCORE_DATABASE_NAME=cnpmcore
5+
6+
# CNPMCORE_DATABASE_TYPE=PostgreSQL
7+
# CNPMCORE_DATABASE_USER=postgres
8+
# CNPMCORE_DATABASE_PASSWORD=postgres
9+
# CNPMCORE_DATABASE_NAME=cnpmcore

DEVELOPER.md

+41-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,32 @@
44

55
本项目的外部服务依赖有:MySQL 数据库或 PostgreSQL 数据库、Redis 缓存服务。
66

7+
生成本地开发环境配置文件:
8+
9+
```bash
10+
cp .env.example .env
11+
```
12+
713
可以通过 Docker 来快速启动本地开发环境:
814

15+
MySQL 开发环境:
16+
17+
```bash
18+
# 启动本地依赖服务 - MySQL + Redis
19+
docker-compose -f docker-compose.yml up -d
20+
21+
# 关闭本地依赖服务
22+
docker-compose -f docker-compose.yml down
23+
```
24+
25+
PostgreSQL 开发环境:
26+
927
```bash
10-
# 启动本地依赖服务
11-
docker-compose up -d
28+
# 启动本地依赖服务 - PostgreSQL + Redis
29+
docker-compose -f docker-compose-postgres.yml up -d
1230

1331
# 关闭本地依赖服务
14-
docker-compose down
32+
docker-compose -f docker-compose-postgres.yml down
1533
```
1634

1735
> 手动初始化依赖服务参见[本地开发环境 - MySQL](./docs/setup.md)[本地开发环境 - PostgreSQL](./docs/setup-with-postgresql.md)
@@ -35,6 +53,9 @@ npm run dev
3553

3654
# 访问
3755
curl -v http://127.0.0.1:7001
56+
57+
# cnpmcore_admin 注册管理员
58+
npm login --registry=http://127.0.0.1:7001
3859
```
3960

4061
### 开发运行 - PostgreSQL
@@ -50,6 +71,23 @@ npm run dev:postgresql
5071
curl -v http://127.0.0.1:7001
5172
```
5273

74+
### 登录和测试发包
75+
76+
注册 cnpmcore_admin 管理员
77+
78+
```bash
79+
npm login --registry=http://127.0.0.1:7001
80+
81+
# 验证登录
82+
npm whoami --registry=http://127.0.0.1:7001
83+
```
84+
85+
发包
86+
87+
```bash
88+
npm publish --registry=http://127.0.0.1:7001
89+
```
90+
5391
### 单元测试
5492

5593
MySQL

app/common/EnvUtil.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export type ValueType = 'string' | 'boolean' | 'number';
2+
3+
export function env(key: string, valueType: ValueType, defaultValue: string): string;
4+
export function env(key: string, valueType: ValueType, defaultValue: boolean): boolean;
5+
export function env(key: string, valueType: ValueType, defaultValue: number): number;
6+
export function env(key: string, valueType: ValueType, defaultValue: string | boolean | number): string | boolean | number {
7+
const value = process.env[key];
8+
if (value === undefined) {
9+
return defaultValue;
10+
}
11+
12+
if (valueType === 'string') {
13+
return value;
14+
}
15+
16+
if (valueType === 'boolean') {
17+
let booleanValue = false;
18+
if (value === 'true' || value === '1') {
19+
booleanValue = true;
20+
} else if (value === 'false' || value === '0') {
21+
booleanValue = false;
22+
} else {
23+
throw new TypeError(`Invalid boolean value: ${value} on process.env.${key}`);
24+
}
25+
return booleanValue;
26+
}
27+
28+
if (valueType === 'number') {
29+
const numberValue = Number(value);
30+
if (isNaN(numberValue)) {
31+
throw new TypeError(`Invalid number value: ${value} on process.env.${key}`);
32+
}
33+
return numberValue;
34+
}
35+
36+
throw new TypeError(`Invalid value type: ${valueType}`);
37+
}

config/config.default.ts

+60-56
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import { strict as assert } from 'node:assert';
22
import { randomUUID } from 'node:crypto';
33
import { join } from 'node:path';
4-
import { EggAppConfig, PowerPartial } from 'egg';
4+
import { EggAppConfig, PowerPartial, Context } from 'egg';
55
import OSSClient from 'oss-cnpm';
66
import { patchAjv } from '../app/port/typebox';
77
import { ChangesStreamMode, NOT_IMPLEMENTED_PATH, SyncDeleteMode, SyncMode } from '../app/common/constants';
8+
import { env } from '../app/common/EnvUtil';
89
import type { CnpmcoreConfig } from '../app/port/config';
910
import { database } from './database';
1011

1112
export const cnpmcoreConfig: CnpmcoreConfig = {
1213
name: 'cnpm',
1314
hookEnable: false,
1415
hooksLimit: 20,
15-
sourceRegistry: 'https://registry.npmjs.org',
16-
sourceRegistryIsCNpm: false,
16+
sourceRegistry: env('CNPMCORE_CONFIG_SOURCE_REGISTRY', 'string', 'https://registry.npmjs.org'),
17+
sourceRegistryIsCNpm: env('CNPMCORE_CONFIG_SOURCE_REGISTRY_IS_CNPM', 'boolean', false),
1718
syncUpstreamFirst: false,
1819
sourceRegistrySyncTimeout: 180000,
1920
taskQueueHighWaterSize: 100,
@@ -33,7 +34,7 @@ export const cnpmcoreConfig: CnpmcoreConfig = {
3334
checkChangesStreamInterval: 500,
3435
changesStreamRegistry: 'https://replicate.npmjs.com',
3536
changesStreamRegistryMode: ChangesStreamMode.streaming,
36-
registry: process.env.CNPMCORE_CONFIG_REGISTRY || 'http://localhost:7001',
37+
registry: env('CNPMCORE_CONFIG_REGISTRY', 'string', 'http://localhost:7001'),
3738
alwaysAuth: false,
3839
allowScopes: [
3940
'@cnpm',
@@ -45,7 +46,7 @@ export const cnpmcoreConfig: CnpmcoreConfig = {
4546
admins: {
4647
cnpmcore_admin: '[email protected]',
4748
},
48-
enableWebAuthn: !!process.env.CNPMCORE_CONFIG_ENABLE_WEB_AUTHN,
49+
enableWebAuthn: env('CNPMCORE_CONFIG_ENABLE_WEB_AUTHN', 'boolean', false),
4950
enableCDN: false,
5051
cdnCacheControlHeader: 'public, max-age=300',
5152
cdnVaryHeader: 'Accept, Accept-Encoding',
@@ -57,7 +58,7 @@ export const cnpmcoreConfig: CnpmcoreConfig = {
5758
enableSyncUnpkgFiles: true,
5859
enableSyncUnpkgFilesWhiteList: false,
5960
strictSyncSpecivicVersion: false,
60-
enableElasticsearch: !!process.env.CNPMCORE_CONFIG_ENABLE_ES,
61+
enableElasticsearch: env('CNPMCORE_CONFIG_ENABLE_ES', 'boolean', false),
6162
elasticsearchIndex: 'cnpmcore_packages',
6263
strictValidateTarballPkg: false,
6364
strictValidatePackageDeps: false,
@@ -69,14 +70,14 @@ export const cnpmcoreConfig: CnpmcoreConfig = {
6970
export default (appInfo: EggAppConfig) => {
7071
const config = {} as PowerPartial<EggAppConfig>;
7172

72-
config.keys = process.env.CNPMCORE_EGG_KEYS || randomUUID();
73+
config.keys = env('CNPMCORE_EGG_KEYS', 'string', randomUUID());
7374
config.cnpmcore = cnpmcoreConfig;
7475

7576
// override config from framework / plugin
76-
config.dataDir = process.env.CNPMCORE_DATA_DIR || join(appInfo.root, '.cnpmcore');
77+
config.dataDir = env('CNPMCORE_DATA_DIR', 'string', join(appInfo.root, '.cnpmcore'));
7778
config.orm = {
7879
...database,
79-
database: database.name ?? 'cnpmcore',
80+
database: database.name || 'cnpmcore',
8081
charset: 'utf8mb4',
8182
logger: {
8283
// https://github.com/cyjake/leoric/blob/master/docs/zh/logging.md#logqueryerror
@@ -90,10 +91,10 @@ export default (appInfo: EggAppConfig) => {
9091

9192
config.redis = {
9293
client: {
93-
port: Number(process.env.CNPMCORE_REDIS_PORT || 6379),
94-
host: process.env.CNPMCORE_REDIS_HOST || '127.0.0.1',
95-
password: process.env.CNPMCORE_REDIS_PASSWORD || '',
96-
db: Number(process.env.CNPMCORE_REDIS_DB || 0),
94+
port: env('CNPMCORE_REDIS_PORT', 'number', 6379),
95+
host: env('CNPMCORE_REDIS_HOST', 'string', '127.0.0.1'),
96+
password: env('CNPMCORE_REDIS_PASSWORD', 'string', ''),
97+
db: env('CNPMCORE_REDIS_DB', 'number', 0),
9798
},
9899
};
99100

@@ -105,7 +106,7 @@ export default (appInfo: EggAppConfig) => {
105106

106107
config.cors = {
107108
// allow all domains
108-
origin: (ctx): string => {
109+
origin: (ctx: Context): string => {
109110
return ctx.get('Origin');
110111
},
111112
credentials: true,
@@ -115,59 +116,61 @@ export default (appInfo: EggAppConfig) => {
115116

116117
config.nfs = {
117118
client: null,
118-
dir: process.env.CNPMCORE_NFS_DIR || join(config.dataDir, 'nfs'),
119+
dir: env('CNPMCORE_NFS_DIR', 'string', join(config.dataDir, 'nfs')),
119120
};
120121
/* c8 ignore next 17 */
121122
// enable oss nfs store by env values
122-
if (process.env.CNPMCORE_NFS_TYPE === 'oss') {
123-
assert(process.env.CNPMCORE_NFS_OSS_BUCKET, 'require env CNPMCORE_NFS_OSS_BUCKET');
124-
assert(process.env.CNPMCORE_NFS_OSS_ENDPOINT, 'require env CNPMCORE_NFS_OSS_ENDPOINT');
125-
assert(process.env.CNPMCORE_NFS_OSS_ID, 'require env CNPMCORE_NFS_OSS_ID');
126-
assert(process.env.CNPMCORE_NFS_OSS_SECRET, 'require env CNPMCORE_NFS_OSS_SECRET');
127-
config.nfs.client = new OSSClient({
128-
cdnBaseUrl: process.env.CNPMCORE_NFS_OSS_CDN,
129-
endpoint: process.env.CNPMCORE_NFS_OSS_ENDPOINT,
130-
bucket: process.env.CNPMCORE_NFS_OSS_BUCKET,
131-
accessKeyId: process.env.CNPMCORE_NFS_OSS_ID,
132-
accessKeySecret: process.env.CNPMCORE_NFS_OSS_SECRET,
123+
const nfsType = env('CNPMCORE_NFS_TYPE', 'string', '');
124+
if (nfsType === 'oss') {
125+
const ossConfig = {
126+
cdnBaseUrl: env('CNPMCORE_NFS_OSS_CDN', 'string', ''),
127+
endpoint: env('CNPMCORE_NFS_OSS_ENDPOINT', 'string', ''),
128+
bucket: env('CNPMCORE_NFS_OSS_BUCKET', 'string', ''),
129+
accessKeyId: env('CNPMCORE_NFS_OSS_ID', 'string', ''),
130+
accessKeySecret: env('CNPMCORE_NFS_OSS_SECRET', 'string', ''),
133131
defaultHeaders: {
134132
'Cache-Control': 'max-age=0, s-maxage=60',
135133
},
136-
});
137-
} else if (process.env.CNPMCORE_NFS_TYPE === 's3') {
138-
assert(process.env.CNPMCORE_NFS_S3_CLIENT_ENDPOINT, 'require env CNPMCORE_NFS_S3_CLIENT_ENDPOINT');
139-
assert(process.env.CNPMCORE_NFS_S3_CLIENT_ID, 'require env CNPMCORE_NFS_S3_CLIENT_ID');
140-
assert(process.env.CNPMCORE_NFS_S3_CLIENT_SECRET, 'require env CNPMCORE_NFS_S3_CLIENT_SECRET');
141-
assert(process.env.CNPMCORE_NFS_S3_CLIENT_BUCKET, 'require env CNPMCORE_NFS_S3_CLIENT_BUCKET');
142-
// eslint-disable-next-line @typescript-eslint/no-var-requires
143-
const S3Client = require('s3-cnpmcore');
144-
config.nfs.client = new S3Client({
145-
region: process.env.CNPMCORE_NFS_S3_CLIENT_REGION || 'default',
146-
endpoint: process.env.CNPMCORE_NFS_S3_CLIENT_ENDPOINT,
134+
};
135+
assert(ossConfig.cdnBaseUrl, 'require env CNPMCORE_NFS_OSS_BUCKET');
136+
assert(ossConfig.endpoint, 'require env CNPMCORE_NFS_OSS_ENDPOINT');
137+
assert(ossConfig.accessKeyId, 'require env CNPMCORE_NFS_OSS_ID');
138+
assert(ossConfig.accessKeySecret, 'require env CNPMCORE_NFS_OSS_SECRET');
139+
config.nfs.client = new OSSClient(ossConfig);
140+
} else if (nfsType === 's3') {
141+
const s3Config = {
142+
region: env('CNPMCORE_NFS_S3_CLIENT_REGION', 'string', 'default'),
143+
endpoint: env('CNPMCORE_NFS_S3_CLIENT_ENDPOINT', 'string', ''),
147144
credentials: {
148-
accessKeyId: process.env.CNPMCORE_NFS_S3_CLIENT_ID,
149-
secretAccessKey: process.env.CNPMCORE_NFS_S3_CLIENT_SECRET,
145+
accessKeyId: env('CNPMCORE_NFS_S3_CLIENT_ID', 'string', ''),
146+
secretAccessKey: env('CNPMCORE_NFS_S3_CLIENT_SECRET', 'string', ''),
150147
},
151-
bucket: process.env.CNPMCORE_NFS_S3_CLIENT_BUCKET,
152-
forcePathStyle: !!process.env.CNPMCORE_NFS_S3_CLIENT_FORCE_PATH_STYLE,
153-
disableURL: !!process.env.CNPMCORE_NFS_S3_CLIENT_DISABLE_URL,
154-
});
148+
bucket: env('CNPMCORE_NFS_S3_CLIENT_BUCKET', 'string', ''),
149+
forcePathStyle: env('CNPMCORE_NFS_S3_CLIENT_FORCE_PATH_STYLE', 'boolean', false),
150+
disableURL: env('CNPMCORE_NFS_S3_CLIENT_DISABLE_URL', 'boolean', false),
151+
};
152+
assert(s3Config.endpoint, 'require env CNPMCORE_NFS_S3_CLIENT_ENDPOINT');
153+
assert(s3Config.credentials.accessKeyId, 'require env CNPMCORE_NFS_S3_CLIENT_ID');
154+
assert(s3Config.credentials.secretAccessKey, 'require env CNPMCORE_NFS_S3_CLIENT_SECRET');
155+
assert(s3Config.bucket, 'require env CNPMCORE_NFS_S3_CLIENT_BUCKET');
156+
// TODO(@fengmk2): should change to use import to support esm
157+
// eslint-disable-next-line @typescript-eslint/no-var-requires
158+
const S3Client = require('s3-cnpmcore');
159+
config.nfs.client = new S3Client(s3Config);
155160
}
156161

157162
config.logger = {
158163
enablePerformanceTimer: true,
159164
enableFastContextLogger: true,
160-
appLogName: process.env.CNPMCORE_APP_LOG_NAME || `${appInfo.name}-web.log`,
161-
coreLogName: process.env.CNPMCORE_CORE_LOG_NAME || 'egg-web.log',
162-
agentLogName: process.env.CNPMCORE_AGENT_LOG_NAME || 'egg-agent.log',
163-
errorLogName: process.env.CNPMCORE_ERROR_LOG_NAME || 'common-error.log',
164-
outputJSON: Boolean(process.env.CNPMCORE_LOG_JSON_OUTPUT || false),
165+
appLogName: env('CNPMCORE_APP_LOG_NAME', 'string', `${appInfo.name}-web.log`),
166+
coreLogName: env('CNPMCORE_CORE_LOG_NAME', 'string', 'egg-web.log'),
167+
agentLogName: env('CNPMCORE_AGENT_LOG_NAME', 'string', 'egg-agent.log'),
168+
errorLogName: env('CNPMCORE_ERROR_LOG_NAME', 'string', 'common-error.log'),
169+
outputJSON: env('CNPMCORE_LOG_JSON_OUTPUT', 'boolean', false),
165170
};
166-
if (process.env.CNPMCORE_LOG_DIR) {
167-
config.logger.dir = process.env.CNPMCORE_LOG_DIR;
168-
}
169-
if (process.env.CNPMCORE_LOG_JSON_OUTPUT) {
170-
config.logger.outputJSON = Boolean(process.env.CNPMCORE_LOG_JSON_OUTPUT);
171+
const logDir = env('CNPMCORE_LOG_DIR', 'string', '');
172+
if (logDir) {
173+
config.logger.dir = logDir;
171174
}
172175

173176
config.logrotator = {
@@ -207,14 +210,15 @@ export default (appInfo: EggAppConfig) => {
207210
if (config.cnpmcore.enableElasticsearch) {
208211
config.elasticsearch = {
209212
client: {
210-
node: process.env.CNPMCORE_CONFIG_ES_CLIENT_NODE,
213+
node: env('CNPMCORE_CONFIG_ES_CLIENT_NODE', 'string', ''),
211214
auth: {
212-
username: process.env.CNPMCORE_CONFIG_ES_CLIENT_AUTH_USERNAME as string,
213-
password: process.env.CNPMCORE_CONFIG_ES_CLIENT_AUTH_PASSWORD as string,
215+
username: env('CNPMCORE_CONFIG_ES_CLIENT_AUTH_USERNAME', 'string', ''),
216+
password: env('CNPMCORE_CONFIG_ES_CLIENT_AUTH_PASSWORD', 'string', ''),
214217
},
215218
},
216219
};
217220
}
218221

219222
return config;
220223
};
224+

config/config.unittest.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default (appInfo: EggAppConfig) => {
1010
config.dataDir = join(appInfo.root, '.cnpmcore_unittest');
1111

1212
config.orm = {
13-
database: database.name ?? 'cnpmcore_unittest',
13+
database: database.name || 'cnpmcore_unittest',
1414
};
1515

1616
config.nfs = {

config/database.ts

+18-16
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,35 @@
1+
import { env } from '../app/common/EnvUtil';
2+
13
export enum DATABASE_TYPE {
24
MySQL = 'MySQL',
35
PostgreSQL = 'PostgreSQL',
46
SQLite = 'SQLite',
57
}
68

7-
const dbType = process.env.CNPMCORE_DATABASE_TYPE ?? DATABASE_TYPE.MySQL;
8-
let dbName = process.env.CNPMCORE_DATABASE_NAME;
9-
let dbHost = process.env.CNPMCORE_DATABASE_HOST;
10-
let dbPort = process.env.CNPMCORE_DATABASE_PORT;
11-
let dbUser = process.env.CNPMCORE_DATABASE_USER;
12-
let dbPassword = process.env.CNPMCORE_DATABASE_PASSWORD;
9+
const dbType = env('CNPMCORE_DATABASE_TYPE', 'string', DATABASE_TYPE.MySQL);
10+
let dbName = env('CNPMCORE_DATABASE_NAME', 'string', '');
11+
let dbHost = env('CNPMCORE_DATABASE_HOST', 'string', '');
12+
let dbPort = env('CNPMCORE_DATABASE_PORT', 'number', 0);
13+
let dbUser = env('CNPMCORE_DATABASE_USER', 'string', '');
14+
let dbPassword = env('CNPMCORE_DATABASE_PASSWORD', 'string', '');
1315
let dialect = 'mysql';
1416
let dbClient = 'mysql2';
1517
if (dbType === DATABASE_TYPE.MySQL) {
1618
// Compatible mysql configurations
17-
dbName = dbName ?? process.env.CNPMCORE_MYSQL_DATABASE ?? process.env.MYSQL_DATABASE;
18-
dbHost = dbHost ?? process.env.CNPMCORE_MYSQL_HOST ?? process.env.MYSQL_HOST ?? '127.0.0.1';
19-
dbPort = dbPort ?? process.env.CNPMCORE_MYSQL_PORT ?? process.env.MYSQL_PORT ?? '3306';
20-
dbUser = dbUser ?? process.env.CNPMCORE_MYSQL_USER ?? process.env.MYSQL_USER ?? 'root';
21-
dbPassword = dbPassword ?? process.env.CNPMCORE_MYSQL_PASSWORD ?? process.env.MYSQL_PASSWORD;
19+
dbName = dbName || env('CNPMCORE_MYSQL_DATABASE', 'string', '') || env('MYSQL_DATABASE', 'string', '') || '';
20+
dbHost = dbHost || env('CNPMCORE_MYSQL_HOST', 'string', '') || env('MYSQL_HOST', 'string', '') || '127.0.0.1';
21+
dbPort = dbPort || env('CNPMCORE_MYSQL_PORT', 'number', 0) || env('MYSQL_PORT', 'number', 0) || 3306;
22+
dbUser = dbUser || env('CNPMCORE_MYSQL_USER', 'string', '') || env('MYSQL_USER', 'string', '') || 'root';
23+
dbPassword = dbPassword || env('CNPMCORE_MYSQL_PASSWORD', 'string', '') || env('MYSQL_PASSWORD', 'string', '');
2224
} else if (dbType === DATABASE_TYPE.PostgreSQL) {
2325
dbClient = 'pg';
2426
dialect = 'postgres';
25-
dbHost = dbHost ?? process.env.CNPMCORE_POSTGRES_HOST ?? process.env.POSTGRES_HOST;
26-
dbPort = dbPort ?? process.env.CNPMCORE_POSTGRES_PORT ?? process.env.POSTGRES_PORT ?? '5432';
27-
dbUser = dbUser ?? process.env.CNPMCORE_POSTGRES_USER ?? process.env.POSTGRES_USER;
28-
dbPassword = dbPassword ?? process.env.CNPMCORE_POSTGRES_PASSWORD ?? process.env.POSTGRES_PASSWORD;
27+
dbHost = dbHost || env('CNPMCORE_POSTGRES_HOST', 'string', '') || env('POSTGRES_HOST', 'string', '') || '127.0.0.1';
28+
dbPort = dbPort || env('CNPMCORE_POSTGRES_PORT', 'number', 0) || env('POSTGRES_PORT', 'number', 0) || 5432;
29+
dbUser = dbUser || env('CNPMCORE_POSTGRES_USER', 'string', '') || env('POSTGRES_USER', 'string', '') || 'postgres';
30+
dbPassword = dbPassword || env('CNPMCORE_POSTGRES_PASSWORD', 'string', '') || env('POSTGRES_PASSWORD', 'string', '') || 'postgres';
2931
} else if (dbType === DATABASE_TYPE.SQLite) {
30-
// TODO
32+
// TODO: Implement SQLite
3133
dbClient = 'sqlite';
3234
dialect = 'sqlite';
3335
}

0 commit comments

Comments
 (0)