Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow to set sourceRegistry by CNPMCORE_CONFIG_SOURCE_REGISTRY #753

Merged
merged 7 commits into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# CNPMCORE_DATABASE_TYPE=MySQL
# CNPMCORE_DATABASE_USER=root
# CNPMCORE_DATABASE_PASSWORD=
# CNPMCORE_DATABASE_NAME=cnpmcore

# CNPMCORE_DATABASE_TYPE=PostgreSQL
# CNPMCORE_DATABASE_USER=postgres
# CNPMCORE_DATABASE_PASSWORD=postgres
# CNPMCORE_DATABASE_NAME=cnpmcore
44 changes: 41 additions & 3 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,32 @@

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

生成本地开发环境配置文件:

```bash
cp .env.example .env
```

可以通过 Docker 来快速启动本地开发环境:

MySQL 开发环境:

```bash
# 启动本地依赖服务 - MySQL + Redis
docker-compose -f docker-compose.yml up -d

# 关闭本地依赖服务
docker-compose -f docker-compose.yml down
```

PostgreSQL 开发环境:

```bash
# 启动本地依赖服务
docker-compose up -d
# 启动本地依赖服务 - PostgreSQL + Redis
docker-compose -f docker-compose-postgres.yml up -d

# 关闭本地依赖服务
docker-compose down
docker-compose -f docker-compose-postgres.yml down
```

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

# 访问
curl -v http://127.0.0.1:7001

# cnpmcore_admin 注册管理员
npm login --registry=http://127.0.0.1:7001
```

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

### 登录和测试发包

注册 cnpmcore_admin 管理员

```bash
npm login --registry=http://127.0.0.1:7001

# 验证登录
npm whoami --registry=http://127.0.0.1:7001
```

发包

```bash
npm publish --registry=http://127.0.0.1:7001
```

### 单元测试

MySQL
Expand Down
37 changes: 37 additions & 0 deletions app/common/EnvUtil.ts
Original file line number Diff line number Diff line change
@@ -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;
}

Check warning on line 26 in app/common/EnvUtil.ts

View check run for this annotation

Codecov / codecov/patch

app/common/EnvUtil.ts#L17-L26

Added lines #L17 - L26 were not covered by tests

if (valueType === 'number') {
const numberValue = Number(value);
if (isNaN(numberValue)) {
throw new TypeError(`Invalid number value: ${value} on process.env.${key}`);
}

Check warning on line 32 in app/common/EnvUtil.ts

View check run for this annotation

Codecov / codecov/patch

app/common/EnvUtil.ts#L31-L32

Added lines #L31 - L32 were not covered by tests
return numberValue;
}

throw new TypeError(`Invalid value type: ${valueType}`);
}

Check warning on line 37 in app/common/EnvUtil.ts

View check run for this annotation

Codecov / codecov/patch

app/common/EnvUtil.ts#L36-L37

Added lines #L36 - L37 were not covered by tests
116 changes: 60 additions & 56 deletions config/config.default.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
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';

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,
Expand All @@ -33,7 +34,7 @@
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',
Expand All @@ -45,7 +46,7 @@
admins: {
cnpmcore_admin: '[email protected]',
},
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',
Expand All @@ -57,7 +58,7 @@
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,
Expand All @@ -69,14 +70,14 @@
export default (appInfo: EggAppConfig) => {
const config = {} as PowerPartial<EggAppConfig>;

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',
database: database.name || 'cnpmcore',
charset: 'utf8mb4',
logger: {
// https://github.com/cyjake/leoric/blob/master/docs/zh/logging.md#logqueryerror
Expand All @@ -90,10 +91,10 @@

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),
},
};

Expand All @@ -105,7 +106,7 @@

config.cors = {
// allow all domains
origin: (ctx): string => {
origin: (ctx: Context): string => {
return ctx.get('Origin');
},
credentials: true,
Expand All @@ -115,59 +116,61 @@

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);

Check warning on line 139 in config/config.default.ts

View check run for this annotation

Codecov / codecov/patch

config/config.default.ts#L139

Added line #L139 was not covered by tests
} else if (nfsType === 's3') {
const s3Config = {
region: env('CNPMCORE_NFS_S3_CLIENT_REGION', 'string', 'default'),
endpoint: env('CNPMCORE_NFS_S3_CLIENT_ENDPOINT', 'string', ''),

Check warning on line 143 in config/config.default.ts

View check run for this annotation

Codecov / codecov/patch

config/config.default.ts#L141-L143

Added lines #L141 - L143 were not covered by tests
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', ''),

Check warning on line 146 in config/config.default.ts

View check run for this annotation

Codecov / codecov/patch

config/config.default.ts#L145-L146

Added lines #L145 - L146 were not covered by tests
},
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);

Check warning on line 159 in config/config.default.ts

View check run for this annotation

Codecov / codecov/patch

config/config.default.ts#L148-L159

Added lines #L148 - L159 were not covered by tests
}

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;

Check warning on line 173 in config/config.default.ts

View check run for this annotation

Codecov / codecov/patch

config/config.default.ts#L173

Added line #L173 was not covered by tests
}

config.logrotator = {
Expand Down Expand Up @@ -207,14 +210,15 @@
if (config.cnpmcore.enableElasticsearch) {
config.elasticsearch = {
client: {
node: process.env.CNPMCORE_CONFIG_ES_CLIENT_NODE,
node: env('CNPMCORE_CONFIG_ES_CLIENT_NODE', 'string', ''),

Check warning on line 213 in config/config.default.ts

View check run for this annotation

Codecov / codecov/patch

config/config.default.ts#L213

Added line #L213 was not covered by tests
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', ''),

Check warning on line 216 in config/config.default.ts

View check run for this annotation

Codecov / codecov/patch

config/config.default.ts#L215-L216

Added lines #L215 - L216 were not covered by tests
},
},
};
}

return config;
};

2 changes: 1 addition & 1 deletion config/config.unittest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default (appInfo: EggAppConfig) => {
config.dataDir = join(appInfo.root, '.cnpmcore_unittest');

config.orm = {
database: database.name ?? 'cnpmcore_unittest',
database: database.name || 'cnpmcore_unittest',
};

config.nfs = {
Expand Down
34 changes: 18 additions & 16 deletions config/database.ts
Original file line number Diff line number Diff line change
@@ -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', '');
fengmk2 marked this conversation as resolved.
Show resolved Hide resolved
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';
Dismissed Show dismissed Hide dismissed
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', '') || '127.0.0.1';
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', '') || 'postgres';
Dismissed Show dismissed Hide dismissed
dbPassword = dbPassword || env('CNPMCORE_POSTGRES_PASSWORD', 'string', '') || env('POSTGRES_PASSWORD', 'string', '') || 'postgres';
Dismissed Show dismissed Hide dismissed
} else if (dbType === DATABASE_TYPE.SQLite) {
// TODO
// TODO: Implement SQLite

Check warning on line 32 in config/database.ts

View check run for this annotation

Codecov / codecov/patch

config/database.ts#L32

Added line #L32 was not covered by tests
dbClient = 'sqlite';
dialect = 'sqlite';
}
Expand Down
Loading
Loading