0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-12-16 21:56:25 -05:00

fix: handle upload scoped tarball and add new deprecations (#3340)

* chore: add local publish support

* chore: fix upload scoped tarball

* add e2e
This commit is contained in:
Juan Picado 2022-09-02 20:40:12 +02:00 committed by GitHub
parent efa2efe531
commit b849128ded
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1617 additions and 1156 deletions

View file

@ -0,0 +1,11 @@
---
'@verdaccio/api': patch
'@verdaccio/cli': patch
'@verdaccio/core': patch
'@verdaccio/types': patch
'@verdaccio/store': patch
'@verdaccio/test-helper': patch
'@verdaccio/local-publish': patch
---
fix: handle upload scoped tarball

View file

@ -183,7 +183,7 @@ a report in our [issue tracker](https://github.com/verdaccio/verdaccio/issues).
- _Features clearly flagged as not supported_
- _Node.js issues installation in any platform_: If you cannot install the
global package (this is considered external issue)
- Any ticket which has beed flagged as an [external issue
- Any ticket which has been flagged as an [external issue
](https://github.com/verdaccio/verdaccio/labels/external-issue)
If you intend to report a **security** issue, please follow our [Security policy
@ -234,7 +234,7 @@ the project. Adding in context and the use-case will really help!
- A detailed description the advantages of your request
- Whether or not it's compatible with `npm`, `pnpm` and [_yarn classic_
](https://github.com/yarnpkg/yarn) or [_yarn berry_
](https://github.com/yarnpkg/yarn) or [_yarn modern_
](https://github.com/yarnpkg/berry).
- A potential implementation or design
- Whatever else is on your mind! 🤓
@ -420,3 +420,25 @@ If you want to develop your own plugin:
3. You are free to host your plugin in your repository
4. Provide a detailed description of your plugin to help users understand how to
use it
## Testing your changes in a local registry
Once you have perform your changes in the code base, the build and tests passes you can publish a local version:
- Ensure you have build all modules (or the one you have modified)
- Run `pnpm local:publish:release` to launch a local registry and publish all packages into it. This command will be alive until server is killed (Control Key + C)
```
pnpm build
pnpm local:publish:release
```
The last step consist on install globally the package from the local registry.
```
npm i -g verdaccio --registry=http://localhost:4873
verdaccio
```
If you perform more changes in the source code, repeat this process, there is not _hot reloading_ support.

View file

@ -40,7 +40,7 @@
"@babel/runtime": "7.18.9",
"@dianmora/contributors": "5.0.0",
"@changesets/changelog-github": "0.4.6",
"@changesets/cli": "2.15.0",
"@changesets/cli": "2.24.4",
"@changesets/get-dependents-graph": "1.3.3",
"@crowdin/cli": "3.7.10",
"@trivago/prettier-plugin-sort-imports": "3.3.0",
@ -93,7 +93,7 @@
"jest-junit": "12.3.0",
"kleur": "3.0.3",
"lint-staged": "11.2.6",
"nock": "12.0.3",
"nock": "13.2.9",
"node-fetch": "cjs",
"nodemon": "2.0.19",
"npm-run-all": "4.1.5",
@ -146,7 +146,10 @@
"crowdin:upload": "crowdin upload sources --auto-update --config ./crowdin.yaml",
"crowdin:download": "crowdin download --verbose --config ./crowdin.yaml",
"crowdin:sync": "pnpm crowdin:upload && pnpm crowdin:download --verbose",
"postinstall": "husky install"
"postinstall": "husky install",
"local:registry": "pnpm start --filter ...@verdaccio/local-publish",
"local:publish": "cross-env npm_config_registry=http://localhost:4873 pnpm ci:publish",
"local:publish:release": "concurrently \"pnpm local:registry\" \"pnpm local:publish\""
},
"engines": {
"node": ">=16.5",

View file

@ -59,6 +59,7 @@ export default function (route: Router, auth: IAuth, storage: Storage): void {
try {
const stream = (await storage.getTarballNext(pkg, filename, {
signal: abort.signal,
// TODO: review why this param
// enableRemote: true,
})) as any;

View file

@ -81,7 +81,7 @@ const debug = buildDebug('verdaccio:api:publish');
* specific flag for star or un start.
* The URL for star is similar to the unpublish (change package format)
*
* npm has no enpoint for star a package, rather mutate the metadata and acts as, the difference
* npm has no endpoint for star a package, rather mutate the metadata and acts as, the difference
* is the users property which is part of the payload and the body only includes
*
* {
@ -206,13 +206,12 @@ export function publishPackageNext(storage: Storage): any {
return next({
// TODO: this could be also Package Updated based on the
// action, deprecate, star, publish new version, or create a package
// the mssage some return from the method
// the message some return from the method
ok: API_MESSAGE.PKG_CREATED,
success: true,
});
} catch (err: any) {
// TODO: review if we need the abort controller here
ac.abort();
next(err);
}
};

View file

@ -0,0 +1,26 @@
auth:
htpasswd:
file: ./htpasswd-publish-proxy
web:
enable: true
title: verdaccio
uplinks:
npmjs:
url: https://registry.npmjs.org/
log: { type: stdout, format: pretty, level: trace }
packages:
'@*/*':
access: $all
publish: $anonymous
unpublish: $anonymous
proxy: npmjs
'**':
access: $all
publish: $anonymous
unpublish: $anonymous
proxy: npmjs
_debug: true

View file

@ -6,64 +6,100 @@ import { Storage } from '@verdaccio/store';
import { initializeServer, publishVersion } from './_helper';
describe('package', () => {
let app;
beforeEach(async () => {
app = await initializeServer('package.yaml');
describe('get tarball', () => {
let app;
beforeEach(async () => {
app = await initializeServer('package.yaml');
});
test.each([
['foo', 'foo-1.0.0.tgz'],
['@scope/foo', 'foo-1.0.0.tgz'],
])('should return a file tarball', async (pkg, fileName) => {
await publishVersion(app, pkg, '1.0.0');
const response = await supertest(app)
.get(`/${pkg}/-/${fileName}`)
.set(HEADERS.ACCEPT, HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.OCTET_STREAM)
.expect(HTTP_STATUS.OK);
expect(Buffer.from(response.body).toString('utf8')).toBeDefined();
});
test.each([
['foo', 'foo-1.0.0.tgz'],
['@scope/foo', 'foo-1.0.0.tgz'],
])('should fails if tarball does not exist', async (pkg, fileName) => {
await publishVersion(app, pkg, '1.0.1');
return await supertest(app)
.get(`/${pkg}/-/${fileName}`)
.set(HEADERS.ACCEPT, HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.OCTET_STREAM)
.expect(HTTP_STATUS.NOT_FOUND);
});
test.todo('check content length file header');
test.todo('fails on file was aborted');
});
test.each([['foo'], ['@scope/foo']])('should return a foo private package', async (pkg) => {
await publishVersion(app, pkg, '1.0.0');
const response = await supertest(app)
.get(`/${pkg}`)
.set(HEADERS.ACCEPT, HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK);
expect(response.body.name).toEqual(pkg);
describe('get package', () => {
let app;
beforeEach(async () => {
app = await initializeServer('package.yaml');
});
test.each([['foo'], ['@scope/foo']])('should return a foo private package', async (pkg) => {
await publishVersion(app, pkg, '1.0.0');
const response = await supertest(app)
.get(`/${pkg}`)
.set(HEADERS.ACCEPT, HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK);
expect(response.body.name).toEqual(pkg);
});
test.each([['foo'], ['@scope/foo']])(
'should return a foo private package by version',
async (pkg) => {
await publishVersion(app, pkg, '1.0.0');
const response = await supertest(app)
.get(`/${pkg}`)
.set(HEADERS.ACCEPT, HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK);
expect(response.body.name).toEqual(pkg);
}
);
test.each([['foo'], ['@scope/foo']])(
'should return a foo private package by version',
async (pkg) => {
await publishVersion(app, pkg, '1.0.0');
const response = await supertest(app)
.get(`/${pkg}`)
.set(HEADERS.ACCEPT, HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK);
expect(response.body.name).toEqual(pkg);
}
);
test.each([['foo-abbreviated'], ['@scope/foo-abbreviated']])(
'should return abbreviated local manifest',
async (pkg) => {
await publishVersion(app, pkg, '1.0.0');
const response = await supertest(app)
.get(`/${pkg}`)
.set(HEADERS.ACCEPT, HEADERS.JSON)
.set(HEADERS.ACCEPT, Storage.ABBREVIATED_HEADER)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK);
expect(response.body.name).toEqual(pkg);
expect(response.body.time).toBeDefined();
expect(response.body.modified).toBeDefined();
expect(response.body[DIST_TAGS]).toEqual({ latest: '1.0.0' });
expect(response.body.readme).not.toBeDefined();
expect(response.body._rev).not.toBeDefined();
expect(response.body.users).not.toBeDefined();
}
);
});
test.each([['foo'], ['@scope/foo']])(
'should return a foo private package by version',
async (pkg) => {
await publishVersion(app, pkg, '1.0.0');
const response = await supertest(app)
.get(`/${pkg}`)
.set(HEADERS.ACCEPT, HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK);
expect(response.body.name).toEqual(pkg);
}
);
test.each([['foo'], ['@scope/foo']])(
'should return a foo private package by version',
async (pkg) => {
await publishVersion(app, pkg, '1.0.0');
const response = await supertest(app)
.get(`/${pkg}`)
.set(HEADERS.ACCEPT, HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK);
expect(response.body.name).toEqual(pkg);
}
);
test.each([['foo-abbreviated'], ['@scope/foo-abbreviated']])(
'should return abbreviated local manifest',
async (pkg) => {
await publishVersion(app, pkg, '1.0.0');
const response = await supertest(app)
.get(`/${pkg}`)
.set(HEADERS.ACCEPT, HEADERS.JSON)
.set(HEADERS.ACCEPT, Storage.ABBREVIATED_HEADER)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.OK);
expect(response.body.name).toEqual(pkg);
expect(response.body.time).toBeDefined();
expect(response.body.modified).toBeDefined();
expect(response.body[DIST_TAGS]).toEqual({ latest: '1.0.0' });
expect(response.body.readme).not.toBeDefined();
expect(response.body._rev).not.toBeDefined();
expect(response.body.users).not.toBeDefined();
}
);
});

View file

@ -1,11 +1,13 @@
import nock from 'nock';
import { basename } from 'path';
import supertest from 'supertest';
import { HTTP_STATUS } from '@verdaccio/core';
import { API_ERROR, API_MESSAGE, HEADERS, HEADER_TYPE } from '@verdaccio/core';
import { generatePackageMetadata } from '@verdaccio/test-helper';
import { generatePackageMetadata, generateRemotePackageMetadata } from '@verdaccio/test-helper';
import { $RequestExtend, $ResponseExtend } from '../../types/custom';
import { initializeServer, publishVersion } from './_helper';
import { getPackage, initializeServer, publishVersion } from './_helper';
const mockApiJWTmiddleware = jest.fn(
() =>
@ -33,31 +35,8 @@ jest.mock('@verdaccio/auth', () => ({
},
}));
// const mockStorage = jest.fn(() => {
// const { Storage } = jest.requireActual('@verdaccio/store');
// return {
// Storage: class extends Storage {
// addPackage(name, metadata, cb) {
// super.addPackage(name, metadata, cb);
// }
// }
// };
// });
// jest.mock('@verdaccio/store', () => {
// const { Storage } = jest.requireActual('@verdaccio/store');
// return ({
// Storage: class extends Storage {
// addPackage(name, metadata, cb) {
// // super.addPackage(name, metadata, cb);
// return mockStorage(name, metadata, cb);
// }
// }
// })
// });
describe('publish', () => {
describe('handle invalid publish formats', () => {
describe('handle errors', () => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
test('should fail on publish a bad _attachments package', async () => {
@ -101,134 +80,179 @@ describe('publish', () => {
});
describe('publish a package', () => {
test('should publish a package', async () => {
const app = await initializeServer('publish.yaml');
return new Promise((resolve) => {
publishVersion(app, 'foo', '1.0.0')
.expect(HTTP_STATUS.CREATED)
.then((response) => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
resolve(response);
});
});
});
test('should publish a new package', async () => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
const app = await initializeServer('publish.yaml');
return new Promise((resolve) => {
supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(Object.assign({}, pkgMetadata)))
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.CREATED)
.then((response) => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
resolve(response);
});
});
});
test('should publish a new package with no readme', async () => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
const app = await initializeServer('publish.yaml');
return new Promise((resolve) => {
supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(
JSON.stringify(
Object.assign({}, pkgMetadata, {
versions: {
['1.0.0']: {
readme: null,
},
},
})
)
)
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.CREATED)
.then((response) => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
resolve(response);
});
});
});
});
test('should fails on publish a duplicated package', async () => {
const app = await initializeServer('publish.yaml');
await publishVersion(app, 'foo', '1.0.0');
return new Promise((resolve) => {
publishVersion(app, 'foo', '1.0.0')
.expect(HTTP_STATUS.CONFLICT)
.then((response) => {
expect(response.body.error).toEqual(API_ERROR.PACKAGE_EXIST);
resolve(response);
describe('no proxies setup', () => {
test.each([['foo', '@scope/foo']])('should publish a package', async (pkgName) => {
const app = await initializeServer('publish.yaml');
return new Promise((resolve) => {
publishVersion(app, pkgName, '1.0.0')
.expect(HTTP_STATUS.CREATED)
.then((response) => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
resolve(response);
});
});
});
test.each([['foo', '@scope/foo']])('should publish a new package', async (pkgName) => {
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
const app = await initializeServer('publish.yaml');
return new Promise((resolve) => {
supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(JSON.stringify(Object.assign({}, pkgMetadata)))
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.CREATED)
.then((response) => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
resolve(response);
});
});
});
test('should publish a new package with no readme', async () => {
const pkgName = 'test';
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
const app = await initializeServer('publish.yaml');
return new Promise((resolve) => {
supertest(app)
.put(`/${encodeURIComponent(pkgName)}`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.send(
JSON.stringify(
Object.assign({}, pkgMetadata, {
versions: {
['1.0.0']: {
readme: null,
},
},
})
)
)
.set('accept', HEADERS.GZIP)
.expect(HTTP_STATUS.CREATED)
.then((response) => {
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
resolve(response);
});
});
});
});
describe('proxies setup', () => {
test.each([['foo', '@scope%2Ffoo']])(
'should publish a a patch package that already exist on a remote',
async (pkgName) => {
const upstreamManifest = generateRemotePackageMetadata(
pkgName,
'1.0.0',
'https://registry.npmjs.org',
['1.0.1', '1.0.2', '1.0.3']
);
nock('https://registry.npmjs.org').get(`/${pkgName}`).reply(200, upstreamManifest);
const app = await initializeServer('publish-proxy.yaml');
const manifest = await getPackage(app, '', decodeURIComponent(pkgName));
expect(manifest.body.name).toEqual(decodeURIComponent(pkgName));
const response = await publishVersion(
app,
decodeURIComponent(pkgName),
'1.0.1-patch'
).expect(HTTP_STATUS.CREATED);
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
const response2 = await publishVersion(
app,
decodeURIComponent(pkgName),
'1.0.2-patch'
).expect(HTTP_STATUS.CREATED);
expect(response2.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
}
);
});
});
test.each([['foo', '@scope/foo']])(
'should fails on publish a duplicated package',
async (pkgName) => {
const app = await initializeServer('publish.yaml');
await publishVersion(app, pkgName, '1.0.0');
return new Promise((resolve) => {
publishVersion(app, pkgName, '1.0.0')
.expect(HTTP_STATUS.CONFLICT)
.then((response) => {
expect(response.body.error).toEqual(API_ERROR.PACKAGE_EXIST);
resolve(response);
});
});
}
);
describe('unpublish a package', () => {
test('should unpublish entirely a package', async () => {
test.each([['foo', '@scope/foo']])('should unpublish entirely a package', async (pkgName) => {
const app = await initializeServer('publish.yaml');
await publishVersion(app, 'foo', '1.0.0');
await publishVersion(app, pkgName, '1.0.0');
const response = await supertest(app)
// FIXME: should be filtered by revision to avoid
// conflicts
.delete(`/${encodeURIComponent('foo')}/-rev/xxx`)
.delete(`/${encodeURIComponent(pkgName)}/-rev/xxx`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.CREATED);
expect(response.body.ok).toEqual(API_MESSAGE.PKG_REMOVED);
// package should be completely un published
await supertest(app)
.get('/foo')
.get(`/${pkgName}`)
.set('Accept', HEADERS.JSON)
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
.expect(HTTP_STATUS.NOT_FOUND);
});
test('should fails unpublish entirely a package', async () => {
const app = await initializeServer('publish.yaml');
const response = await supertest(app)
.delete(`/${encodeURIComponent('foo')}/-rev/1cf3-fe3`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.NOT_FOUND);
expect(response.body.error).toEqual(API_ERROR.NO_PACKAGE);
});
test.each([['foo', '@scope/foo']])(
'should fails unpublish entirely a package',
async (pkgName) => {
const app = await initializeServer('publish.yaml');
const response = await supertest(app)
.delete(`/${encodeURIComponent(pkgName)}/-rev/1cf3-fe3`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.NOT_FOUND);
expect(response.body.error).toEqual(API_ERROR.NO_PACKAGE);
}
);
test('should fails remove a tarball of a package does not exist', async () => {
const app = await initializeServer('publish.yaml');
const response = await supertest(app)
.delete(`/foo/-/foo-1.0.3.tgz/-rev/revision`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.NOT_FOUND);
expect(response.body.error).toEqual(API_ERROR.NO_PACKAGE);
});
test.each([['foo', '@scope/foo']])(
'should fails remove a tarball of a package does not exist',
async (pkgName) => {
const app = await initializeServer('publish.yaml');
const response = await supertest(app)
.delete(`/${pkgName}/-/${basename(pkgName)}-1.0.3.tgz/-rev/revision`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.NOT_FOUND);
expect(response.body.error).toEqual(API_ERROR.NO_PACKAGE);
}
);
test('should fails on try remove a tarball does not exist', async () => {
const app = await initializeServer('publish.yaml');
await publishVersion(app, 'foo', '1.0.0');
const response = await supertest(app)
.delete(`/foo/-/foo-1.0.3.tgz/-rev/revision`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.NOT_FOUND);
expect(response.body.error).toEqual(API_ERROR.NO_SUCH_FILE);
});
test.each([['foo', '@scope/foo']])(
'should fails on try remove a tarball does not exist',
async (pkgName) => {
const app = await initializeServer('publish.yaml');
await publishVersion(app, pkgName, '1.0.0');
const response = await supertest(app)
.delete(`/${pkgName}/-/${basename(pkgName)}-1.0.3.tgz/-rev/revision`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.NOT_FOUND);
expect(response.body.error).toEqual(API_ERROR.NO_SUCH_FILE);
}
);
test('should remove a tarball that does exist', async () => {
const app = await initializeServer('publish.yaml');
await publishVersion(app, 'foo', '1.0.0');
const response = await supertest(app)
.delete(`/foo/-/foo-1.0.0.tgz/-rev/revision`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.CREATED);
expect(response.body.ok).toEqual(API_MESSAGE.TARBALL_REMOVED);
});
test.each([['foo', '@scope/foo']])(
'should remove a tarball that does exist',
async (pkgName) => {
const app = await initializeServer('publish.yaml');
await publishVersion(app, pkgName, '1.0.0');
const response = await supertest(app)
.delete(`/${pkgName}/-/${basename(pkgName)}-1.0.0.tgz/-rev/revision`)
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
.expect(HTTP_STATUS.CREATED);
expect(response.body.ok).toEqual(API_MESSAGE.TARBALL_REMOVED);
}
);
});
describe('star a package', () => {});

View file

@ -1,8 +1,6 @@
import nock from 'nock';
import supertest from 'supertest';
import { API_ERROR, HEADERS, HEADER_TYPE, HTTP_STATUS, TOKEN_BEARER } from '@verdaccio/core';
import { generateRemotePackageMetadata } from '@verdaccio/test-helper';
import { buildToken } from '@verdaccio/utils';
import { createUser, getPackage, initializeServer } from './_helper';
@ -13,13 +11,6 @@ describe('token', () => {
describe('basics', () => {
const FAKE_TOKEN: string = buildToken(TOKEN_BEARER, 'fake');
test.each([['user.yaml'], ['user.jwt.yaml']])('should test add a new user', async (conf) => {
const upstreamManifest = generateRemotePackageMetadata(
'vue',
'1.0.0',
'https://registry.verdaccio.org'
);
nock('https://registry.verdaccio.org').get(`/vue`).reply(201, upstreamManifest);
const app = await initializeServer(conf);
const credentials = { name: 'JotaJWT', password: 'secretPass' };
const response = await createUser(app, credentials.name, credentials.password);

View file

@ -1,6 +1,7 @@
import { Command, Option } from 'clipanion';
import { findConfigFile, parseConfigFile } from '@verdaccio/config';
import { warningUtils } from '@verdaccio/core';
import { logger, setup } from '@verdaccio/logger';
import { initServer } from '@verdaccio/node-api';
import { ConfigYaml, LoggerConfigItem } from '@verdaccio/types';
@ -45,17 +46,13 @@ export class InitCommand extends Command {
});
private initLogger(logConfig: ConfigYaml) {
try {
// @ts-expect-error
if (logConfig.logs) {
// @ts-expect-error
if (logConfig.logs) {
throw Error(
'the property config "logs" property is longer supported, rename to "log" and use object instead'
);
}
setup(logConfig.log as LoggerConfigItem);
} catch (err: any) {
throw new Error(err);
logConfig.log = logConfig.logs;
warningUtils.emit(warningUtils.Codes.VERWAR002);
}
setup(logConfig.log as LoggerConfigItem);
}
public async execute() {

View file

@ -6,12 +6,19 @@ const verdaccioDeprecation = 'VerdaccioDeprecation';
export enum Codes {
VERWAR001 = 'VERWAR001',
VERWAR002 = 'VERWAR002',
VERWAR003 = 'VERWAR003',
VERWAR004 = 'VERWAR004',
// deprecation warnings
VERDEP003 = 'VERDEP003',
}
warningInstance.create(
verdaccioWarning,
Codes.VERWAR002,
`The property config "logs" property is longer supported, rename to "log" and use object instead`
);
warningInstance.create(
verdaccioWarning,
Codes.VERWAR001,

View file

@ -181,6 +181,7 @@ export interface FullRemoteManifest {
homepage?: string;
repository?: string | { type?: string; url: string; directory?: string };
keywords?: string[];
author?: string | Author;
}
export interface Manifest extends FullRemoteManifest, PublishManifest {

View file

@ -1,7 +1,8 @@
import assert from 'assert';
import buildDebug from 'debug';
import _, { isEmpty, isNil } from 'lodash';
import { PassThrough, Readable, Transform, Writable, pipeline as streamPipeline } from 'stream';
import { basename } from 'path';
import { PassThrough, Readable, Transform, pipeline as streamPipeline } from 'stream';
import { pipeline } from 'stream/promises';
import { default as URL } from 'url';
@ -961,7 +962,7 @@ class Storage {
// if (typeof storage === 'undefined') {
// throw errorUtils.getNotFound();
// }
throw errorUtils.getInternalError('no implemenation ready for npm deprecate');
throw errorUtils.getInternalError('no implementation ready for npm deprecate');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@ -972,7 +973,7 @@ class Storage {
// throw errorUtils.getNotFound();
// }
throw errorUtils.getInternalError('no implemenation ready for npm star');
throw errorUtils.getInternalError('no implementation ready for npm star');
}
/**
@ -1060,9 +1061,9 @@ class Storage {
debug('%s version %s already exists', name, versionToPublish);
throw errorUtils.getConflict();
}
const uplinksLook = this.config?.publish?.allow_offline === false;
// if execution get here, package does not exist locally, we search upstream
const remoteManifest = await this.checkPackageRemote(name, this.isAllowPublishOffline());
const remoteManifest = await this.checkPackageRemote(name, uplinksLook);
if (remoteManifest?.versions[versionToPublish] != null) {
debug('%s version %s already exists', name, versionToPublish);
throw errorUtils.getConflict();
@ -1112,7 +1113,7 @@ class Storage {
// 3. upload the tarball to the storage
try {
const readable = Readable.from(buffer);
await this.uploadTarball(name, firstAttachmentKey, readable, {
await this.uploadTarball(name, basename(firstAttachmentKey), readable, {
signal: options.signal,
});
} catch (err: any) {
@ -1148,43 +1149,46 @@ class Storage {
* @param options
* @returns
*/
public async uploadTarball(
public uploadTarball(
name: string,
fileName: string,
contentReadable: Readable,
{ signal }
): Promise<void> {
return new Promise((resolve, reject) => {
(async () => {
const stream: Writable = await this.uploadTarballAsStream(name, fileName, {
signal,
this.uploadTarballAsStream(name, fileName, {
signal,
})
.then((stream) => {
stream.on('error', (err) => {
debug(
'error on stream a tarball %o for %o with error %o',
'foo.tar.gz',
name,
err.message
);
reject(err);
});
stream.on('success', () => {
this.logger.debug(
{ fileName, name },
'file @{fileName} for package @{name} has been successfully uploaded'
);
resolve();
});
return stream;
})
.then((stream) => {
pipeline(contentReadable, stream, { signal })
.then(() => {
debug('success pipe upload tarball');
})
.catch(reject);
});
stream.on('error', (err) => {
debug(
'error on stream a tarball %o for %o with error %o',
'foo.tar.gz',
name,
err.message
);
reject(err);
});
stream.on('success', () => {
this.logger.debug(
{ fileName, name },
'file @{fileName} for package @{name} has been succesfully uploaded'
);
resolve();
});
await pipeline(contentReadable, stream, { signal });
})().catch((err) => {
reject(err);
});
});
}
public async uploadTarballAsStream(
private async uploadTarballAsStream(
pkgName: string,
filename: string,
{ signal }
@ -1395,14 +1399,6 @@ class Storage {
}
}
private isAllowPublishOffline(): boolean {
return (
typeof this.config.publish !== 'undefined' &&
_.isBoolean(this.config.publish.allow_offline) &&
this.config.publish.allow_offline
);
}
/**
*
* @param name package name
@ -1567,8 +1563,8 @@ class Storage {
A package requires uplinks syncronization if enables the proxy section, uplinks
can be more than one, the more are the most slow request will take, the request
are made in serie and if 1st call fails, the second will be triggered, otherwise
the 1st will reply and others will be discareded. The order is important.
are made in serial and if 1st call fails, the second will be triggered, otherwise
the 1st will reply and others will be discarded. The order is important.
Errors on upkinks are considered are, time outs, connection fails and http status 304,
in that case the request returns empty body and we want ask next on the list if has fresh

View file

@ -668,8 +668,9 @@ describe('storage', () => {
});
test.todo('should handle double proxy with last one success');
});
describe('options', () => {
test('should handle disable uplinks via options.uplinksLook=false', async () => {
test('should handle disable uplinks via options.uplinksLook=false with cache', async () => {
const fooManifest = generatePackageMetadata('foo', '8.0.0');
nock('https://registry.verdaccio.org').get('/foo').reply(201, manifestFooRemoteNpmjs);
const config = new Config(
@ -691,6 +692,35 @@ describe('storage', () => {
expect((response as Manifest).name).toEqual(fooManifest.name);
expect((response as Manifest)[DIST_TAGS].latest).toEqual('8.0.0');
});
test('should handle disable uplinks via options.uplinksLook=false without cache', async () => {
const fooRemoteManifest = generateRemotePackageMetadata(
'foo',
'9.0.0',
'https://registry.verdaccio.org',
['9.0.0', '9.0.1', '9.0.2', '9.0.3']
);
nock('https://registry.verdaccio.org').get('/foo').reply(201, fooRemoteManifest);
const config = new Config(
configExample(
{
...getDefaultConfig(),
storage: generateRandomStorage(),
},
'./fixtures/config/syncSingleUplinksMetadata.yaml',
__dirname
)
);
const storage = new Storage(config);
await storage.init(config);
const [response] = await storage.syncUplinksMetadataNext('foo', null, {
uplinksLook: true,
});
expect((response as Manifest).name).toEqual('foo');
expect((response as Manifest)[DIST_TAGS].latest).toEqual('9.0.0');
});
});
});

View file

@ -0,0 +1,10 @@
const config = require('../../../jest/config');
module.exports = Object.assign({}, config, {
coverageThreshold: {
global: {
// FIXME: increase to 90
lines: 50,
},
},
});

View file

@ -1,4 +1,4 @@
import { GenericBody, Manifest } from '@verdaccio/types';
import { FullRemoteManifest, GenericBody, Manifest, Version, Versions } from '@verdaccio/types';
export interface DistTags {
[key: string]: string;
@ -49,7 +49,7 @@ export function addNewVersion(
};
// update the latest with the new version
newManifest['dist-tags'] = { latest: version };
// add new version does not need attachmetns
// add new version does not need attachments
if (isRemote) {
newManifest._distfiles = {
...newManifest._distfiles,
@ -137,58 +137,83 @@ export function generateLocalPackageMetadata(
export function generateRemotePackageMetadata(
pkgName: string,
version = '1.0.0',
domain: string = 'http://localhost:5555'
): Manifest {
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]: {
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`,
},
[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',
_attachments: {},
_uplinks: {},
_distfiles: {
[`${pkgName}-${version}.tgz`]: {
url: `${domain}/${pkgName}\/-\/${getTarball(pkgName)}-${version}.tgz`,
sha: '2c03764f651a9f016ca0b7620421457b619151b9', // pragma: allowlist secret
},
},
_rev: '',
_rev: '12-c8fe8a9c79fa57a87347a0213e6f2548',
};
}

View file

@ -1,3 +0,0 @@
const config = require('../../jest/config');
module.exports = Object.assign({}, config, {});

View file

@ -35,27 +35,27 @@ describe('generate metadata', () => {
});
describe('generateRemotePackageMetadata', () => {
test('should generate package metadata', () => {
const m = generateRemotePackageMetadata('foo', '1.0.0', 'https://registry.verdaccio.org');
expect(m).toBeDefined();
expect(m._attachments).toEqual({});
expect(m._distfiles['foo-1.0.0.tgz']).toEqual({
sha: '2c03764f651a9f016ca0b7620421457b619151b9',
url: 'https://registry.verdaccio.org/foo/-/foo-1.0.0.tgz',
});
expect(
generateRemotePackageMetadata('foo', '1.0.0', 'https://registry.verdaccio.org')
).toBeDefined();
});
test('should add new versions remote', () => {
const manifest = generateRemotePackageMetadata('foo', '1.0.0');
const m1 = addNewVersion(manifest, '1.0.1', true);
expect(Object.keys(m1._attachments)).toEqual([]);
expect(Object.keys(m1._distfiles)).toEqual(['foo-1.0.0.tgz', 'foo-1.0.1.tgz']);
const m2 = addNewVersion(m1, '1.0.2');
expect(Object.keys(m2.versions)).toEqual(['1.0.0', '1.0.1', '1.0.2']);
expect(m2['dist-tags'].latest).toEqual('1.0.2');
expect(m2._distfiles['foo-1.0.2.tgz']).toEqual({
sha: '2c03764f651a9f016ca0b7620421457b619151b9',
url: 'http://localhost:5555/foo/-/foo-1.0.2.tgz',
});
expect(Object.keys(m2._attachments)).toEqual([]);
test('should generate package metadata with multiple versions', () => {
const m = generateRemotePackageMetadata('foo', '1.0.0', 'https://registry.verdaccio.org', [
'1.0.1',
'1.0.2',
'3.0.0',
]);
expect(m).toBeDefined();
expect(Object.keys(m.versions)).toEqual(['1.0.0', '1.0.1', '1.0.2', '3.0.0']);
expect(Object.keys(m.time)).toEqual([
'modified',
'created',
'1.0.0',
'1.0.1',
'1.0.2',
'3.0.0',
]);
});
});
describe('generateLocalPackageMetadata', () => {

View file

@ -0,0 +1,3 @@
{
"extends": "../../../.babelrc"
}

View file

@ -0,0 +1,3 @@
node_modules
coverage/
lib/

View file

@ -0,0 +1 @@
lib/

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Verdaccio
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,22 @@
{
"name": "@verdaccio/local-publish",
"version": "0.0.1",
"private": true,
"description": "trigger server for local development",
"author": "Juan Picado <juanpicado19@gmail.com>",
"license": "MIT",
"homepage": "https://verdaccio.org",
"main": "build/index.js",
"types": "build/index.d.ts",
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.15",
"@verdaccio/core": "workspace:6.0.0-6-next.7",
"@verdaccio/config": "workspace:6.0.0-6-next.16",
"verdaccio": "5.14.0",
"ts-node": "10.9.1"
},
"scripts": {
"start": "ts-node src/index.ts",
"build": "echo 0"
}
}

View file

@ -0,0 +1,5 @@
{
"rules": {
"no-console": 0
}
}

View file

@ -0,0 +1,52 @@
import { runServer } from 'verdaccio';
import { ConfigBuilder } from '@verdaccio/config';
import { constants, fileUtils } from '@verdaccio/core';
fileUtils
.createTempFolder('test')
.then((folderPath) => {
const configuration = ConfigBuilder.build({
storage: folderPath,
// @ts-ignore
logs: { level: 'info', type: 'stdout', format: 'pretty' },
uplinks: {},
packages: {},
self_path: folderPath,
})
.addUplink('npmjs', { url: 'https://registry.npmjs.org' })
.addPackageAccess('@verdaccio/*', {
access: constants.ROLES.$ANONYMOUS,
publish: constants.ROLES.$ANONYMOUS,
})
.addPackageAccess(constants.PACKAGE_ACCESS.SCOPE, {
access: constants.ROLES.$ANONYMOUS,
publish: constants.ROLES.$ANONYMOUS,
proxy: 'npmjs',
})
.addPackageAccess('verdaccio', {
access: constants.ROLES.$ANONYMOUS,
publish: constants.ROLES.$ANONYMOUS,
})
.addPackageAccess('verdaccio-*', {
access: constants.ROLES.$ANONYMOUS,
publish: constants.ROLES.$ANONYMOUS,
})
.addPackageAccess(constants.PACKAGE_ACCESS.ALL, {
access: constants.ROLES.$ALL,
publish: constants.ROLES.$ALL,
proxy: 'npmjs',
})
.addAuth({
htpasswd: {
file: './htpasswd',
},
});
return runServer(configuration.getConfig());
})
.then((app: any) => {
app.listen(4873, () => {
console.log('running verdaccio@5 server');
});
})
.catch(console.error);

View file

@ -0,0 +1,10 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./build",
"preserveSymlinks": true
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts"]
}

View file

@ -0,0 +1,18 @@
{
"extends": "../../../tsconfig.reference.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./build",
"composite": true,
"declaration": true
},
"include": ["src/**/*.ts"],
"references": [
{
"path": "../../config"
},
{
"path": "../../core/core"
}
]
}

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,10 @@
export { getDefaultConfig } from '@verdaccio/config';
export { initialSetup } from './registry';
export { addNpmPrefix, addYarnClassicPrefix, addRegistry, prepareYarnModernProject } from './utils';
export {
addNpmPrefix,
addYarnClassicPrefix,
addRegistry,
prepareYarnModernProject,
prepareGenericEmptyProject,
} from './utils';
export { exec, ExecOutput } from './process';

View file

@ -3,6 +3,7 @@ import buildDebug from 'debug';
import { Registry } from 'verdaccio';
import { getDefaultConfig } from '@verdaccio/config';
import { ConfigYaml } from '@verdaccio/types';
const debug = buildDebug('verdaccio:e2e:registry-utils');
@ -11,8 +12,10 @@ export type Setup = {
tempFolder: string;
};
export async function initialSetup(): Promise<Setup> {
const { configPath, tempFolder } = await Registry.fromConfigToPath(getDefaultConfig());
export async function initialSetup(customConfig?: ConfigYaml): Promise<Setup> {
const { configPath, tempFolder } = await Registry.fromConfigToPath({
...(customConfig ? customConfig : { ...getDefaultConfig(), _debug: true }),
});
debug(`configPath %o`, configPath);
debug(`tempFolder %o`, tempFolder);
const registry = new Registry(configPath);

View file

@ -61,3 +61,43 @@ export async function prepareYarnModernProject(
await cp(yarnPath, join(tempFolder, '.yarn/releases/yarn.js'), { dereference: true });
return { tempFolder };
}
export async function prepareGenericEmptyProject(
packageName: string,
version: string,
port: number,
token: string,
registryDomain: string
) {
const getPackageJSON = (packageName, version = '1.0.0') => {
const json = {
name: packageName,
version,
description: 'some cool project',
main: 'index.js',
scripts: {
test: 'echo exit 1',
},
keywords: ['foo', 'bar'],
author: 'Juan Picado <jotadeveloper@gmail.com>',
license: 'MIT',
};
return JSON.stringify(json);
};
const getREADME = (packageName) => `
# My README ${packageName}
some text
## subtitle
more text
`;
const getNPMrc = (port, token, registry) => `//localhost:${port}/:_authToken=${token}
registry=${registry}`;
const tempFolder = await createTempFolder('temp-folder');
await writeFile(join(tempFolder, 'package.json'), getPackageJSON(packageName, version));
await writeFile(join(tempFolder, 'README.md'), getREADME(packageName));
await writeFile(join(tempFolder, '.npmrc'), getNPMrc(port, token, registryDomain));
return { tempFolder };
}

View file

@ -14,6 +14,7 @@ describe('install a package', () => {
test('should run npm info json body', async () => {
const resp = await npm(
{},
'info',
'verdaccio',
'--json',

View file

@ -0,0 +1,41 @@
import { addRegistry, initialSetup, prepareGenericEmptyProject } from '@verdaccio/test-cli-commons';
import { npm } from './utils';
describe('install a package', () => {
jest.setTimeout(10000);
let registry;
beforeAll(async () => {
const setup = await initialSetup();
registry = setup.registry;
await registry.init();
});
test.each([['verdaccio-memory', 'verdaccio', '@verdaccio/foo', '@verdaccio/some-foo']])(
'should publish a package %s',
async (pkgName) => {
const { tempFolder } = await prepareGenericEmptyProject(
pkgName,
'1.0.0-patch',
registry.port,
registry.getToken(),
registry.getRegistryUrl()
);
const resp = await npm(
{ cwd: tempFolder },
'publish',
'--json',
...addRegistry(registry.getRegistryUrl())
);
const parsedBody = JSON.parse(resp.stdout as string);
expect(parsedBody.name).toEqual(pkgName);
expect(parsedBody.files).toBeDefined();
expect(parsedBody.files).toBeDefined();
}
);
afterAll(async () => {
registry.stop();
});
});

View file

@ -1,3 +1,4 @@
import { SpawnOptions } from 'child_process';
import { join } from 'path';
import { exec } from '@verdaccio/test-cli-commons';
@ -6,6 +7,6 @@ export function getCommand() {
return join(__dirname, './node_modules/.bin/npm');
}
export function npm(...args: string[]) {
return exec({}, getCommand(), args);
export function npm(options: SpawnOptions, ...args: string[]) {
return exec(options, getCommand(), args);
}

View file

@ -14,6 +14,7 @@ describe('install a package', () => {
test('should run npm info json body', async () => {
const resp = await npm(
{},
'info',
'verdaccio',
'--json',

View file

@ -0,0 +1,41 @@
import { addRegistry, initialSetup, prepareGenericEmptyProject } from '@verdaccio/test-cli-commons';
import { npm } from './utils';
describe('install a package', () => {
jest.setTimeout(10000);
let registry;
beforeAll(async () => {
const setup = await initialSetup();
registry = setup.registry;
await registry.init();
});
test.each([['verdaccio-memory', 'verdaccio', '@verdaccio/foo', '@verdaccio/some-foo']])(
'should publish a package %s',
async (pkgName) => {
const { tempFolder } = await prepareGenericEmptyProject(
pkgName,
'1.0.0-patch',
registry.port,
registry.getToken(),
registry.getRegistryUrl()
);
const resp = await npm(
{ cwd: tempFolder },
'publish',
'--json',
...addRegistry(registry.getRegistryUrl())
);
const parsedBody = JSON.parse(resp.stdout as string);
expect(parsedBody.name).toEqual(pkgName);
expect(parsedBody.files).toBeDefined();
expect(parsedBody.files).toBeDefined();
}
);
afterAll(async () => {
registry.stop();
});
});

View file

@ -1,3 +1,4 @@
import { SpawnOptions } from 'child_process';
import { join } from 'path';
import { exec } from '@verdaccio/test-cli-commons';
@ -6,6 +7,6 @@ export function getCommand() {
return join(__dirname, './node_modules/.bin/npm');
}
export function npm(...args: string[]) {
return exec({}, getCommand(), args);
export function npm(options: SpawnOptions, ...args: string[]) {
return exec(options, getCommand(), args);
}

View file

@ -14,6 +14,7 @@ describe('install a package', () => {
test('should run npm info json body', async () => {
const resp = await npm(
{},
'info',
'verdaccio',
'--json',

View file

@ -0,0 +1,41 @@
import { addRegistry, initialSetup, prepareGenericEmptyProject } from '@verdaccio/test-cli-commons';
import { npm } from './utils';
describe('install a package', () => {
jest.setTimeout(10000);
let registry;
beforeAll(async () => {
const setup = await initialSetup();
registry = setup.registry;
await registry.init();
});
test.each([['verdaccio-memory', 'verdaccio', '@verdaccio/foo', '@verdaccio/some-foo']])(
'should publish a package %s',
async (pkgName) => {
const { tempFolder } = await prepareGenericEmptyProject(
pkgName,
'1.0.0-patch',
registry.port,
registry.getToken(),
registry.getRegistryUrl()
);
const resp = await npm(
{ cwd: tempFolder },
'publish',
'--json',
...addRegistry(registry.getRegistryUrl())
);
const parsedBody = JSON.parse(resp.stdout as string);
expect(parsedBody.name).toEqual(pkgName);
expect(parsedBody.files).toBeDefined();
expect(parsedBody.files).toBeDefined();
}
);
afterAll(async () => {
registry.stop();
});
});

View file

@ -1,3 +1,4 @@
import { SpawnOptions } from 'child_process';
import { join } from 'path';
import { exec } from '@verdaccio/test-cli-commons';
@ -6,6 +7,6 @@ export function getCommand() {
return join(__dirname, './node_modules/.bin/npm');
}
export function npm(...args: string[]) {
return exec({}, getCommand(), args);
export function npm(options: SpawnOptions, ...args: string[]) {
return exec(options, getCommand(), args);
}

View file

@ -1,6 +1,6 @@
import { addRegistry, initialSetup } from '@verdaccio/test-cli-commons';
import { npm } from './utils';
import { pnpm } from './utils';
describe('install a package', () => {
jest.setTimeout(10000);
@ -13,7 +13,8 @@ describe('install a package', () => {
});
test('should run pnpm info json body', async () => {
const resp = await npm(
const resp = await pnpm(
{},
'info',
'verdaccio',
'--json',

View file

@ -0,0 +1,41 @@
import { addRegistry, initialSetup, prepareGenericEmptyProject } from '@verdaccio/test-cli-commons';
import { pnpm } from './utils';
describe('install a package', () => {
jest.setTimeout(10000);
let registry;
beforeAll(async () => {
const setup = await initialSetup();
registry = setup.registry;
await registry.init();
});
test.each([['verdaccio-memory', 'verdaccio', '@verdaccio/foo', '@verdaccio/some-foo']])(
'should publish a package %s',
async (pkgName) => {
const { tempFolder } = await prepareGenericEmptyProject(
pkgName,
'1.0.0-patch',
registry.port,
registry.getToken(),
registry.getRegistryUrl()
);
const resp = await pnpm(
{ cwd: tempFolder },
'publish',
'--json',
...addRegistry(registry.getRegistryUrl())
);
const parsedBody = JSON.parse(resp.stdout as string);
expect(parsedBody.name).toEqual(pkgName);
expect(parsedBody.files).toBeDefined();
expect(parsedBody.files).toBeDefined();
}
);
afterAll(async () => {
registry.stop();
});
});

View file

@ -1,3 +1,4 @@
import { SpawnOptions } from 'child_process';
import { join } from 'path';
import { exec } from '@verdaccio/test-cli-commons';
@ -6,6 +7,6 @@ export function getCommand() {
return join(__dirname, './node_modules/.bin/pnpm');
}
export function npm(...args: string[]) {
return exec({}, getCommand(), args);
export function pnpm(options: SpawnOptions, ...args: string[]) {
return exec(options, getCommand(), args);
}

View file

@ -1,6 +1,6 @@
import { addRegistry, initialSetup } from '@verdaccio/test-cli-commons';
import { npm } from './utils';
import { pnpm } from './utils';
describe('install a package', () => {
jest.setTimeout(10000);
@ -13,7 +13,8 @@ describe('install a package', () => {
});
test('should run pnpm info json body', async () => {
const resp = await npm(
const resp = await pnpm(
{},
'info',
'verdaccio',
'--json',

View file

@ -0,0 +1,41 @@
import { addRegistry, initialSetup, prepareGenericEmptyProject } from '@verdaccio/test-cli-commons';
import { pnpm } from './utils';
describe('install a package', () => {
jest.setTimeout(10000);
let registry;
beforeAll(async () => {
const setup = await initialSetup();
registry = setup.registry;
await registry.init();
});
test.each([['verdaccio-memory', 'verdaccio', '@verdaccio/foo', '@verdaccio/some-foo']])(
'should publish a package %s',
async (pkgName) => {
const { tempFolder } = await prepareGenericEmptyProject(
pkgName,
'1.0.0-patch',
registry.port,
registry.getToken(),
registry.getRegistryUrl()
);
const resp = await pnpm(
{ cwd: tempFolder },
'publish',
'--json',
...addRegistry(registry.getRegistryUrl())
);
const parsedBody = JSON.parse(resp.stdout as string);
expect(parsedBody.name).toEqual(pkgName);
expect(parsedBody.files).toBeDefined();
expect(parsedBody.files).toBeDefined();
}
);
afterAll(async () => {
registry.stop();
});
});

View file

@ -1,3 +1,4 @@
import { SpawnOptions } from 'child_process';
import { join } from 'path';
import { exec } from '@verdaccio/test-cli-commons';
@ -6,6 +7,6 @@ export function getCommand() {
return join(__dirname, './node_modules/.bin/pnpm');
}
export function npm(...args: string[]) {
return exec({}, getCommand(), args);
export function pnpm(options: SpawnOptions, ...args: string[]) {
return exec(options, getCommand(), args);
}

View file

@ -14,6 +14,7 @@ describe('install a package', () => {
test('should run yarn info json body', async () => {
const resp = await yarn(
{},
'info',
'verdaccio',
'--json',

View file

@ -0,0 +1,39 @@
import { addRegistry, initialSetup, prepareGenericEmptyProject } from '@verdaccio/test-cli-commons';
import { yarn } from './utils';
describe('install a package', () => {
jest.setTimeout(10000);
let registry;
beforeAll(async () => {
const setup = await initialSetup();
registry = setup.registry;
await registry.init();
});
test.each([['verdaccio-memory', 'verdaccio', '@verdaccio/foo', '@verdaccio/some-foo']])(
'should publish a package %s',
async (pkgName) => {
const { tempFolder } = await prepareGenericEmptyProject(
pkgName,
'1.0.0-patch',
registry.port,
registry.getToken(),
registry.getRegistryUrl()
);
const resp = await yarn(
{ cwd: tempFolder },
'publish',
'--json',
...addRegistry(registry.getRegistryUrl())
);
// TODO: improve parsing to get better expects
expect(typeof resp.stdout === 'string').toBeDefined();
}
);
afterAll(async () => {
registry.stop();
});
});

View file

@ -1,3 +1,4 @@
import { SpawnOptions } from 'child_process';
import { join } from 'path';
import { exec } from '@verdaccio/test-cli-commons';
@ -6,6 +7,6 @@ export function getCommand() {
return join(__dirname, './node_modules/.bin/yarn');
}
export function yarn(...args: string[]) {
return exec({}, getCommand(), args);
export function yarn(options: SpawnOptions, ...args: string[]) {
return exec(options, getCommand(), args);
}