mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
feat: npm deprecation support (#1842)
* support deprecation * test case for deprecation * fix format * testing for multiple packages deprecation * update README Co-authored-by: Juan Picado <juanpicado19@gmail.com>
This commit is contained in:
parent
67c31b69ca
commit
80ade97801
8 changed files with 127 additions and 9 deletions
|
@ -171,7 +171,7 @@ Verdaccio aims to support all features of a standard npm client that make sense
|
|||
|
||||
- Unpublishing packages (npm unpublish) - **supported**
|
||||
- Tagging (npm tag) - **supported**
|
||||
- Deprecation (npm deprecate) - not supported - *PR-welcome*
|
||||
- Deprecation (npm deprecate) - supported
|
||||
|
||||
### User management
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import Path from 'path';
|
|||
import mime from 'mime';
|
||||
|
||||
import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '../../../lib/constants';
|
||||
import {validateMetadata, isObject, ErrorCode, hasDiffOneKey} from '../../../lib/utils';
|
||||
import {validateMetadata, isObject, ErrorCode, hasDiffOneKey, isRelatedToDeprecation} from '../../../lib/utils';
|
||||
import { media, expectJson, allow } from '../../middleware';
|
||||
import { notify } from '../../../lib/notify';
|
||||
import star from './star';
|
||||
|
@ -144,8 +144,9 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
|||
|
||||
const { _attachments, versions } = metadataCopy;
|
||||
|
||||
// if the is no attachments, it is change, it is a new package.
|
||||
if (_.isNil(_attachments)) {
|
||||
// `npm star` wouldn't have attachments
|
||||
// and `npm deprecate` would have attachments as a empty object, i.e {}
|
||||
if (_.isNil(_attachments) || JSON.stringify(_attachments) === '{}') {
|
||||
if (error) {
|
||||
return next(error);
|
||||
}
|
||||
|
@ -214,7 +215,8 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
|
|||
|
||||
try {
|
||||
const metadata = validateMetadata(req.body, packageName);
|
||||
if (req.params._rev) {
|
||||
// treating deprecation as updating a package
|
||||
if (req.params._rev || isRelatedToDeprecation(req.body)) {
|
||||
logger.debug({packageName} , `updating a new version for @{packageName}`);
|
||||
// we check unpublish permissions, an update is basically remove versions
|
||||
const remote = req.remote_user;
|
||||
|
|
|
@ -320,7 +320,7 @@ class LocalStorage implements IStorage {
|
|||
|
||||
/**
|
||||
* Update the package metadata, tags and attachments (tarballs).
|
||||
* Note: Currently supports unpublishing only.
|
||||
* Note: Currently supports unpublishing and deprecation.
|
||||
* @param {*} name
|
||||
* @param {*} incomingPkg
|
||||
* @param {*} revision
|
||||
|
@ -338,7 +338,8 @@ class LocalStorage implements IStorage {
|
|||
name,
|
||||
(localData: Package, cb: CallbackAction): void => {
|
||||
for (const version in localData.versions) {
|
||||
if (_.isNil(incomingPkg.versions[version])) {
|
||||
const incomingVersion = incomingPkg.versions[version];
|
||||
if (_.isNil(incomingVersion)) {
|
||||
this.logger.info({ name: name, version: version }, 'unpublishing @{name}@@{version}');
|
||||
|
||||
// FIXME: I prefer return a new object rather mutate the metadata
|
||||
|
@ -350,6 +351,18 @@ class LocalStorage implements IStorage {
|
|||
delete localData._attachments[file].version;
|
||||
}
|
||||
}
|
||||
} else if (Object.prototype.hasOwnProperty.call(incomingVersion, 'deprecated')) {
|
||||
const incomingDeprecated = incomingVersion.deprecated;
|
||||
if (incomingDeprecated != localData.versions[version].deprecated) {
|
||||
if (!incomingDeprecated) {
|
||||
this.logger.info({ name: name, version: version }, 'undeprecating @{name}@@{version}');
|
||||
delete localData.versions[version].deprecated;
|
||||
} else {
|
||||
this.logger.info({ name: name, version: version }, 'deprecating @{name}@@{version}');
|
||||
localData.versions[version].deprecated = incomingDeprecated;
|
||||
}
|
||||
localData.time!.modified = new Date().toISOString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -635,3 +635,13 @@ export function isVersionValid(packageMeta, packageVersion): boolean {
|
|||
const hasMatchVersion = Object.keys(packageMeta.versions).includes(packageVersion);
|
||||
return hasMatchVersion;
|
||||
}
|
||||
|
||||
export function isRelatedToDeprecation(pkgInfo: Package): boolean {
|
||||
const { versions } = pkgInfo;
|
||||
for (const version in versions) {
|
||||
if (Object.prototype.hasOwnProperty.call(versions[version], 'deprecated')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -68,7 +68,8 @@ export function getPackage(
|
|||
return new Promise((resolve) => {
|
||||
let getRequest = request.get(`/${pkgName}`);
|
||||
|
||||
if (_.isNil(token) === false || _.isEmpty(token) === false) {
|
||||
// token is a string
|
||||
if (token !== '') {
|
||||
getRequest.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token));
|
||||
}
|
||||
|
||||
|
|
|
@ -158,3 +158,12 @@ export function generatePackageMetadata(pkgName: string, version = '1.0.0'): Pac
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function generateDeprecateMetadata(pkgName: string, version = '1.0.0', deprecated:string = ''): Package {
|
||||
const res = {
|
||||
...generatePackageMetadata(pkgName, version),
|
||||
_attachments: {},
|
||||
};
|
||||
res.versions[version].deprecated = deprecated;
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -20,10 +20,17 @@ import {DOMAIN_SERVERS} from '../../../functional/config.functional';
|
|||
import {buildToken, encodeScopedUri} from '../../../../src/lib/utils';
|
||||
import {
|
||||
getNewToken,
|
||||
getPackage,
|
||||
putPackage,
|
||||
verifyPackageVersionDoesExist, generateUnPublishURI
|
||||
} from '../../__helper/api';
|
||||
import {generatePackageMetadata, generatePackageUnpublish, generateStarMedatada} from '../../__helper/utils';
|
||||
import {
|
||||
generatePackageMetadata,
|
||||
generatePackageUnpublish,
|
||||
generateStarMedatada,
|
||||
generateDeprecateMetadata,
|
||||
generateVersion,
|
||||
} from '../../__helper/utils';
|
||||
|
||||
require('../../../../src/lib/logger').setup([
|
||||
{ type: 'stdout', format: 'pretty', level: 'warn' }
|
||||
|
@ -903,5 +910,73 @@ describe('endpoint unit test', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should test (un)deprecate api', () => {
|
||||
const pkgName = '@scope/deprecate';
|
||||
const credentials = { name: 'jota_deprecate', password: 'secretPass' };
|
||||
const version = '1.0.0'
|
||||
let token = '';
|
||||
beforeAll(async (done) =>{
|
||||
token = await getNewToken(request(app), credentials);
|
||||
await putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName, version), token);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should deprecate a package', async (done) => {
|
||||
const pkg = generateDeprecateMetadata(pkgName, version, 'get deprecated');
|
||||
const [err] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, pkg, token);
|
||||
if (err) {
|
||||
expect(err).toBeNull();
|
||||
return done(err);
|
||||
}
|
||||
const [,res] = await getPackage(request(app), '', pkgName);
|
||||
expect(res.body.versions[version].deprecated).toEqual('get deprecated');
|
||||
done();
|
||||
});
|
||||
|
||||
test('should undeprecate a package', async (done) => {
|
||||
let pkg = generateDeprecateMetadata(pkgName, version, 'get deprecated');
|
||||
await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, pkg, token);
|
||||
pkg = generateDeprecateMetadata(pkgName, version, '');
|
||||
const [err] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, pkg, token);
|
||||
if (err) {
|
||||
expect(err).toBeNull();
|
||||
return done(err);
|
||||
}
|
||||
const [,res] = await getPackage(request(app), '', pkgName);
|
||||
expect(res.body.versions[version].deprecated).not.toBeDefined();
|
||||
done();
|
||||
});
|
||||
|
||||
test('should require both publish and unpublish access to (un)deprecate a package', async () => {
|
||||
let credentials = { name: 'only_publish', password: 'secretPass' };
|
||||
let token = await getNewToken(request(app), credentials);
|
||||
const pkg = generateDeprecateMetadata(pkgName, version, 'get deprecated');
|
||||
const [err, res] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, pkg, token);
|
||||
expect(err).not.toBeNull();
|
||||
expect(res.body.error).toBeDefined();
|
||||
expect(res.body.error).toMatch(/user only_publish is not allowed to unpublish package @scope\/deprecate/);
|
||||
credentials = { name: 'only_unpublish', password: 'secretPass' };
|
||||
token = await getNewToken(request(app), credentials);
|
||||
const [err2, res2] = await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, pkg, token);
|
||||
expect(err2).not.toBeNull();
|
||||
expect(res2.body.error).toBeDefined();
|
||||
expect(res2.body.error).toMatch(/user only_unpublish is not allowed to publish package @scope\/deprecate/);
|
||||
})
|
||||
|
||||
test('should deprecate multiple packages', async (done) => {
|
||||
await putPackage(request(app), `/${pkgName}`, generatePackageMetadata(pkgName, '1.0.1'), token);
|
||||
const pkg = generateDeprecateMetadata(pkgName, version, 'get deprecated');
|
||||
pkg.versions['1.0.1'] = {
|
||||
...generateVersion(pkgName, '1.0.1'),
|
||||
deprecated: 'get deprecated',
|
||||
};
|
||||
await putPackage(request(app), `/${encodeScopedUri(pkgName)}`, pkg, token);
|
||||
const [,res] = await getPackage(request(app), '', pkgName);
|
||||
expect(res.body.versions[version].deprecated).toEqual('get deprecated');
|
||||
expect(res.body.versions['1.0.1'].deprecated).toEqual('get deprecated');
|
||||
done()
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,14 @@ packages:
|
|||
access: $anonymous jota_unpublish
|
||||
publish: $anonymous jota_unpublish
|
||||
unpublish: $anonymous jota_unpublish
|
||||
'@scope/deprecate':
|
||||
access: $all
|
||||
publish:
|
||||
- jota_deprecate
|
||||
- only_publish
|
||||
unpublish:
|
||||
- jota_deprecate
|
||||
- only_unpublish
|
||||
'@scope/starPackage':
|
||||
access: $all
|
||||
publish: jota_star
|
||||
|
|
Loading…
Reference in a new issue