-
Notifications
You must be signed in to change notification settings - Fork 86
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: proxy模式增强 #724
base: master
Are you sure you want to change the base?
feat: proxy模式增强 #724
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,8 @@ import { calculateIntegrity } from '../../common/PackageUtil'; | |
import { ABBREVIATED_META_TYPE, PROXY_CACHE_DIR_NAME } from '../../common/constants'; | ||
import { DIST_NAMES } from '../entity/Package'; | ||
import type { AbbreviatedPackageManifestType, AbbreviatedPackageJSONType, PackageManifestType, PackageJSONType } from '../../repository/PackageRepository'; | ||
import { PackageManagerService } from './PackageManagerService'; | ||
import { getScopeAndName } from '../../common/PackageUtil'; | ||
|
||
function isoNow() { | ||
return new Date().toISOString(); | ||
|
@@ -50,6 +52,8 @@ export class ProxyCacheService extends AbstractService { | |
private readonly cacheService: CacheService; | ||
@Inject() | ||
private readonly backgroundTaskHelper:BackgroundTaskHelper; | ||
@Inject() | ||
private readonly packageManagerService: PackageManagerService; | ||
|
||
async getPackageVersionTarResponse(fullname: string, ctx: EggContext): Promise<HttpClientResponse> { | ||
if (this.config.cnpmcore.syncPackageBlockList.includes(fullname)) { | ||
|
@@ -59,12 +63,17 @@ export class ProxyCacheService extends AbstractService { | |
} | ||
|
||
async getPackageManifest(fullname: string, fileType: DIST_NAMES.FULL_MANIFESTS| DIST_NAMES.ABBREVIATED_MANIFESTS): Promise<AbbreviatedPackageManifestType|PackageManifestType> { | ||
const cachedStoreKey = (await this.proxyCacheRepository.findProxyCache(fullname, fileType))?.filePath; | ||
if (cachedStoreKey) { | ||
const nfsBytes = await this.nfsAdapter.getBytes(cachedStoreKey); | ||
const nfsString = Buffer.from(nfsBytes!).toString(); | ||
const nfsPkgManifgest = JSON.parse(nfsString); | ||
return nfsPkgManifgest; | ||
try { | ||
const cachedStoreKey = (await this.proxyCacheRepository.findProxyCache(fullname, fileType))?.filePath; | ||
if (cachedStoreKey) { | ||
const nfsBytes = await this.nfsAdapter.getBytes(cachedStoreKey); | ||
const nfsString = Buffer.from(nfsBytes!).toString(); | ||
const nfsPkgManifest = JSON.parse(nfsString); | ||
return nfsPkgManifest; | ||
} | ||
} catch (e) { | ||
this.logger.error(e); | ||
this.logger.error('[ProxyCacheService.getPackageManifest:error] get cache error, ignore'); | ||
} | ||
|
||
const manifest = await this.getRewrittenManifest<typeof fileType>(fullname, fileType); | ||
|
@@ -152,6 +161,7 @@ export class ProxyCacheService extends AbstractService { | |
} | ||
|
||
async getRewrittenManifest<T extends DIST_NAMES>(fullname:string, fileType: T, versionOrTag?:string): Promise<GetSourceManifestAndCacheReturnType<T>> { | ||
const [ scope, name ] = getScopeAndName(fullname); | ||
let responseResult; | ||
switch (fileType) { | ||
case DIST_NAMES.FULL_MANIFESTS: | ||
|
@@ -171,9 +181,24 @@ export class ProxyCacheService extends AbstractService { | |
} | ||
|
||
// replace tarball url | ||
const manifest = responseResult.data; | ||
const { status, data: manifest } = responseResult; | ||
// sourceRegistry not found, check private package | ||
if (status === 404) { | ||
const { etag, data: manifest, blockReason } = fileType === DIST_NAMES.FULL_MANIFESTS ? | ||
await this.packageManagerService.listPackageFullManifests(scope, name, false) : | ||
await this.packageManagerService.listPackageAbbreviatedManifests(scope, name, false); | ||
// found in private package | ||
if (etag && !blockReason) { | ||
return manifest as any; | ||
} | ||
} | ||
const { sourceRegistry, registry } = this.config.cnpmcore; | ||
if (isPkgManifest(fileType)) { | ||
const { etag, data, blockReason } = fileType === DIST_NAMES.FULL_MANIFESTS ? | ||
await this.packageManagerService.listPackageFullManifests(scope, name, false) : | ||
await this.packageManagerService.listPackageAbbreviatedManifests(scope, name, false); | ||
const hasPrivatePackage = etag && !blockReason; | ||
|
||
// pkg manifest | ||
const versionMap = manifest.versions || {}; | ||
for (const key in versionMap) { | ||
|
@@ -182,9 +207,26 @@ export class ProxyCacheService extends AbstractService { | |
versionItem.dist.tarball = versionItem.dist.tarball.replace(sourceRegistry, registry); | ||
} | ||
} | ||
// private manifest | ||
if (hasPrivatePackage) { | ||
const privateVersionMap = data?.versions || {}; | ||
for (const key in privateVersionMap) { | ||
if (!versionMap[key]) { | ||
versionMap[key] = privateVersionMap[key]; | ||
} | ||
} | ||
if (manifest.time) { | ||
const privateTimeMap = data?.time || {}; | ||
for (const key in privateTimeMap) { | ||
if (!manifest.time[key]) { | ||
manifest.time[key] = privateTimeMap[key]; | ||
} | ||
} | ||
} | ||
} | ||
Comment on lines
+210
to
+226
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add defensive checks for manifest merging The manifest merging logic should include additional checks to handle edge cases and ensure data integrity. -if (hasPrivatePackage) {
+if (hasPrivatePackage && data && manifest) {
+ // Ensure versions object exists
+ manifest.versions = manifest.versions || {};
+ manifest.time = manifest.time || {};
+
const privateVersionMap = data?.versions || {};
for (const key in privateVersionMap) {
- if (!versionMap[key]) {
+ if (!versionMap[key] && privateVersionMap[key]?.version === key) {
versionMap[key] = privateVersionMap[key];
}
}
- if (manifest.time) {
- const privateTimeMap = data?.time || {};
- for (const key in privateTimeMap) {
- if (!manifest.time[key]) {
- manifest.time[key] = privateTimeMap[key];
- }
+ const privateTimeMap = data?.time || {};
+ for (const key in privateTimeMap) {
+ const timeValue = privateTimeMap[key];
+ if (!manifest.time[key] && typeof timeValue === 'string' && isValidISODateString(timeValue)) {
+ manifest.time[key] = timeValue;
}
}
} Consider adding a helper function to validate ISO date strings: function isValidISODateString(dateString: string): boolean {
if (typeof dateString !== 'string') return false;
const date = new Date(dateString);
return date instanceof Date && !isNaN(date.getTime()) && date.toISOString() === dateString;
} 🛠️ Refactor suggestion Add version format validation before merging The version merging logic should validate the format of version strings before merging to prevent invalid versions from being added to the manifest. Consider using the if (hasPrivatePackage) {
const privateVersionMap = data?.versions || {};
for (const key in privateVersionMap) {
- if (!versionMap[key]) {
+ if (!versionMap[key] && semverValid(key)) {
versionMap[key] = privateVersionMap[key];
+ } else if (!semverValid(key)) {
+ this.logger.warn('[ProxyCacheService:getRewrittenManifest] Skipping invalid version %s from private package %s', key, fullname);
}
}
if (manifest.time) {
const privateTimeMap = data?.time || {};
for (const key in privateTimeMap) {
- if (!manifest.time[key]) {
+ if (!manifest.time[key] && (key === 'created' || key === 'modified' || semverValid(key))) {
manifest.time[key] = privateTimeMap[key];
+ } else if (key !== 'created' && key !== 'modified' && !semverValid(key)) {
+ this.logger.warn('[ProxyCacheService:getRewrittenManifest] Skipping invalid time entry %s from private package %s', key, fullname);
}
}
}
}
|
||
} else { | ||
// pkg version manifest | ||
const distItem = manifest.dist || {}; | ||
const distItem = manifest?.dist || {}; | ||
if (distItem.tarball) { | ||
distItem.tarball = distItem.tarball.replace(sourceRegistry, registry); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,9 @@ import { AbstractController } from '../AbstractController'; | |
import { FULLNAME_REG_STRING } from '../../../common/PackageUtil'; | ||
import { User as UserEntity } from '../../../core/entity/User'; | ||
import { PackageManagerService } from '../../../core/service/PackageManagerService'; | ||
import { SyncMode } from '../../../common/constants'; | ||
import { ProxyCacheRepository } from '../../../repository/ProxyCacheRepository'; | ||
import { isPkgManifest, ProxyCacheService } from '../../../core/service/ProxyCacheService'; | ||
|
||
const MaintainerDataRule = Type.Object({ | ||
maintainers: Type.Array(Type.Object({ | ||
|
@@ -30,7 +33,10 @@ type Maintainer = Static<typeof MaintainerDataRule>; | |
export class UpdatePackageController extends AbstractController { | ||
@Inject() | ||
private packageManagerService: PackageManagerService; | ||
|
||
@Inject() | ||
private readonly proxyCacheRepository: ProxyCacheRepository; | ||
@Inject() | ||
private readonly proxyCacheService: ProxyCacheService; | ||
// https://github.com/npm/cli/blob/latest/lib/commands/owner.js#L191 | ||
@HTTPMethod({ | ||
// PUT /:fullname/-rev/:rev | ||
|
@@ -65,6 +71,26 @@ export class UpdatePackageController extends AbstractController { | |
} | ||
|
||
await this.packageManagerService.replacePackageMaintainersAndDist(pkg, users); | ||
// 代理模式下,更新代理缓存 | ||
fangzhengjin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (this.config.cnpmcore.syncMode === SyncMode.proxy) { | ||
const refreshList = await this.proxyCacheRepository.findProxyCaches(fullname); | ||
if (refreshList.length !== 0) { | ||
const taskList = refreshList | ||
// 仅manifests需要更新,指定版本的package.json文件发布后不会改变 | ||
.filter(i => isPkgManifest(i.fileType)) | ||
.map(async item => { | ||
const task = await this.proxyCacheService.createTask( | ||
`${item.fullname}/${item.fileType}`, | ||
{ | ||
fullname: item.fullname, | ||
fileType: item.fileType, | ||
}, | ||
); | ||
return task; | ||
}); | ||
await Promise.all(taskList); | ||
} | ||
} | ||
Comment on lines
+74
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling for proxy cache updates While the proxy cache update logic is correct, it lacks error handling for the Promise.all operation. Failed cache updates should be logged but not block the main operation. Consider applying this improvement: if (this.config.cnpmcore.syncMode === SyncMode.proxy) {
const refreshList = await this.proxyCacheRepository.findProxyCaches(fullname);
if (refreshList.length !== 0) {
const taskList = refreshList
.filter(i => isPkgManifest(i.fileType))
.map(item =>
this.proxyCacheService.createTask(
`${item.fullname}/${item.fileType}`,
{
fullname: item.fullname,
fileType: item.fileType,
},
)
);
+ try {
await Promise.all(taskList);
+ } catch (error) {
+ // Log error but don't block the main operation
+ ctx.logger.error('[UpdatePackageController] Failed to update proxy cache', {
+ error,
+ fullname,
+ taskCount: taskList.length,
+ });
+ }
}
}
|
||
return { ok: true }; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
manifest 是 const 的话,下面会被重新赋值,应该先设置它是
let manifest = undefined
,确保代码的可读性。There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
两个manifest的作用域不一样,不存在重新赋值的说法吧?如果是为了可读性的话,一个改成remoteManifest一个改成privateManifest,这样可以吗?