mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
refactor: reimplement star command (#3410)
This commit is contained in:
parent
6ad13de884
commit
ce013d2fcc
30 changed files with 1086 additions and 354 deletions
9
.changeset/rich-badgers-begin.md
Normal file
9
.changeset/rich-badgers-begin.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
'@verdaccio/api': minor
|
||||
'@verdaccio/url': minor
|
||||
'@verdaccio/store': minor
|
||||
'@verdaccio/test-helper': minor
|
||||
'verdaccio': minor
|
||||
---
|
||||
|
||||
refactor: npm star command support reimplemented
|
|
@ -19,6 +19,7 @@
|
|||
| deprecate | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⛔ | ⛔ | ⛔ | ⛔ |
|
||||
| ping | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⛔ | ⛔ | ⛔ | ⛔ |
|
||||
| search | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⛔ | ⛔ | ⛔ | ⛔ |
|
||||
| star | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⛔ | ⛔ | ⛔ | ⛔ |
|
||||
| dist-tag | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
|
||||
> notes:
|
||||
|
|
69
e2e/cli/e2e-npm6/star.spec.ts
Normal file
69
e2e/cli/e2e-npm6/star.spec.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import {
|
||||
addRegistry,
|
||||
initialSetup,
|
||||
npmUtils,
|
||||
prepareGenericEmptyProject,
|
||||
} from '@verdaccio/test-cli-commons';
|
||||
|
||||
import { npm } from './utils';
|
||||
|
||||
describe('star a package', () => {
|
||||
jest.setTimeout(20000);
|
||||
let registry;
|
||||
|
||||
beforeAll(async () => {
|
||||
const setup = await initialSetup();
|
||||
registry = setup.registry;
|
||||
await registry.init();
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/foo']])('should star a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await npmUtils.publish(npm, tempFolder, pkgName, registry);
|
||||
const resp = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/bar']])('should unstar a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await npmUtils.publish(npm, tempFolder, pkgName, registry);
|
||||
const resp = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
|
||||
const resp1 = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'unstar',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp1.stdout).toEqual(`☆ ${pkgName}`);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
registry.stop();
|
||||
});
|
||||
});
|
69
e2e/cli/e2e-npm7/star.spec.ts
Normal file
69
e2e/cli/e2e-npm7/star.spec.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import {
|
||||
addRegistry,
|
||||
initialSetup,
|
||||
npmUtils,
|
||||
prepareGenericEmptyProject,
|
||||
} from '@verdaccio/test-cli-commons';
|
||||
|
||||
import { npm } from './utils';
|
||||
|
||||
describe('star a package', () => {
|
||||
jest.setTimeout(20000);
|
||||
let registry;
|
||||
|
||||
beforeAll(async () => {
|
||||
const setup = await initialSetup();
|
||||
registry = setup.registry;
|
||||
await registry.init();
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/foo']])('should star a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await npmUtils.publish(npm, tempFolder, pkgName, registry);
|
||||
const resp = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/bar']])('should unstar a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await npmUtils.publish(npm, tempFolder, pkgName, registry);
|
||||
const resp = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
|
||||
const resp1 = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'unstar',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp1.stdout).toEqual(`☆ ${pkgName}`);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
registry.stop();
|
||||
});
|
||||
});
|
69
e2e/cli/e2e-npm8/star.spec.ts
Normal file
69
e2e/cli/e2e-npm8/star.spec.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import {
|
||||
addRegistry,
|
||||
initialSetup,
|
||||
npmUtils,
|
||||
prepareGenericEmptyProject,
|
||||
} from '@verdaccio/test-cli-commons';
|
||||
|
||||
import { npm } from './utils';
|
||||
|
||||
describe('star a package', () => {
|
||||
jest.setTimeout(20000);
|
||||
let registry;
|
||||
|
||||
beforeAll(async () => {
|
||||
const setup = await initialSetup();
|
||||
registry = setup.registry;
|
||||
await registry.init();
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/foo']])('should star a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await npmUtils.publish(npm, tempFolder, pkgName, registry);
|
||||
const resp = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/bar']])('should unstar a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await npmUtils.publish(npm, tempFolder, pkgName, registry);
|
||||
const resp = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
|
||||
const resp1 = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'unstar',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp1.stdout).toEqual(`☆ ${pkgName}`);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
registry.stop();
|
||||
});
|
||||
});
|
69
e2e/cli/e2e-npm9/star.spec.ts
Normal file
69
e2e/cli/e2e-npm9/star.spec.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import {
|
||||
addRegistry,
|
||||
initialSetup,
|
||||
npmUtils,
|
||||
prepareGenericEmptyProject,
|
||||
} from '@verdaccio/test-cli-commons';
|
||||
|
||||
import { npm } from './utils';
|
||||
|
||||
describe('star a package', () => {
|
||||
jest.setTimeout(20000);
|
||||
let registry;
|
||||
|
||||
beforeAll(async () => {
|
||||
const setup = await initialSetup();
|
||||
registry = setup.registry;
|
||||
await registry.init();
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/foo']])('should star a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await npmUtils.publish(npm, tempFolder, pkgName, registry);
|
||||
const resp = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/bar']])('should unstar a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await npmUtils.publish(npm, tempFolder, pkgName, registry);
|
||||
const resp = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
|
||||
const resp1 = await npm(
|
||||
{ cwd: tempFolder },
|
||||
'unstar',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp1.stdout).toEqual(`☆ ${pkgName}`);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
registry.stop();
|
||||
});
|
||||
});
|
69
e2e/cli/e2e-pnpm6/star.spec.ts
Normal file
69
e2e/cli/e2e-pnpm6/star.spec.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import {
|
||||
addRegistry,
|
||||
initialSetup,
|
||||
pnpmUtils,
|
||||
prepareGenericEmptyProject,
|
||||
} from '@verdaccio/test-cli-commons';
|
||||
|
||||
import { pnpm } from './utils';
|
||||
|
||||
describe('star a package', () => {
|
||||
jest.setTimeout(20000);
|
||||
let registry;
|
||||
|
||||
beforeAll(async () => {
|
||||
const setup = await initialSetup();
|
||||
registry = setup.registry;
|
||||
await registry.init();
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/foo']])('should star a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await pnpmUtils.publish(pnpm, tempFolder, pkgName, registry);
|
||||
const resp = await pnpm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/bar']])('should unstar a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await pnpmUtils.publish(pnpm, tempFolder, pkgName, registry);
|
||||
const resp = await pnpm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
|
||||
const resp1 = await pnpm(
|
||||
{ cwd: tempFolder },
|
||||
'unstar',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp1.stdout).toEqual(`☆ ${pkgName}`);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
registry.stop();
|
||||
});
|
||||
});
|
69
e2e/cli/e2e-pnpm7/star.spec.ts
Normal file
69
e2e/cli/e2e-pnpm7/star.spec.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
import {
|
||||
addRegistry,
|
||||
initialSetup,
|
||||
pnpmUtils,
|
||||
prepareGenericEmptyProject,
|
||||
} from '@verdaccio/test-cli-commons';
|
||||
|
||||
import { pnpm } from './utils';
|
||||
|
||||
describe('star a package', () => {
|
||||
jest.setTimeout(20000);
|
||||
let registry;
|
||||
|
||||
beforeAll(async () => {
|
||||
const setup = await initialSetup();
|
||||
registry = setup.registry;
|
||||
await registry.init();
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/foo']])('should star a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await pnpmUtils.publish(pnpm, tempFolder, pkgName, registry);
|
||||
const resp = await pnpm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
});
|
||||
|
||||
test.each([['@verdaccio/bar']])('should unstar a package %s', async (pkgName) => {
|
||||
const { tempFolder } = await prepareGenericEmptyProject(
|
||||
pkgName,
|
||||
'1.0.0-patch',
|
||||
registry.port,
|
||||
registry.getToken(),
|
||||
registry.getRegistryUrl()
|
||||
);
|
||||
|
||||
await pnpmUtils.publish(pnpm, tempFolder, pkgName, registry);
|
||||
const resp = await pnpm(
|
||||
{ cwd: tempFolder },
|
||||
'star',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp.stdout).toEqual(`★ ${pkgName}`);
|
||||
|
||||
const resp1 = await pnpm(
|
||||
{ cwd: tempFolder },
|
||||
'unstar',
|
||||
pkgName,
|
||||
...addRegistry(registry.getRegistryUrl())
|
||||
);
|
||||
expect(resp1.stdout).toEqual(`☆ ${pkgName}`);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
registry.stop();
|
||||
});
|
||||
});
|
|
@ -11,7 +11,6 @@ import { Storage } from '@verdaccio/store';
|
|||
import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types/custom';
|
||||
|
||||
// import star from './star';
|
||||
// import { isPublishablePackage, isRelatedToDeprecation } from './utils';
|
||||
|
||||
const debug = buildDebug('verdaccio:api:publish');
|
||||
|
||||
|
@ -177,17 +176,17 @@ export default function publish(router: Router, auth: IAuth, storage: Storage):
|
|||
export function publishPackage(storage: Storage): any {
|
||||
return async function (
|
||||
req: $RequestExtend,
|
||||
_res: $ResponseExtend,
|
||||
res: $ResponseExtend,
|
||||
next: $NextFunctionVer
|
||||
): Promise<void> {
|
||||
const ac = new AbortController();
|
||||
const packageName = req.params.package;
|
||||
const { revision } = req.params;
|
||||
const metadata = req.body;
|
||||
const username = req?.remote_user?.name;
|
||||
|
||||
try {
|
||||
debug('publishing %s', packageName);
|
||||
await storage.updateManifest(metadata, {
|
||||
const message = await storage.updateManifest(metadata, {
|
||||
name: packageName,
|
||||
revision,
|
||||
signal: ac.signal,
|
||||
|
@ -196,16 +195,15 @@ export function publishPackage(storage: Storage): any {
|
|||
protocol: req.protocol,
|
||||
// @ts-ignore
|
||||
headers: req.headers,
|
||||
username,
|
||||
},
|
||||
});
|
||||
_res.status(HTTP_STATUS.CREATED);
|
||||
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
|
||||
return next({
|
||||
// TODO: this could be also Package Updated based on the
|
||||
// action, deprecate, star, publish new version, or create a package
|
||||
// the message some return from the method
|
||||
ok: API_MESSAGE.PKG_CREATED,
|
||||
success: true,
|
||||
ok: message,
|
||||
});
|
||||
} catch (err: any) {
|
||||
// TODO: review if we need the abort controller here
|
||||
|
|
|
@ -160,13 +160,13 @@ describe('publish', () => {
|
|||
decodeURIComponent(pkgName),
|
||||
'1.0.1-patch'
|
||||
).expect(HTTP_STATUS.CREATED);
|
||||
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
|
||||
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CHANGED);
|
||||
const response2 = await publishVersion(
|
||||
app,
|
||||
decodeURIComponent(pkgName),
|
||||
'1.0.2-patch'
|
||||
).expect(HTTP_STATUS.CREATED);
|
||||
expect(response2.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
|
||||
expect(response2.body.ok).toEqual(API_MESSAGE.PKG_CHANGED);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -90,10 +90,23 @@ export function validateURL(publicUrl: string | void) {
|
|||
}
|
||||
|
||||
export type RequestOptions = {
|
||||
/**
|
||||
* Request host.
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
* Request protocol.
|
||||
*/
|
||||
protocol: string;
|
||||
/**
|
||||
* Request headers.
|
||||
*/
|
||||
headers: { [key: string]: string };
|
||||
remoteAddress?: string;
|
||||
/**
|
||||
* Logged username the request, usually after token verification.
|
||||
*/
|
||||
username?: string;
|
||||
};
|
||||
|
||||
export function getPublicUrl(url_prefix: string = '', requestOptions: RequestOptions): string {
|
||||
|
|
|
@ -4,9 +4,9 @@ module.exports = Object.assign({}, config, {
|
|||
coverageThreshold: {
|
||||
global: {
|
||||
// FIXME: increase to 90
|
||||
branches: 51,
|
||||
functions: 69,
|
||||
lines: 66,
|
||||
branches: 62,
|
||||
functions: 86,
|
||||
lines: 76,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,17 +1,8 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { validatioUtils } from '@verdaccio/core';
|
||||
import { Manifest } from '@verdaccio/types';
|
||||
|
||||
import { Users } from '../type';
|
||||
import { Manifest, PackageUsers } from '@verdaccio/types';
|
||||
|
||||
/**
|
||||
* Check whether the package metadta has enough data to be published
|
||||
* @param pkg metadata
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check whether the package metadta has enough data to be published
|
||||
* Check whether the package metadata has enough data to be published
|
||||
* @param pkg metadata
|
||||
*/
|
||||
export function isPublishablePackage(pkg: Manifest): boolean {
|
||||
|
@ -21,27 +12,31 @@ export function isPublishablePackage(pkg: Manifest): boolean {
|
|||
return keys.includes('versions');
|
||||
}
|
||||
|
||||
// @deprecated don't think this is used anymore (REMOVE)
|
||||
export function isRelatedToDeprecation(pkgInfo: Manifest): boolean {
|
||||
const { versions } = pkgInfo;
|
||||
for (const version in versions) {
|
||||
if (Object.prototype.hasOwnProperty.call(versions[version], 'deprecated')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function validateInputs(localUsers: Users, username: string, isStar: boolean): boolean {
|
||||
const isExistlocalUsers = _.isNil(localUsers[username]) === false;
|
||||
if (isStar && isExistlocalUsers && localUsers[username]) {
|
||||
return true;
|
||||
} else if (!isStar && isExistlocalUsers) {
|
||||
/**
|
||||
* Verify if the user is actually executing an action, to avoid unnecessary calls
|
||||
* to the storage.
|
||||
* @param localUsers current state at cache
|
||||
* @param username user is executing the action
|
||||
* @param userIsAddingStar whether user is removing or adding star
|
||||
* @returns boolean
|
||||
*/
|
||||
export function isExecutingStarCommand(
|
||||
localUsers: PackageUsers,
|
||||
username: string,
|
||||
userIsAddingStar: boolean
|
||||
): boolean {
|
||||
const isExist = typeof localUsers[username] !== 'undefined';
|
||||
// fails if user already exist and us trying to add star.
|
||||
if (userIsAddingStar && isExist && localUsers[username]) {
|
||||
return false;
|
||||
} else if (!isStar && !isExistlocalUsers) {
|
||||
// if is not adding a start but user exists (removing star)
|
||||
} else if (!userIsAddingStar && isExist) {
|
||||
return true;
|
||||
// fails if user does not exist and is not adding any star
|
||||
} else if (!userIsAddingStar && !isExist) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isStarManifest(manifest: Manifest): boolean {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { default as URL } from 'url';
|
|||
import { hasProxyTo } from '@verdaccio/config';
|
||||
import {
|
||||
API_ERROR,
|
||||
API_MESSAGE,
|
||||
DIST_TAGS,
|
||||
HEADER_TYPE,
|
||||
HTTP_STATUS,
|
||||
|
@ -38,6 +39,7 @@ import {
|
|||
Logger,
|
||||
Manifest,
|
||||
MergeTags,
|
||||
PackageUsers,
|
||||
StringValue,
|
||||
Token,
|
||||
TokenFilter,
|
||||
|
@ -57,6 +59,7 @@ import {
|
|||
import { TransFormResults } from './lib/TransFormResults';
|
||||
import { removeDuplicates } from './lib/search-utils';
|
||||
import { isPublishablePackage } from './lib/star-utils';
|
||||
import { isExecutingStarCommand } from './lib/star-utils';
|
||||
import {
|
||||
STORAGE,
|
||||
cleanUpLinksRef,
|
||||
|
@ -72,7 +75,7 @@ import {
|
|||
import { ProxyInstanceList, setupUpLinks, updateVersionsHiddenUpLinkNext } from './lib/uplink-util';
|
||||
import { getVersion } from './lib/versions-utils';
|
||||
import { LocalStorage } from './local-storage';
|
||||
import { IGetPackageOptionsNext, IPluginFilters } from './type';
|
||||
import { IGetPackageOptionsNext, IPluginFilters, StarManifestBody } from './type';
|
||||
|
||||
const debug = buildDebug('verdaccio:storage');
|
||||
|
||||
|
@ -915,26 +918,33 @@ class Storage {
|
|||
return uplink;
|
||||
}
|
||||
|
||||
public async updateManifest(manifest: Manifest, options: UpdateManifestOptions): Promise<void> {
|
||||
if (isDeprecatedManifest(manifest)) {
|
||||
public async updateManifest(
|
||||
manifest: Manifest | StarManifestBody,
|
||||
options: UpdateManifestOptions
|
||||
): Promise<string | undefined> {
|
||||
if (isDeprecatedManifest(manifest as Manifest)) {
|
||||
// if the manifest is deprecated, we need to update the package.json
|
||||
await this.deprecate(manifest, {
|
||||
await this.deprecate(manifest as Manifest, {
|
||||
...options,
|
||||
});
|
||||
} else if (
|
||||
isPublishablePackage(manifest) === false &&
|
||||
isPublishablePackage(manifest as Manifest) === false &&
|
||||
validatioUtils.isObject(manifest.users)
|
||||
) {
|
||||
// if user request to apply a star to the manifest
|
||||
await this.star(manifest, {
|
||||
await this.star(manifest as StarManifestBody, {
|
||||
...options,
|
||||
});
|
||||
return API_MESSAGE.PKG_CHANGED;
|
||||
} else if (validatioUtils.validatePublishSingleVersion(manifest)) {
|
||||
// if continue, the version to be published does not exist
|
||||
// we create a new package
|
||||
const [mergedManifest, version] = await this.publishANewVersion(manifest, {
|
||||
...options,
|
||||
});
|
||||
const [mergedManifest, version, message] = await this.publishANewVersion(
|
||||
manifest as Manifest,
|
||||
{
|
||||
...options,
|
||||
}
|
||||
);
|
||||
// send notification of publication (notification step, non transactional)
|
||||
try {
|
||||
const { name } = mergedManifest;
|
||||
|
@ -943,6 +953,7 @@ class Storage {
|
|||
} catch (error: any) {
|
||||
logger.error({ error: error.message }, 'notify batch service has failed: @{error}');
|
||||
}
|
||||
return message;
|
||||
} else {
|
||||
debug('invalid body format');
|
||||
logger.info(
|
||||
|
@ -959,15 +970,51 @@ class Storage {
|
|||
return this.changePackage(name, manifest, options.revision as string);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
private async star(_body: Manifest, _options: PublishOptions): Promise<void> {
|
||||
// // const storage: IPackageStorage = this.getPrivatePackageStorage(opname);
|
||||
private async star(manifest: StarManifestBody, options: UpdateManifestOptions): Promise<string> {
|
||||
const { users } = manifest;
|
||||
const { requestOptions, name } = options;
|
||||
debug('star %s', name);
|
||||
const { username } = requestOptions;
|
||||
if (!username) {
|
||||
throw errorUtils.getBadRequest('update users only allowed to logged users');
|
||||
}
|
||||
|
||||
// if (typeof storage === 'undefined') {
|
||||
// throw errorUtils.getNotFound();
|
||||
// }
|
||||
const localPackage = await this.getPackageManifest({
|
||||
name,
|
||||
requestOptions,
|
||||
uplinksLook: false,
|
||||
});
|
||||
// backward compatible in case users are not in the storage.
|
||||
const localStarUsers = localPackage.users || {};
|
||||
// if trying to add a star
|
||||
const userIsAddingStar = Object.keys(users as PackageUsers).includes(username);
|
||||
if (!isExecutingStarCommand(localPackage.users as PackageUsers, username, userIsAddingStar)) {
|
||||
return API_MESSAGE.PKG_CHANGED;
|
||||
}
|
||||
|
||||
throw errorUtils.getInternalError('no implementation ready for npm star');
|
||||
const newUsers = userIsAddingStar
|
||||
? {
|
||||
...localStarUsers,
|
||||
[username]: true,
|
||||
}
|
||||
: _.reduce(
|
||||
localStarUsers,
|
||||
(users, value, key) => {
|
||||
if (key !== username) {
|
||||
users[key] = value;
|
||||
}
|
||||
return users;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
await this.changePackage(
|
||||
name,
|
||||
{ ...localPackage, users: newUsers },
|
||||
options.revision as string
|
||||
);
|
||||
|
||||
return API_MESSAGE.PKG_CHANGED;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1025,10 +1072,10 @@ class Storage {
|
|||
private async publishANewVersion(
|
||||
body: Manifest,
|
||||
options: PublishOptions
|
||||
): Promise<[Manifest, string]> {
|
||||
): Promise<[Manifest, string, string]> {
|
||||
const { name } = options;
|
||||
debug('publishing a new package for %o', name);
|
||||
|
||||
let successResponseMessage;
|
||||
const manifest: Manifest = { ...validatioUtils.normalizeMetadata(body, name) };
|
||||
const { _attachments, versions } = manifest;
|
||||
|
||||
|
@ -1066,6 +1113,9 @@ class Storage {
|
|||
const hasPackageInStorage = await this.hasPackage(name);
|
||||
if (!hasPackageInStorage) {
|
||||
await this.createNewLocalCachePackage(name, versionToPublish);
|
||||
successResponseMessage = API_MESSAGE.PKG_CREATED;
|
||||
} else {
|
||||
successResponseMessage = API_MESSAGE.PKG_CHANGED;
|
||||
}
|
||||
} catch (err: any) {
|
||||
debug('error on change or update a package with %o', err.message);
|
||||
|
@ -1120,7 +1170,7 @@ class Storage {
|
|||
'package @{name}@@{version} has been published'
|
||||
);
|
||||
|
||||
return [mergedManifest, versionToPublish];
|
||||
return [mergedManifest, versionToPublish, successResponseMessage];
|
||||
}
|
||||
|
||||
// TODO: pending implementation
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FetchOptions } from '@verdaccio/proxy';
|
||||
import { Config, IPluginStorageFilter, RemoteUser } from '@verdaccio/types';
|
||||
import { Config, IPluginStorageFilter, Manifest, RemoteUser } from '@verdaccio/types';
|
||||
import { RequestOptions } from '@verdaccio/url';
|
||||
|
||||
// @deprecated use IGetPackageOptionsNext
|
||||
|
@ -57,13 +57,10 @@ export type UpdateManifestOptions = {
|
|||
signal: AbortSignal;
|
||||
};
|
||||
|
||||
export type Users = {
|
||||
[key: string]: string;
|
||||
};
|
||||
export interface StarBody {
|
||||
_id: string;
|
||||
_rev: string;
|
||||
users: Users;
|
||||
}
|
||||
|
||||
export type IPluginFilters = IPluginStorageFilter<Config>[];
|
||||
|
||||
/**
|
||||
* When the command `npm star` is executed, the body only contains the following
|
||||
* values in the body.
|
||||
*/
|
||||
export type StarManifestBody = Pick<Manifest, '_id' | 'users' | '_rev'>;
|
||||
|
|
58
packages/store/test/star-utils.test.ts
Normal file
58
packages/store/test/star-utils.test.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { Manifest } from '@verdaccio/types';
|
||||
|
||||
import { generatePackageMetadata } from '../../api/node_modules/@verdaccio/test-helper/build';
|
||||
import { isExecutingStarCommand } from '../src';
|
||||
import { isStarManifest } from '../src';
|
||||
|
||||
describe('Star Utils', () => {
|
||||
describe('isExecutingStarCommand', () => {
|
||||
describe('disallow states', () => {
|
||||
test('should not allow add star with no existing users', () => {
|
||||
expect(isExecutingStarCommand({}, 'foo', false)).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should not allow add star with existing users', () => {
|
||||
expect(isExecutingStarCommand({ bar: true }, 'foo', false)).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should fails if user already exist and us trying to add star', () => {
|
||||
expect(isExecutingStarCommand({ foo: true }, 'foo', true)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('allow states', () => {
|
||||
test('should allow add star with existing users', () => {
|
||||
expect(isExecutingStarCommand({ foo: true }, 'foo', false)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should allow if is adding star and does not exist', () => {
|
||||
expect(isExecutingStarCommand({ foo: true }, 'bar', true)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isStarManifest', () => {
|
||||
test('is not star manifest', () => {
|
||||
const pkg = generatePackageMetadata('foo');
|
||||
expect(isStarManifest(pkg)).toBe(false);
|
||||
});
|
||||
|
||||
test('is not star manifest empty users', () => {
|
||||
const pkg = generatePackageMetadata('foo');
|
||||
pkg.users = {};
|
||||
expect(isStarManifest(pkg)).toBe(false);
|
||||
});
|
||||
|
||||
test('is star manifest', () => {
|
||||
const pkg = generatePackageMetadata('foo', '3.0.0') as Manifest;
|
||||
// Staring a package usually is without versions and the user property within
|
||||
// the manifest body
|
||||
// @ts-expect-error
|
||||
delete pkg.versions;
|
||||
pkg.users = {
|
||||
foo: true,
|
||||
};
|
||||
expect(isStarManifest(pkg)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,31 +0,0 @@
|
|||
import { Manifest } from '@verdaccio/types';
|
||||
|
||||
import { generatePackageMetadata } from '../../api/node_modules/@verdaccio/test-helper/build';
|
||||
import { isStarManifest } from '../src';
|
||||
|
||||
describe('Star Utils', () => {
|
||||
describe('isStarManifest', () => {
|
||||
test('is not star manifest', () => {
|
||||
const pkg = generatePackageMetadata('foo');
|
||||
expect(isStarManifest(pkg)).toBe(false);
|
||||
});
|
||||
|
||||
test('is not star manifest empty users', () => {
|
||||
const pkg = generatePackageMetadata('foo');
|
||||
pkg.users = {};
|
||||
expect(isStarManifest(pkg)).toBe(false);
|
||||
});
|
||||
|
||||
test('is star manifest', () => {
|
||||
const pkg = generatePackageMetadata('foo', '3.0.0') as Manifest;
|
||||
// Staring a package usually is without versions and the user property within
|
||||
// the manifest body
|
||||
// @ts-expect-error
|
||||
delete pkg.versions;
|
||||
pkg.users = {
|
||||
foo: true,
|
||||
};
|
||||
expect(isStarManifest(pkg)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,7 +7,15 @@ import os from 'os';
|
|||
import path from 'path';
|
||||
|
||||
import { Config, getDefaultConfig } from '@verdaccio/config';
|
||||
import { API_ERROR, DIST_TAGS, HEADERS, HEADER_TYPE, errorUtils, fileUtils } from '@verdaccio/core';
|
||||
import {
|
||||
API_ERROR,
|
||||
API_MESSAGE,
|
||||
DIST_TAGS,
|
||||
HEADERS,
|
||||
HEADER_TYPE,
|
||||
errorUtils,
|
||||
fileUtils,
|
||||
} from '@verdaccio/core';
|
||||
import { setup } from '@verdaccio/logger';
|
||||
import {
|
||||
addNewVersion,
|
||||
|
@ -16,7 +24,7 @@ import {
|
|||
generateRemotePackageMetadata,
|
||||
getDeprecatedPackageMetadata,
|
||||
} from '@verdaccio/test-helper';
|
||||
import { AbbreviatedManifest, ConfigYaml, Manifest, Version } from '@verdaccio/types';
|
||||
import { AbbreviatedManifest, ConfigYaml, Manifest, PackageUsers, Version } from '@verdaccio/types';
|
||||
|
||||
import { Storage } from '../src';
|
||||
import manifestFooRemoteNpmjs from './fixtures/manifests/foo-npmjs.json';
|
||||
|
@ -56,6 +64,31 @@ const defaultRequestOptions = {
|
|||
headers: {},
|
||||
};
|
||||
|
||||
const executeStarPackage = async (
|
||||
storage,
|
||||
options: {
|
||||
users: PackageUsers;
|
||||
username: string;
|
||||
name: string;
|
||||
_rev: string;
|
||||
_id?: string;
|
||||
}
|
||||
) => {
|
||||
const { name, _rev, _id, users, username } = options;
|
||||
const starManifest = {
|
||||
_rev,
|
||||
_id,
|
||||
users,
|
||||
};
|
||||
return storage.updateManifest(starManifest, {
|
||||
signal: new AbortController().signal,
|
||||
name,
|
||||
uplinksLook: true,
|
||||
revision: '1',
|
||||
requestOptions: { ...defaultRequestOptions, username },
|
||||
});
|
||||
};
|
||||
|
||||
describe('storage', () => {
|
||||
beforeEach(() => {
|
||||
nock.cleanAll();
|
||||
|
@ -400,6 +433,182 @@ describe('storage', () => {
|
|||
expect(manifest3._rev !== deprecatedManifest._rev).toBeTruthy();
|
||||
});
|
||||
});
|
||||
describe('star', () => {
|
||||
test.each([['foo']])('star package %s', async (pkgName) => {
|
||||
const config = getConfig('deprecate.yaml');
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
||||
await storage.updateManifest(bodyNewManifest, {
|
||||
signal: new AbortController().signal,
|
||||
name: pkgName,
|
||||
uplinksLook: true,
|
||||
revision: '1',
|
||||
requestOptions: defaultRequestOptions,
|
||||
});
|
||||
const message = await executeStarPackage(storage, {
|
||||
_rev: bodyNewManifest._rev,
|
||||
_id: bodyNewManifest._id,
|
||||
name: pkgName,
|
||||
username: 'fooUser',
|
||||
users: { fooUser: true },
|
||||
});
|
||||
expect(message).toEqual(API_MESSAGE.PKG_CHANGED);
|
||||
const manifest1 = (await storage.getPackageByOptions({
|
||||
name: pkgName,
|
||||
uplinksLook: true,
|
||||
requestOptions: defaultRequestOptions,
|
||||
})) as Manifest;
|
||||
|
||||
expect(manifest1?.users).toEqual({
|
||||
fooUser: true,
|
||||
});
|
||||
});
|
||||
|
||||
test.each([['foo']])('should add multiple users to package %s', async (pkgName) => {
|
||||
const mockDate = '2018-01-14T11:17:40.712Z';
|
||||
MockDate.set(mockDate);
|
||||
const config = getConfig('deprecate.yaml');
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
||||
await storage.updateManifest(bodyNewManifest, {
|
||||
signal: new AbortController().signal,
|
||||
name: pkgName,
|
||||
uplinksLook: true,
|
||||
revision: '1',
|
||||
requestOptions: defaultRequestOptions,
|
||||
});
|
||||
const message = await executeStarPackage(storage, {
|
||||
_rev: bodyNewManifest._rev,
|
||||
_id: bodyNewManifest._id,
|
||||
name: pkgName,
|
||||
username: 'fooUser',
|
||||
users: { fooUser: true },
|
||||
});
|
||||
expect(message).toEqual(API_MESSAGE.PKG_CHANGED);
|
||||
|
||||
await executeStarPackage(storage, {
|
||||
_rev: bodyNewManifest._rev,
|
||||
_id: bodyNewManifest._id,
|
||||
name: pkgName,
|
||||
username: 'owner',
|
||||
users: { owner: true },
|
||||
});
|
||||
const manifest1 = (await storage.getPackageByOptions({
|
||||
name: pkgName,
|
||||
uplinksLook: true,
|
||||
requestOptions: defaultRequestOptions,
|
||||
})) as Manifest;
|
||||
|
||||
expect(manifest1?.users).toEqual({
|
||||
fooUser: true,
|
||||
owner: true,
|
||||
});
|
||||
});
|
||||
|
||||
test.each([['foo']])('should ignore duplicate users to package %s', async (pkgName) => {
|
||||
const mockDate = '2018-01-14T11:17:40.712Z';
|
||||
MockDate.set(mockDate);
|
||||
const config = getConfig('deprecate.yaml');
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
||||
await storage.updateManifest(bodyNewManifest, {
|
||||
signal: new AbortController().signal,
|
||||
name: pkgName,
|
||||
uplinksLook: true,
|
||||
revision: '1',
|
||||
requestOptions: defaultRequestOptions,
|
||||
});
|
||||
const message = await executeStarPackage(storage, {
|
||||
_rev: bodyNewManifest._rev,
|
||||
_id: bodyNewManifest._id,
|
||||
name: pkgName,
|
||||
username: 'fooUser',
|
||||
users: { fooUser: true },
|
||||
});
|
||||
expect(message).toEqual(API_MESSAGE.PKG_CHANGED);
|
||||
|
||||
await executeStarPackage(storage, {
|
||||
_rev: bodyNewManifest._rev,
|
||||
_id: bodyNewManifest._id,
|
||||
name: pkgName,
|
||||
username: 'fooUser',
|
||||
users: { fooUser: true },
|
||||
});
|
||||
const manifest1 = (await storage.getPackageByOptions({
|
||||
name: pkgName,
|
||||
uplinksLook: true,
|
||||
requestOptions: defaultRequestOptions,
|
||||
})) as Manifest;
|
||||
|
||||
expect(manifest1?.users).toEqual({
|
||||
fooUser: true,
|
||||
});
|
||||
});
|
||||
|
||||
test.each([['foo']])('should unstar a package %s', async (pkgName) => {
|
||||
const config = getConfig('deprecate.yaml');
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
||||
await storage.updateManifest(bodyNewManifest, {
|
||||
signal: new AbortController().signal,
|
||||
name: pkgName,
|
||||
uplinksLook: true,
|
||||
revision: '1',
|
||||
requestOptions: defaultRequestOptions,
|
||||
});
|
||||
const message = await executeStarPackage(storage, {
|
||||
_rev: bodyNewManifest._rev,
|
||||
_id: bodyNewManifest._id,
|
||||
name: pkgName,
|
||||
username: 'fooUser',
|
||||
users: { fooUser: true },
|
||||
});
|
||||
expect(message).toEqual(API_MESSAGE.PKG_CHANGED);
|
||||
|
||||
await executeStarPackage(storage, {
|
||||
_rev: bodyNewManifest._rev,
|
||||
_id: bodyNewManifest._id,
|
||||
name: pkgName,
|
||||
username: 'fooUser',
|
||||
users: {},
|
||||
});
|
||||
const manifest1 = (await storage.getPackageByOptions({
|
||||
name: pkgName,
|
||||
uplinksLook: true,
|
||||
requestOptions: defaultRequestOptions,
|
||||
})) as Manifest;
|
||||
|
||||
expect(manifest1?.users).toEqual({});
|
||||
});
|
||||
|
||||
test.each([['foo']])('should handle missing username %s', async (pkgName) => {
|
||||
const config = getConfig('deprecate.yaml');
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
const bodyNewManifest = generatePackageMetadata(pkgName, '1.0.0');
|
||||
await storage.updateManifest(bodyNewManifest, {
|
||||
signal: new AbortController().signal,
|
||||
name: pkgName,
|
||||
uplinksLook: true,
|
||||
revision: '1',
|
||||
requestOptions: defaultRequestOptions,
|
||||
});
|
||||
await expect(
|
||||
executeStarPackage(storage, {
|
||||
_rev: bodyNewManifest._rev,
|
||||
_id: bodyNewManifest._id,
|
||||
name: pkgName,
|
||||
// @ts-expect-error
|
||||
username: undefined,
|
||||
users: { fooUser: true },
|
||||
})
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTarballNext', () => {
|
||||
|
|
60
packages/tools/helpers/src/addNewVersion.ts
Normal file
60
packages/tools/helpers/src/addNewVersion.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { Manifest } from '@verdaccio/types';
|
||||
|
||||
import { getTarball } from './utils';
|
||||
|
||||
export function addNewVersion(
|
||||
manifest: Manifest,
|
||||
version: string,
|
||||
isRemote: boolean = true,
|
||||
domain: string = 'http://localhost:5555'
|
||||
): Manifest {
|
||||
const currentVersions = Object.keys(manifest.versions);
|
||||
if (currentVersions.includes(version)) {
|
||||
throw new Error(`Version ${version} already exists`);
|
||||
}
|
||||
|
||||
const newManifest = { ...manifest };
|
||||
newManifest.versions[version] = {
|
||||
name: manifest.name,
|
||||
version,
|
||||
description: manifest.description ?? '',
|
||||
readme: '',
|
||||
main: 'index.js',
|
||||
scripts: { test: 'echo "Error: no test specified" && exit 1' },
|
||||
keywords: [],
|
||||
author: { name: 'User NPM', email: 'user@domain.com' },
|
||||
license: 'ISC',
|
||||
dependencies: { verdaccio: '^2.7.2' },
|
||||
readmeFilename: 'README.md',
|
||||
_id: `${manifest.name}@${version}`,
|
||||
_npmVersion: '5.5.1',
|
||||
_npmUser: { name: 'foo' },
|
||||
dist: {
|
||||
integrity: 'sha512-6gHiERpiDgtb3hjqpQHoPoH4g==',
|
||||
shasum: '2c03764f651a9f016ca0b7620421457b619151b9',
|
||||
tarball: `${domain}/${manifest.name}/-/${getTarball(manifest.name)}-${version}.tgz`,
|
||||
},
|
||||
contributors: [],
|
||||
};
|
||||
// update the latest with the new version
|
||||
newManifest['dist-tags'] = { latest: version };
|
||||
// add new version does not need attachments
|
||||
if (isRemote) {
|
||||
newManifest._distfiles = {
|
||||
...newManifest._distfiles,
|
||||
[`${getTarball(manifest.name)}-${version}.tgz`]: {
|
||||
sha: '2c03764f651a9f016ca0b7620421457b619151b9',
|
||||
url: `${domain}/${manifest.name}/-/${getTarball(manifest.name)}-${version}.tgz`,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
newManifest._attachments = {
|
||||
...newManifest._attachments,
|
||||
[`${getTarball(manifest.name)}-${version}.tgz`]: {
|
||||
shasum: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
|
||||
version: version,
|
||||
},
|
||||
};
|
||||
}
|
||||
return newManifest;
|
||||
}
|
67
packages/tools/helpers/src/generateLocalPackageMetadata.ts
Normal file
67
packages/tools/helpers/src/generateLocalPackageMetadata.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { GenericBody, Manifest } from '@verdaccio/types';
|
||||
|
||||
import { getTarball } from './utils';
|
||||
|
||||
export function generateLocalPackageMetadata(
|
||||
pkgName: string,
|
||||
version = '1.0.0',
|
||||
domain: string = 'http://localhost:5555',
|
||||
time?: GenericBody
|
||||
): Manifest {
|
||||
// @ts-ignore
|
||||
return {
|
||||
_id: pkgName,
|
||||
name: pkgName,
|
||||
description: '',
|
||||
'dist-tags': { ['latest']: version },
|
||||
versions: {
|
||||
[version]: {
|
||||
name: pkgName,
|
||||
version: version,
|
||||
description: 'package generated',
|
||||
main: 'index.js',
|
||||
scripts: {
|
||||
test: 'echo "Error: no test specified" && exit 1',
|
||||
},
|
||||
keywords: [],
|
||||
author: {
|
||||
name: 'User NPM',
|
||||
email: 'user@domain.com',
|
||||
},
|
||||
license: 'ISC',
|
||||
dependencies: {
|
||||
verdaccio: '^2.7.2',
|
||||
},
|
||||
readme: '# test',
|
||||
readmeFilename: 'README.md',
|
||||
_id: `${pkgName}@${version}`,
|
||||
_npmVersion: '5.5.1',
|
||||
_npmUser: {
|
||||
name: 'foo',
|
||||
},
|
||||
dist: {
|
||||
integrity:
|
||||
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cm' +
|
||||
'E6dUBf+XoPoH4g==',
|
||||
shasum: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
|
||||
tarball: `${domain}/${pkgName}\/-\/${getTarball(pkgName)}-${version}.tgz`,
|
||||
},
|
||||
},
|
||||
},
|
||||
time: time ?? {
|
||||
modified: new Date().toISOString(),
|
||||
created: new Date().toISOString(),
|
||||
[version]: new Date().toISOString(),
|
||||
},
|
||||
readme: '# test',
|
||||
_attachments: {
|
||||
[`${getTarball(pkgName)}-${version}.tgz`]: {
|
||||
shasum: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
|
||||
version: version,
|
||||
},
|
||||
},
|
||||
_uplinks: {},
|
||||
_distfiles: {},
|
||||
_rev: '',
|
||||
};
|
||||
}
|
|
@ -1,236 +1,7 @@
|
|||
import { FullRemoteManifest, GenericBody, Manifest, Version, Versions } from '@verdaccio/types';
|
||||
import { Manifest } from '@verdaccio/types';
|
||||
|
||||
export interface DistTags {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
const getTarball = (name: string): string => {
|
||||
const r = name.split('/');
|
||||
if (r.length === 1) {
|
||||
return r[0];
|
||||
} else {
|
||||
return r[1];
|
||||
}
|
||||
};
|
||||
|
||||
export function addNewVersion(
|
||||
manifest: Manifest,
|
||||
version: string,
|
||||
isRemote: boolean = true,
|
||||
domain: string = 'http://localhost:5555'
|
||||
): Manifest {
|
||||
const currentVersions = Object.keys(manifest.versions);
|
||||
if (currentVersions.includes(version)) {
|
||||
throw new Error(`Version ${version} already exists`);
|
||||
}
|
||||
|
||||
const newManifest = { ...manifest };
|
||||
newManifest.versions[version] = {
|
||||
name: manifest.name,
|
||||
version,
|
||||
description: manifest.description ?? '',
|
||||
readme: '',
|
||||
main: 'index.js',
|
||||
scripts: { test: 'echo "Error: no test specified" && exit 1' },
|
||||
keywords: [],
|
||||
author: { name: 'User NPM', email: 'user@domain.com' },
|
||||
license: 'ISC',
|
||||
dependencies: { verdaccio: '^2.7.2' },
|
||||
readmeFilename: 'README.md',
|
||||
_id: `${manifest.name}@${version}`,
|
||||
_npmVersion: '5.5.1',
|
||||
_npmUser: { name: 'foo' },
|
||||
dist: {
|
||||
integrity: 'sha512-6gHiERpiDgtb3hjqpQHoPoH4g==',
|
||||
shasum: '2c03764f651a9f016ca0b7620421457b619151b9',
|
||||
tarball: `${domain}/${manifest.name}/-/${getTarball(manifest.name)}-${version}.tgz`,
|
||||
},
|
||||
contributors: [],
|
||||
};
|
||||
// update the latest with the new version
|
||||
newManifest['dist-tags'] = { latest: version };
|
||||
// add new version does not need attachments
|
||||
if (isRemote) {
|
||||
newManifest._distfiles = {
|
||||
...newManifest._distfiles,
|
||||
[`${getTarball(manifest.name)}-${version}.tgz`]: {
|
||||
sha: '2c03764f651a9f016ca0b7620421457b619151b9',
|
||||
url: `${domain}/${manifest.name}/-/${getTarball(manifest.name)}-${version}.tgz`,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
newManifest._attachments = {
|
||||
...newManifest._attachments,
|
||||
[`${getTarball(manifest.name)}-${version}.tgz`]: {
|
||||
shasum: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
|
||||
version: version,
|
||||
},
|
||||
};
|
||||
}
|
||||
return newManifest;
|
||||
}
|
||||
|
||||
export function generateLocalPackageMetadata(
|
||||
pkgName: string,
|
||||
version = '1.0.0',
|
||||
domain: string = 'http://localhost:5555',
|
||||
time?: GenericBody
|
||||
): Manifest {
|
||||
// @ts-ignore
|
||||
return {
|
||||
_id: pkgName,
|
||||
name: pkgName,
|
||||
description: '',
|
||||
'dist-tags': { ['latest']: version },
|
||||
versions: {
|
||||
[version]: {
|
||||
name: pkgName,
|
||||
version: version,
|
||||
description: 'package generated',
|
||||
main: 'index.js',
|
||||
scripts: {
|
||||
test: 'echo "Error: no test specified" && exit 1',
|
||||
},
|
||||
keywords: [],
|
||||
author: {
|
||||
name: 'User NPM',
|
||||
email: 'user@domain.com',
|
||||
},
|
||||
license: 'ISC',
|
||||
dependencies: {
|
||||
verdaccio: '^2.7.2',
|
||||
},
|
||||
readme: '# test',
|
||||
readmeFilename: 'README.md',
|
||||
_id: `${pkgName}@${version}`,
|
||||
_npmVersion: '5.5.1',
|
||||
_npmUser: {
|
||||
name: 'foo',
|
||||
},
|
||||
dist: {
|
||||
integrity:
|
||||
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cm' +
|
||||
'E6dUBf+XoPoH4g==',
|
||||
shasum: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
|
||||
tarball: `${domain}/${pkgName}\/-\/${getTarball(pkgName)}-${version}.tgz`,
|
||||
},
|
||||
},
|
||||
},
|
||||
time: time ?? {
|
||||
modified: new Date().toISOString(),
|
||||
created: new Date().toISOString(),
|
||||
[version]: new Date().toISOString(),
|
||||
},
|
||||
readme: '# test',
|
||||
_attachments: {
|
||||
[`${getTarball(pkgName)}-${version}.tgz`]: {
|
||||
shasum: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
|
||||
version: version,
|
||||
},
|
||||
},
|
||||
_uplinks: {},
|
||||
_distfiles: {},
|
||||
_rev: '',
|
||||
};
|
||||
}
|
||||
|
||||
export function generateRemotePackageMetadata(
|
||||
pkgName: string,
|
||||
version = '1.0.0',
|
||||
domain: string = 'http://localhost:5555',
|
||||
versions: string[] = []
|
||||
): FullRemoteManifest {
|
||||
// @ts-ignore
|
||||
const generateVersion = (version: string): Version => {
|
||||
const metadata = {
|
||||
name: pkgName,
|
||||
version: version,
|
||||
description: 'package generated',
|
||||
main: 'index.js',
|
||||
scripts: {
|
||||
test: 'echo "Error: no test specified" && exit 1',
|
||||
},
|
||||
keywords: [],
|
||||
author: {
|
||||
name: 'User NPM',
|
||||
email: 'user@domain.com',
|
||||
},
|
||||
license: 'ISC',
|
||||
dependencies: {
|
||||
verdaccio: '^2.7.2',
|
||||
},
|
||||
readme: '# test',
|
||||
readmeFilename: 'README.md',
|
||||
_id: `${pkgName}@${version}`,
|
||||
_npmVersion: '5.5.1',
|
||||
_npmUser: {
|
||||
name: 'foo',
|
||||
},
|
||||
dist: {
|
||||
integrity:
|
||||
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cm' +
|
||||
'E6dUBf+XoPoH4g==',
|
||||
shasum: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
|
||||
tarball: `${domain}\/${pkgName}\/-\/${getTarball(pkgName)}-${version}.tgz`,
|
||||
},
|
||||
};
|
||||
|
||||
return metadata;
|
||||
};
|
||||
const mappedVersions: Versions = versions.reduce((acc, v) => {
|
||||
acc[v] = generateVersion(v);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const mappedTimes: GenericBody = versions.reduce((acc, v) => {
|
||||
const date = new Date(Date.now());
|
||||
acc[v] = date.toISOString();
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
_id: pkgName,
|
||||
name: pkgName,
|
||||
description: '',
|
||||
'dist-tags': { ['latest']: version },
|
||||
versions: {
|
||||
[version]: generateVersion(version),
|
||||
...mappedVersions,
|
||||
},
|
||||
time: {
|
||||
modified: '2019-06-13T06:44:45.747Z',
|
||||
created: '2019-06-13T06:44:45.747Z',
|
||||
[version]: '2019-06-13T06:44:45.747Z',
|
||||
...mappedTimes,
|
||||
},
|
||||
maintainers: [
|
||||
{
|
||||
name: 'foo',
|
||||
email: 'foo@foo.com',
|
||||
},
|
||||
],
|
||||
author: {
|
||||
name: 'foo',
|
||||
},
|
||||
readme: '# test',
|
||||
_rev: '12-c8fe8a9c79fa57a87347a0213e6f2548',
|
||||
};
|
||||
}
|
||||
|
||||
export function getDeprecatedPackageMetadata(
|
||||
pkgName: string,
|
||||
version = '1.0.0',
|
||||
distTags: DistTags = { ['latest']: version },
|
||||
deprecated = 'default deprecated message',
|
||||
rev = 'rev-foo'
|
||||
): Manifest {
|
||||
const manifest = generatePackageMetadata(pkgName, version, distTags);
|
||||
// deprecated message requires empty attachments
|
||||
manifest._attachments = {};
|
||||
manifest._rev = rev;
|
||||
manifest.versions[version].deprecated = deprecated;
|
||||
return manifest;
|
||||
}
|
||||
import { DistTags } from './types';
|
||||
import { getTarball } from './utils';
|
||||
|
||||
export function generatePackageMetadata(
|
||||
pkgName: string,
|
||||
|
|
86
packages/tools/helpers/src/generateRemotePackageMetadata.ts
Normal file
86
packages/tools/helpers/src/generateRemotePackageMetadata.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
import { FullRemoteManifest, GenericBody, Version, Versions } from '@verdaccio/types';
|
||||
|
||||
import { getTarball } from './utils';
|
||||
|
||||
export function generateRemotePackageMetadata(
|
||||
pkgName: string,
|
||||
version = '1.0.0',
|
||||
domain: string = 'http://localhost:5555',
|
||||
versions: string[] = []
|
||||
): FullRemoteManifest {
|
||||
// @ts-ignore
|
||||
const generateVersion = (version: string): Version => {
|
||||
const metadata = {
|
||||
name: pkgName,
|
||||
version: version,
|
||||
description: 'package generated',
|
||||
main: 'index.js',
|
||||
scripts: {
|
||||
test: 'echo "Error: no test specified" && exit 1',
|
||||
},
|
||||
keywords: [],
|
||||
author: {
|
||||
name: 'User NPM',
|
||||
email: 'user@domain.com',
|
||||
},
|
||||
license: 'ISC',
|
||||
dependencies: {
|
||||
verdaccio: '^2.7.2',
|
||||
},
|
||||
readme: '# test',
|
||||
readmeFilename: 'README.md',
|
||||
_id: `${pkgName}@${version}`,
|
||||
_npmVersion: '5.5.1',
|
||||
_npmUser: {
|
||||
name: 'foo',
|
||||
},
|
||||
dist: {
|
||||
integrity:
|
||||
'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cm' +
|
||||
'E6dUBf+XoPoH4g==',
|
||||
shasum: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
|
||||
tarball: `${domain}\/${pkgName}\/-\/${getTarball(pkgName)}-${version}.tgz`,
|
||||
},
|
||||
};
|
||||
|
||||
return metadata;
|
||||
};
|
||||
const mappedVersions: Versions = versions.reduce((acc, v) => {
|
||||
acc[v] = generateVersion(v);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const mappedTimes: GenericBody = versions.reduce((acc, v) => {
|
||||
const date = new Date(Date.now());
|
||||
acc[v] = date.toISOString();
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
_id: pkgName,
|
||||
name: pkgName,
|
||||
description: '',
|
||||
'dist-tags': { ['latest']: version },
|
||||
versions: {
|
||||
[version]: generateVersion(version),
|
||||
...mappedVersions,
|
||||
},
|
||||
time: {
|
||||
modified: '2019-06-13T06:44:45.747Z',
|
||||
created: '2019-06-13T06:44:45.747Z',
|
||||
[version]: '2019-06-13T06:44:45.747Z',
|
||||
...mappedTimes,
|
||||
},
|
||||
maintainers: [
|
||||
{
|
||||
name: 'foo',
|
||||
email: 'foo@foo.com',
|
||||
},
|
||||
],
|
||||
author: {
|
||||
name: 'foo',
|
||||
},
|
||||
readme: '# test',
|
||||
_rev: '12-c8fe8a9c79fa57a87347a0213e6f2548',
|
||||
};
|
||||
}
|
19
packages/tools/helpers/src/getDeprecatedPackageMetadata.ts
Normal file
19
packages/tools/helpers/src/getDeprecatedPackageMetadata.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { Manifest } from '@verdaccio/types';
|
||||
|
||||
import { generatePackageMetadata } from './generatePackageMetadata';
|
||||
import { DistTags } from './types';
|
||||
|
||||
export function getDeprecatedPackageMetadata(
|
||||
pkgName: string,
|
||||
version = '1.0.0',
|
||||
distTags: DistTags = { ['latest']: version },
|
||||
deprecated = 'default deprecated message',
|
||||
rev = 'rev-foo'
|
||||
): Manifest {
|
||||
const manifest = generatePackageMetadata(pkgName, version, distTags);
|
||||
// deprecated message requires empty attachments
|
||||
manifest._attachments = {};
|
||||
manifest._rev = rev;
|
||||
manifest.versions[version].deprecated = deprecated;
|
||||
return manifest;
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
export {
|
||||
generatePackageMetadata,
|
||||
addNewVersion,
|
||||
generateLocalPackageMetadata,
|
||||
generateRemotePackageMetadata,
|
||||
getDeprecatedPackageMetadata,
|
||||
} from './generatePackageMetadata';
|
||||
export { generatePackageMetadata } from './generatePackageMetadata';
|
||||
export { getDeprecatedPackageMetadata } from './getDeprecatedPackageMetadata';
|
||||
export { generateLocalPackageMetadata } from './generateLocalPackageMetadata';
|
||||
export { generateRemotePackageMetadata } from './generateRemotePackageMetadata';
|
||||
export { addNewVersion } from './addNewVersion';
|
||||
export { generatePublishNewVersionManifest } from './generatePublishNewVersionManifest';
|
||||
export { initializeServer } from './initializeServer';
|
||||
export { publishVersion } from './actions';
|
||||
|
|
3
packages/tools/helpers/src/types.ts
Normal file
3
packages/tools/helpers/src/types.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export interface DistTags {
|
||||
[key: string]: string;
|
||||
}
|
|
@ -11,3 +11,12 @@ import path from 'path';
|
|||
export function createTempFolder(prefix: string): string {
|
||||
return fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), prefix));
|
||||
}
|
||||
|
||||
export const getTarball = (name: string): string => {
|
||||
const r = name.split('/');
|
||||
if (r.length === 1) {
|
||||
return r[0];
|
||||
} else {
|
||||
return r[1];
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { addNewVersion, generatePackageMetadata } from '../src';
|
||||
import {
|
||||
addNewVersion,
|
||||
generateLocalPackageMetadata,
|
||||
generatePackageMetadata,
|
||||
generateRemotePackageMetadata,
|
||||
} from '../src/generatePackageMetadata';
|
||||
} from '../src';
|
||||
|
||||
describe('generate metadata', () => {
|
||||
describe('generatePackageMetadata', () => {
|
||||
|
|
|
@ -275,10 +275,14 @@ export class ServerQuery {
|
|||
});
|
||||
}
|
||||
|
||||
public async addPackage(name: string, version: string = '1.0.0'): Promise<ResponseAssert> {
|
||||
public async addPackage(
|
||||
name: string,
|
||||
version: string = '1.0.0',
|
||||
message = API_MESSAGE.PKG_CREATED
|
||||
): Promise<ResponseAssert> {
|
||||
return (await this.putPackage(name, generatePackageMetadata(name, version)))
|
||||
.status(HTTP_STATUS.CREATED)
|
||||
.body_ok(API_MESSAGE.PKG_CREATED);
|
||||
.body_ok(message);
|
||||
}
|
||||
|
||||
public async addPackageAssert(name: string, version: string = '1.0.0'): Promise<ResponseAssert> {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ConfigBuilder } from '@verdaccio/config';
|
||||
import { HTTP_STATUS, constants, fileUtils } from '@verdaccio/core';
|
||||
import { API_MESSAGE, HTTP_STATUS, constants, fileUtils } from '@verdaccio/core';
|
||||
|
||||
import { Registry, ServerQuery } from '../src/server';
|
||||
|
||||
|
@ -41,8 +41,8 @@ describe('basic test endpoints', () => {
|
|||
test('shoud unpublish the whole package of many published', async function () {
|
||||
const server = new ServerQuery(registry.getRegistryUrl());
|
||||
await server.addPackage('unpublish-new-package', '1.0.0');
|
||||
await server.addPackage('unpublish-new-package', '1.0.1');
|
||||
await server.addPackage('unpublish-new-package', '1.0.2');
|
||||
await server.addPackage('unpublish-new-package', '1.0.1', API_MESSAGE.PKG_CHANGED);
|
||||
await server.addPackage('unpublish-new-package', '1.0.2', API_MESSAGE.PKG_CHANGED);
|
||||
(await server.getPackage('unpublish-new-package')).status(HTTP_STATUS.OK);
|
||||
(await server.removePackage('unpublish-new-package', '_rev')).status(HTTP_STATUS.CREATED);
|
||||
(await server.getPackage('unpublish-new-package')).status(HTTP_STATUS.NOT_FOUND);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ConfigBuilder } from '@verdaccio/config';
|
||||
import { constants, fileUtils } from '@verdaccio/core';
|
||||
import { API_MESSAGE, constants, fileUtils } from '@verdaccio/core';
|
||||
|
||||
import { Registry, ServerQuery } from '../src/server';
|
||||
|
||||
|
@ -37,12 +37,13 @@ describe('race publishing packages', () => {
|
|||
|
||||
for (const time of Array.from(Array(times).keys())) {
|
||||
try {
|
||||
await server.addPackage('race-pkg', `1.0.${time}`);
|
||||
let message = success === 0 ? API_MESSAGE.PKG_CREATED : API_MESSAGE.PKG_CHANGED;
|
||||
await server.addPackage('race-pkg', `1.0.${time}`, message);
|
||||
success++;
|
||||
} catch (error) {
|
||||
console.error('this should not trigger', error);
|
||||
}
|
||||
}
|
||||
expect(success).toBe(times);
|
||||
}, 30000);
|
||||
}, 40000);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue