update
This commit is contained in:
@@ -47,11 +47,14 @@ export class NpmRegistry extends BaseRegistry {
|
||||
|
||||
public async handleRequest(context: IRequestContext): Promise<IResponse> {
|
||||
const path = context.path.replace(this.basePath, '');
|
||||
console.log(`[NPM handleRequest] method=${context.method}, path=${path}`);
|
||||
|
||||
// Extract token from Authorization header
|
||||
const authHeader = context.headers['authorization'] || context.headers['Authorization'];
|
||||
const tokenString = authHeader?.replace(/^Bearer\s+/i, '');
|
||||
console.log(`[NPM handleRequest] authHeader=${authHeader}, tokenString=${tokenString}`);
|
||||
const token = tokenString ? await this.authManager.validateToken(tokenString, 'npm') : null;
|
||||
console.log(`[NPM handleRequest] token validated:`, token);
|
||||
|
||||
// Registry root
|
||||
if (path === '/' || path === '') {
|
||||
@@ -88,20 +91,38 @@ export class NpmRegistry extends BaseRegistry {
|
||||
return this.handleTarballDownload(packageName, filename, token);
|
||||
}
|
||||
|
||||
// Package operations: /{package}
|
||||
const packageMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)$/);
|
||||
if (packageMatch) {
|
||||
const packageName = packageMatch[1];
|
||||
return this.handlePackage(context.method, packageName, context.body, context.query, token);
|
||||
// Unpublish specific version: DELETE /{package}/-/{version}
|
||||
const unpublishVersionMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/-\/([^\/]+)$/);
|
||||
if (unpublishVersionMatch && context.method === 'DELETE') {
|
||||
const [, packageName, version] = unpublishVersionMatch;
|
||||
console.log(`[unpublishVersionMatch] packageName=${packageName}, version=${version}`);
|
||||
return this.unpublishVersion(packageName, version, token);
|
||||
}
|
||||
|
||||
// Unpublish entire package: DELETE /{package}/-rev/{rev}
|
||||
const unpublishPackageMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/-rev\/([^\/]+)$/);
|
||||
if (unpublishPackageMatch && context.method === 'DELETE') {
|
||||
const [, packageName, rev] = unpublishPackageMatch;
|
||||
console.log(`[unpublishPackageMatch] packageName=${packageName}, rev=${rev}`);
|
||||
return this.unpublishPackage(packageName, token);
|
||||
}
|
||||
|
||||
// Package version: /{package}/{version}
|
||||
const versionMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/([^\/]+)$/);
|
||||
if (versionMatch) {
|
||||
const [, packageName, version] = versionMatch;
|
||||
console.log(`[versionMatch] matched! packageName=${packageName}, version=${version}`);
|
||||
return this.handlePackageVersion(packageName, version, token);
|
||||
}
|
||||
|
||||
// Package operations: /{package}
|
||||
const packageMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)$/);
|
||||
if (packageMatch) {
|
||||
const packageName = packageMatch[1];
|
||||
console.log(`[packageMatch] matched! packageName=${packageName}`);
|
||||
return this.handlePackage(context.method, packageName, context.body, context.query, token);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 404,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -209,7 +230,12 @@ export class NpmRegistry extends BaseRegistry {
|
||||
version: string,
|
||||
token: IAuthToken | null
|
||||
): Promise<IResponse> {
|
||||
console.log(`[handlePackageVersion] packageName=${packageName}, version=${version}`);
|
||||
const packument = await this.storage.getNpmPackument(packageName);
|
||||
console.log(`[handlePackageVersion] packument found:`, !!packument);
|
||||
if (packument) {
|
||||
console.log(`[handlePackageVersion] versions:`, Object.keys(packument.versions || {}));
|
||||
}
|
||||
if (!packument) {
|
||||
return {
|
||||
status: 404,
|
||||
@@ -252,7 +278,10 @@ export class NpmRegistry extends BaseRegistry {
|
||||
body: IPublishRequest,
|
||||
token: IAuthToken | null
|
||||
): Promise<IResponse> {
|
||||
if (!await this.checkPermission(token, packageName, 'write')) {
|
||||
console.log(`[publishPackage] packageName=${packageName}, token=`, token);
|
||||
const hasPermission = await this.checkPermission(token, packageName, 'write');
|
||||
console.log(`[publishPackage] hasPermission=${hasPermission}`);
|
||||
if (!hasPermission) {
|
||||
return {
|
||||
status: 401,
|
||||
headers: {},
|
||||
@@ -361,6 +390,67 @@ export class NpmRegistry extends BaseRegistry {
|
||||
};
|
||||
}
|
||||
|
||||
private async unpublishVersion(
|
||||
packageName: string,
|
||||
version: string,
|
||||
token: IAuthToken | null
|
||||
): Promise<IResponse> {
|
||||
if (!await this.checkPermission(token, packageName, 'delete')) {
|
||||
return {
|
||||
status: 401,
|
||||
headers: {},
|
||||
body: this.createError('EUNAUTHORIZED', 'Unauthorized'),
|
||||
};
|
||||
}
|
||||
|
||||
const packument = await this.storage.getNpmPackument(packageName);
|
||||
if (!packument) {
|
||||
return {
|
||||
status: 404,
|
||||
headers: {},
|
||||
body: this.createError('E404', 'Package not found'),
|
||||
};
|
||||
}
|
||||
|
||||
// Check if version exists
|
||||
if (!packument.versions[version]) {
|
||||
return {
|
||||
status: 404,
|
||||
headers: {},
|
||||
body: this.createError('E404', 'Version not found'),
|
||||
};
|
||||
}
|
||||
|
||||
// Delete tarball
|
||||
await this.storage.deleteNpmTarball(packageName, version);
|
||||
|
||||
// Remove version from packument
|
||||
delete packument.versions[version];
|
||||
if (packument.time) {
|
||||
delete packument.time[version];
|
||||
packument.time.modified = new Date().toISOString();
|
||||
}
|
||||
|
||||
// Update latest tag if this was the latest version
|
||||
if (packument['dist-tags']?.latest === version) {
|
||||
const remainingVersions = Object.keys(packument.versions);
|
||||
if (remainingVersions.length > 0) {
|
||||
packument['dist-tags'].latest = remainingVersions[remainingVersions.length - 1];
|
||||
} else {
|
||||
delete packument['dist-tags'].latest;
|
||||
}
|
||||
}
|
||||
|
||||
// Save updated packument
|
||||
await this.storage.putNpmPackument(packageName, packument);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: { ok: true },
|
||||
};
|
||||
}
|
||||
|
||||
private async unpublishPackage(
|
||||
packageName: string,
|
||||
token: IAuthToken | null
|
||||
@@ -438,14 +528,64 @@ export class NpmRegistry extends BaseRegistry {
|
||||
const size = parseInt(query.size || '20', 10);
|
||||
const from = parseInt(query.from || '0', 10);
|
||||
|
||||
// Simple search implementation (in production, use proper search index)
|
||||
// Simple search implementation
|
||||
const results: ISearchResult[] = [];
|
||||
|
||||
// For now, return empty results
|
||||
// In production, implement full-text search across packuments
|
||||
try {
|
||||
// List all package paths
|
||||
const packagePaths = await this.storage.listObjects('npm/packages/');
|
||||
|
||||
// Extract unique package names from paths (format: npm/packages/{packageName}/...)
|
||||
const packageNames = new Set<string>();
|
||||
for (const path of packagePaths) {
|
||||
const match = path.match(/^npm\/packages\/([^\/]+)\/index\.json$/);
|
||||
if (match) {
|
||||
packageNames.add(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Load packuments and filter by search text
|
||||
for (const packageName of packageNames) {
|
||||
if (!text || packageName.toLowerCase().includes(text.toLowerCase())) {
|
||||
const packument = await this.storage.getNpmPackument(packageName);
|
||||
if (packument) {
|
||||
const latestVersion = packument['dist-tags']?.latest;
|
||||
const versionData = latestVersion ? packument.versions[latestVersion] : null;
|
||||
|
||||
results.push({
|
||||
package: {
|
||||
name: packument.name,
|
||||
version: latestVersion || '0.0.0',
|
||||
description: packument.description || versionData?.description || '',
|
||||
keywords: versionData?.keywords || [],
|
||||
date: packument.time?.modified || new Date().toISOString(),
|
||||
links: {},
|
||||
author: versionData?.author || {},
|
||||
publisher: versionData?._npmUser || {},
|
||||
maintainers: packument.maintainers || [],
|
||||
},
|
||||
score: {
|
||||
final: 1.0,
|
||||
detail: {
|
||||
quality: 1.0,
|
||||
popularity: 1.0,
|
||||
maintenance: 1.0,
|
||||
},
|
||||
},
|
||||
searchScore: 1.0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[handleSearch] Error:', error);
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
const paginatedResults = results.slice(from, from + size);
|
||||
|
||||
const response: ISearchResponse = {
|
||||
objects: results,
|
||||
objects: paginatedResults,
|
||||
total: results.length,
|
||||
time: new Date().toISOString(),
|
||||
};
|
||||
@@ -581,7 +721,7 @@ export class NpmRegistry extends BaseRegistry {
|
||||
const newToken = await this.authManager.createNpmToken(token.userId, readonly);
|
||||
|
||||
return {
|
||||
status: 201,
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: {
|
||||
token: newToken,
|
||||
|
||||
Reference in New Issue
Block a user