Compare commits
42 Commits
@verdaccio
...
@verdaccio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64c8221d35 | ||
|
|
c908963132 | ||
|
|
46db10fc4f | ||
|
|
fadba40a9f | ||
|
|
02023afd74 | ||
|
|
f130817828 | ||
|
|
d2f9013ece | ||
|
|
b8554c8935 | ||
|
|
8ea24df16a | ||
|
|
67bbd93379 | ||
|
|
d09ea21bc9 | ||
|
|
0b72c32400 | ||
|
|
2d137274c4 | ||
|
|
cc30a85d3d | ||
|
|
85a01746a5 | ||
|
|
94f5e765a8 | ||
|
|
4cd71c6409 | ||
|
|
be71d77511 | ||
|
|
92a6f9a3fd | ||
|
|
b9a947aa2f | ||
|
|
110d90d888 | ||
|
|
6bed458925 | ||
|
|
73b3479a3a | ||
|
|
459af1cd39 | ||
|
|
e0d639a1a6 | ||
|
|
b78f35257e | ||
|
|
06e400cb34 | ||
|
|
dd380c5a5e | ||
|
|
ad3151c3f3 | ||
|
|
cca1f7f32a | ||
|
|
3d86b836c3 | ||
|
|
7f48edc254 | ||
|
|
9245b4d39b | ||
|
|
3b363fb531 | ||
|
|
5b39aeb623 | ||
|
|
edc7a538f3 | ||
|
|
d585216153 | ||
|
|
1367beae51 | ||
|
|
944ecf6874 | ||
|
|
2bd10d6010 | ||
|
|
7ff4808be6 | ||
|
|
7bb3c2bf0e |
5
.changeset/fuzzy-drinks-taste.md
Normal file
5
.changeset/fuzzy-drinks-taste.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@verdaccio/ui-theme': minor
|
||||
---
|
||||
|
||||
fix: remove engines from ui-theme
|
||||
@@ -58,6 +58,7 @@
|
||||
"few-cooks-destroy",
|
||||
"few-mangos-grow",
|
||||
"fifty-jars-rest",
|
||||
"fuzzy-drinks-taste",
|
||||
"fuzzy-onions-draw",
|
||||
"gentle-parrots-lay",
|
||||
"gentle-trains-switch",
|
||||
@@ -83,6 +84,7 @@
|
||||
"pretty-hounds-tap",
|
||||
"proud-jeans-walk",
|
||||
"red-chefs-float",
|
||||
"red-yaks-sell",
|
||||
"rich-ghosts-rule",
|
||||
"shaggy-carrots-unite",
|
||||
"shaggy-parrots-smash",
|
||||
@@ -90,11 +92,13 @@
|
||||
"smart-apricots-kneel",
|
||||
"sour-buses-shout",
|
||||
"spicy-frogs-press",
|
||||
"spicy-snakes-sip",
|
||||
"ten-parents-breathe",
|
||||
"tender-bags-call",
|
||||
"thick-countries-move",
|
||||
"three-moles-drop",
|
||||
"three-pots-sit",
|
||||
"tiny-seals-join",
|
||||
"two-dolls-check",
|
||||
"wild-jokes-beam"
|
||||
]
|
||||
|
||||
5
.changeset/red-yaks-sell.md
Normal file
5
.changeset/red-yaks-sell.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@verdaccio/logger': patch
|
||||
---
|
||||
|
||||
Fix re-opening log files using SIGUSR2
|
||||
5
.changeset/spicy-snakes-sip.md
Normal file
5
.changeset/spicy-snakes-sip.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@verdaccio/ui-theme': patch
|
||||
---
|
||||
|
||||
fix: specific version package detail page not showing
|
||||
5
.changeset/tiny-seals-join.md
Normal file
5
.changeset/tiny-seals-join.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@verdaccio/ui-theme': minor
|
||||
---
|
||||
|
||||
feat: improve registry info dialog and language switch
|
||||
8
.github/workflows/benchmark.yml
vendored
8
.github/workflows/benchmark.yml
vendored
@@ -57,8 +57,8 @@ jobs:
|
||||
# - local
|
||||
- 3.13.1
|
||||
- 4.12.2
|
||||
- 5.3.0
|
||||
- 6.0.0-6-next.27
|
||||
- 5.5.2
|
||||
- 6.0.0-6-next.31
|
||||
name: Benchmark autocannon
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -118,8 +118,8 @@ jobs:
|
||||
# old versions to compare same test along previous releases
|
||||
- 3.13.1
|
||||
- 4.12.2
|
||||
- 5.3.0
|
||||
- 6.0.0-6-next.27
|
||||
- 5.5.2
|
||||
- 6.0.0-6-next.31
|
||||
name: Benchmark hyperfine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
48
.github/workflows/contributors.yml
vendored
Normal file
48
.github/workflows/contributors.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: contributors
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# twice peer week
|
||||
- cron: '0 0 * * 1,4'
|
||||
# for now, scheduled, we can enable on push master but not make much sense now
|
||||
# push:
|
||||
# branches:
|
||||
# - master
|
||||
jobs:
|
||||
prepare:
|
||||
name: Run script
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 17.x
|
||||
- name: install pnpm
|
||||
run: sudo npm i pnpm@6.24.1 -g
|
||||
- name: set store
|
||||
run: |
|
||||
mkdir ~/.pnpm-store
|
||||
pnpm config set store-dir ~/.pnpm-store
|
||||
- name: setup pnpm config registry
|
||||
run: pnpm config set registry https://registry.verdaccio.org
|
||||
- name: install dependencies
|
||||
run: pnpm install
|
||||
- name: update contributors
|
||||
run: pnpm run contributors
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: format
|
||||
run: pnpm format
|
||||
- name: Commit & Push changes
|
||||
uses: actions-js/push@v1.3
|
||||
with:
|
||||
github_token: ${{ secrets.TOKEN_VERDACCIOBOT_GITHUB }}
|
||||
message: "chore: updated contributors list"
|
||||
branch: master
|
||||
author_email: verdaccio.npm@gmail.com
|
||||
author_name: verdacciobot
|
||||
2
.github/workflows/website.yml
vendored
2
.github/workflows/website.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}-
|
||||
|
||||
- uses: pnpm/action-setup@v2.0.1
|
||||
- uses: pnpm/action-setup@v2.1.0
|
||||
with:
|
||||
version: 6.10.2
|
||||
run_install: |
|
||||
|
||||
16
README.md
16
README.md
@@ -45,7 +45,7 @@ docker pull verdaccio/verdaccio:nightly-master
|
||||
|
||||
## Donations
|
||||
|
||||
Verdaccio is run by **volunteers**; nobody is working full-time on it. If you find this project to be useful and would like to support its development, consider making a donation - **your logo might end up in this readme.** 😉
|
||||
Verdaccio is run by **volunteers**; nobody is working full-time on it. If you find this project to be useful and would like to support its development, consider do a long support donation - **and your logo will be on this section of the readme.**
|
||||
|
||||
**[Donate](https://github.com/sponsors/verdaccio)** 💵👍🏻 starting from _$1/month_ or just one single contribution.
|
||||
|
||||
@@ -73,7 +73,15 @@ If you want to use a modified version of some 3rd-party package (for example, yo
|
||||
Verdaccio has proved to be a lightweight registry that can be
|
||||
booted in a couple of seconds, fast enough for any CI. Many open source projects use verdaccio for end to end testing, to mention some examples, **create-react-app**, **mozilla neutrino**, **pnpm**, **storybook**, **alfresco** or **eclipse theia**. You can read more in dedicated article to E2E in our blog.
|
||||
|
||||
## Talks
|
||||
## Watch our Videos
|
||||
|
||||
**Node Congress 2022, February 2022, Online Free**
|
||||
|
||||
<div>
|
||||
<a href="https://nodecongress.com/">
|
||||
<img src="https://cdn.verdaccio.dev/readme/nodejscongress2022.jpg" alt="nodejs" width="300"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### **Using Docker and Verdaccio to make Integration Testing Easy - Docker All Hands #4 December - 2021**.
|
||||
|
||||
@@ -101,7 +109,7 @@ verdaccio
|
||||
You would need set some npm configuration, this is optional.
|
||||
|
||||
```bash
|
||||
$ npm set registry http://localhost:4873/
|
||||
npm set registry http://localhost:4873/
|
||||
```
|
||||
|
||||
For one-off commands or to avoid setting the registry globally:
|
||||
@@ -125,7 +133,7 @@ npm adduser --registry http://localhost:4873
|
||||
> if you use HTTPS, add an appropriate CA information ("null" means get CA list from OS)
|
||||
|
||||
```bash
|
||||
$ npm set ca null
|
||||
npm set ca null
|
||||
```
|
||||
|
||||
#### 2. publish your package
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# see https://github.com/verdaccio/verdaccio/blob/master/wiki/docker.md#docker-and-custom-port-configuration
|
||||
#
|
||||
# Look here for more config file examples:
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/conf
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/packages/config/src/conf
|
||||
#
|
||||
|
||||
# path to a directory with all packages
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# see https://github.com/verdaccio/verdaccio/blob/master/wiki/docker.md#docker-and-custom-port-configuration
|
||||
#
|
||||
# Look here for more config file examples:
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/conf
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/packages/config/src/conf
|
||||
#
|
||||
|
||||
# path to a directory with all packages
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# see https://github.com/verdaccio/verdaccio/blob/master/wiki/docker.md#docker-and-custom-port-configuration
|
||||
#
|
||||
# Look here for more config file examples:
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/conf
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/packages/config/src/conf
|
||||
#
|
||||
|
||||
# path to a directory with all packages
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# see https://github.com/verdaccio/verdaccio/blob/master/wiki/docker.md#docker-and-custom-port-configuration
|
||||
#
|
||||
# Look here for more config file examples:
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/conf
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/packages/config/src/conf
|
||||
#
|
||||
|
||||
# path to a directory with all packages
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# see https://github.com/verdaccio/verdaccio/blob/master/wiki/docker.md#docker-and-custom-port-configuration
|
||||
#
|
||||
# Look here for more config file examples:
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/conf
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/packages/config/src/conf
|
||||
#
|
||||
|
||||
# path to a directory with all packages
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# see https://github.com/verdaccio/verdaccio/blob/master/wiki/docker.md#docker-and-custom-port-configuration
|
||||
#
|
||||
# Look here for more config file examples:
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/conf
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/packages/config/src/conf
|
||||
#
|
||||
|
||||
# path to a directory with all packages
|
||||
|
||||
33
package.json
33
package.json
@@ -16,7 +16,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.16.8",
|
||||
"@babel/core": "7.16.7",
|
||||
"@babel/core": "7.16.12",
|
||||
"@babel/node": "7.16.8",
|
||||
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||
"@babel/plugin-proposal-decorators": "7.16.7",
|
||||
@@ -32,26 +32,28 @@
|
||||
"@babel/plugin-syntax-import-meta": "7.10.4",
|
||||
"@babel/plugin-transform-async-to-generator": "7.16.8",
|
||||
"@babel/plugin-transform-classes": "7.16.7",
|
||||
"@babel/plugin-transform-runtime": "7.16.8",
|
||||
"@babel/preset-env": "7.16.8",
|
||||
"@babel/plugin-transform-runtime": "7.16.10",
|
||||
"@babel/preset-env": "7.16.11",
|
||||
"@babel/preset-react": "7.16.7",
|
||||
"@babel/preset-typescript": "7.16.7",
|
||||
"@babel/register": "7.16.9",
|
||||
"@babel/runtime": "7.16.7",
|
||||
"@dianmora/contributors": "2.0.2",
|
||||
"@changesets/changelog-github": "0.4.2",
|
||||
"@changesets/cli": "2.15.0",
|
||||
"@changesets/get-dependents-graph": "1.2.4",
|
||||
"@crowdin/cli": "3.7.6",
|
||||
"@trivago/prettier-plugin-sort-imports": "3.1.1",
|
||||
"@crowdin/cli": "3.7.7",
|
||||
"@trivago/prettier-plugin-sort-imports": "3.2.0",
|
||||
"@types/async": "3.2.12",
|
||||
"@types/autocannon": "4.1.1",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/http-errors": "1.8.1",
|
||||
"@types/http-errors": "1.8.2",
|
||||
"@types/jest": "27.4.0",
|
||||
"@types/lodash": "4.14.178",
|
||||
"@types/mime": "2.0.3",
|
||||
"@types/minimatch": "3.0.5",
|
||||
"@types/node": "16.11.19",
|
||||
"@types/node": "16.11.21",
|
||||
"@types/jsonwebtoken": "8.5.1",
|
||||
"@types/request": "2.48.8",
|
||||
"@types/semver": "7.3.9",
|
||||
"@types/supertest": "2.0.11",
|
||||
@@ -59,8 +61,8 @@
|
||||
"@types/validator": "13.7.1",
|
||||
"@types/webpack": "5.28.0",
|
||||
"@types/webpack-env": "1.16.3",
|
||||
"@typescript-eslint/eslint-plugin": "4.33.0",
|
||||
"@typescript-eslint/parser": "4.33.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.10.2",
|
||||
"@typescript-eslint/parser": "5.10.2",
|
||||
"@verdaccio/benchmark": "workspace:*",
|
||||
"@verdaccio/eslint-config": "workspace:*",
|
||||
"@verdaccio/types": "workspace:*",
|
||||
@@ -73,11 +75,11 @@
|
||||
"babel-plugin-emotion": "10.2.2",
|
||||
"codecov": "3.8.3",
|
||||
"concurrently": "6.5.1",
|
||||
"core-js": "3.20.2",
|
||||
"core-js": "3.20.3",
|
||||
"cross-env": "7.0.3",
|
||||
"debug": "4.3.3",
|
||||
"detect-secrets": "1.0.6",
|
||||
"eslint": "7.32.0",
|
||||
"eslint": "8.8.0",
|
||||
"fs-extra": "10.0.0",
|
||||
"husky": "7.0.4",
|
||||
"in-publish": "2.0.1",
|
||||
@@ -95,11 +97,11 @@
|
||||
"prettier": "2.5.1",
|
||||
"rimraf": "3.0.2",
|
||||
"selfsigned": "1.10.14",
|
||||
"supertest": "6.2.1",
|
||||
"supertest": "6.2.2",
|
||||
"ts-node": "10.4.0",
|
||||
"typescript": "4.5.4",
|
||||
"typescript": "4.5.5",
|
||||
"update-ts-references": "2.4.1",
|
||||
"verdaccio": "5.4.0",
|
||||
"verdaccio": "5.5.0",
|
||||
"verdaccio-audit": "workspace:*",
|
||||
"verdaccio-auth-memory": "workspace:*",
|
||||
"verdaccio-htpasswd": "workspace:*",
|
||||
@@ -112,7 +114,7 @@
|
||||
"docker": "docker build -t verdaccio/verdaccio:local . --no-cache",
|
||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\"",
|
||||
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\"",
|
||||
"lint": "eslint --max-warnings 47 \"**/*.{js,jsx,ts,tsx}\"",
|
||||
"lint": "eslint --max-warnings 44 \"**/*.{js,jsx,ts,tsx}\"",
|
||||
"test": "pnpm recursive test --filter ./packages",
|
||||
"test:e2e:cli": "pnpm test --filter ...@verdaccio/e2e-cli",
|
||||
"test:e2e:ui": "pnpm test --filter ...@verdaccio/e2e-ui",
|
||||
@@ -120,6 +122,7 @@
|
||||
"benchmark:hyper": "verdaccio-benchmark hyper -r ./hyper-results.json",
|
||||
"benchmark:api": "verdaccio-benchmark api",
|
||||
"benchmark:submit": "pnpm ts-node ./scripts/submit-metrics.ts",
|
||||
"contributors": "ts-node ./scripts/contributors-update.ts",
|
||||
"start:watch": "concurrently --kill-others \"pnpm _build:watch\" \"pnpm _start:server\" \"pnpm _debug:reload\"",
|
||||
"_build:watch": "pnpm run --parallel watch --filter ./packages",
|
||||
"_start:server": "node --inspect packages/verdaccio/debug/bootstrap.js --listen 8000",
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @verdaccio/api
|
||||
|
||||
## 6.0.0-6-next.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b78f3525]
|
||||
- @verdaccio/logger@6.0.0-6-next.10
|
||||
- @verdaccio/auth@6.0.0-6-next.18
|
||||
- @verdaccio/hooks@6.0.0-6-next.12
|
||||
- @verdaccio/middleware@6.0.0-6-next.18
|
||||
- @verdaccio/store@6.0.0-6-next.19
|
||||
|
||||
## 6.0.0-6-next.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/api",
|
||||
"version": "6.0.0-6-next.20",
|
||||
"version": "6.0.0-6-next.21",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -39,13 +39,13 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.17",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.18",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/core": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/hooks": "workspace:6.0.0-6-next.11",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.9",
|
||||
"@verdaccio/middleware": "workspace:6.0.0-6-next.17",
|
||||
"@verdaccio/store": "workspace:6.0.0-6-next.18",
|
||||
"@verdaccio/hooks": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.10",
|
||||
"@verdaccio/middleware": "workspace:6.0.0-6-next.18",
|
||||
"@verdaccio/store": "workspace:6.0.0-6-next.19",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.10",
|
||||
"abortcontroller-polyfill": "1.7.3",
|
||||
"cookies": "0.8.0",
|
||||
@@ -57,11 +57,11 @@
|
||||
"semver": "7.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.11.19",
|
||||
"@verdaccio/server": "workspace:6.0.0-6-next.25",
|
||||
"@types/node": "16.11.21",
|
||||
"@verdaccio/server": "workspace:6.0.0-6-next.26",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.10",
|
||||
"@verdaccio/helper": "1.0.0",
|
||||
"supertest": "6.2.1"
|
||||
"supertest": "6.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
||||
@@ -55,13 +55,13 @@ export default function (route: Router, auth: IAuth, storage: Storage): void {
|
||||
try {
|
||||
// TODO: this is just temporary while I migrate all plugins to use the new API
|
||||
// the method will be renamed to getPackage again but Promise Based.
|
||||
if (!storage.getPackageNext) {
|
||||
if (!storage.getPackageByOptions) {
|
||||
throw errorUtils.getInternalError(
|
||||
'getPackageNext not implemented, check pr-2750 for more details'
|
||||
'getPackageByOptions not implemented, check pr-2750 for more details'
|
||||
);
|
||||
}
|
||||
|
||||
const manifest = await storage.getPackageNext({
|
||||
const manifest = await storage.getPackageByOptions({
|
||||
name,
|
||||
uplinksLook: true,
|
||||
req,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Package } from '@verdaccio/types';
|
||||
*/
|
||||
|
||||
export function isPublishablePackage(pkg: Package): boolean {
|
||||
// TODO: we can do better, no need get keys
|
||||
const keys: string[] = Object.keys(pkg);
|
||||
|
||||
return _.includes(keys, 'versions');
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @verdaccio/auth
|
||||
|
||||
## 6.0.0-6-next.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b78f3525]
|
||||
- @verdaccio/logger@6.0.0-6-next.10
|
||||
- @verdaccio/loaders@6.0.0-6-next.10
|
||||
|
||||
## 6.0.0-6-next.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/auth",
|
||||
"version": "6.0.0-6-next.17",
|
||||
"version": "6.0.0-6-next.18",
|
||||
"description": "logger",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -41,8 +41,8 @@
|
||||
"dependencies": {
|
||||
"@verdaccio/core": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/loaders": "workspace:6.0.0-6-next.9",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.9",
|
||||
"@verdaccio/loaders": "workspace:6.0.0-6-next.10",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.10",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.10",
|
||||
"debug": "4.3.3",
|
||||
"express": "4.17.2",
|
||||
|
||||
@@ -21,12 +21,14 @@ export async function signPayload(
|
||||
return jwt.sign(
|
||||
payload,
|
||||
secretOrPrivateKey,
|
||||
// FIXME: upgrade to the latest library and types
|
||||
// @ts-ignore
|
||||
{
|
||||
// 1 === 1ms (one millisecond)
|
||||
notBefore: '1', // Make sure the time will not rollback :)
|
||||
...options,
|
||||
},
|
||||
(error, token) => {
|
||||
(error, token: string) => {
|
||||
debug('error on sign jwt token');
|
||||
return error ? reject(error) : resolve(token);
|
||||
}
|
||||
@@ -36,5 +38,5 @@ export async function signPayload(
|
||||
|
||||
export function verifyPayload(token: string, secretOrPrivateKey: string): RemoteUser {
|
||||
debug('verify jwt token');
|
||||
return jwt.verify(token, secretOrPrivateKey);
|
||||
return jwt.verify(token, secretOrPrivateKey) as RemoteUser;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @verdaccio/cli
|
||||
|
||||
## 6.0.0-6-next.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b78f3525]
|
||||
- @verdaccio/logger@6.0.0-6-next.10
|
||||
- @verdaccio/fastify-migration@6.0.0-6-next.19
|
||||
- @verdaccio/node-api@6.0.0-6-next.27
|
||||
|
||||
## 6.0.0-6-next.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/cli",
|
||||
"version": "6.0.0-6-next.27",
|
||||
"version": "6.0.0-6-next.28",
|
||||
"author": {
|
||||
"name": "Juan Picado",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
@@ -46,9 +46,9 @@
|
||||
"dependencies": {
|
||||
"@verdaccio/core": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.9",
|
||||
"@verdaccio/node-api": "workspace:6.0.0-6-next.26",
|
||||
"@verdaccio/fastify-migration": "workspace:6.0.0-6-next.18",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.10",
|
||||
"@verdaccio/node-api": "workspace:6.0.0-6-next.27",
|
||||
"@verdaccio/fastify-migration": "workspace:6.0.0-6-next.19",
|
||||
"clipanion": "3.1.0",
|
||||
"envinfo": "7.8.1",
|
||||
"kleur": "3.0.3",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# so don't use it on production systems.
|
||||
#
|
||||
# Look here for more config file examples:
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/conf
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/packages/config/src/conf
|
||||
#
|
||||
|
||||
# path to a directory with all packages
|
||||
@@ -67,7 +67,7 @@ packages:
|
||||
# and three keywords: "$all", "$anonymous", "$authenticated"
|
||||
access: $all
|
||||
|
||||
# allow all known users to publish/publish packages
|
||||
# allow all known users to publish/unpublish packages
|
||||
# (anyone can register by default, remember?)
|
||||
publish: $authenticated
|
||||
unpublish: $authenticated
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# see https://verdaccio.org/docs/en/docker#docker-and-custom-port-configuration
|
||||
#
|
||||
# Look here for more config file examples:
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/conf
|
||||
# https://github.com/verdaccio/verdaccio/tree/master/packages/config/src/conf
|
||||
#
|
||||
|
||||
# path to a directory with all packages
|
||||
@@ -58,7 +58,7 @@ packages:
|
||||
# and three keywords: "$all", "$anonymous", "$authenticated"
|
||||
access: $all
|
||||
|
||||
# allow all known users to publish/publish packages
|
||||
# allow all known users to publish/unpublish packages
|
||||
# (anyone can register by default, remember?)
|
||||
publish: $authenticated
|
||||
unpublish: $authenticated
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"http-status-codes": "2.2.0",
|
||||
"semver": "7.3.5",
|
||||
"process-warning": "1.0.0",
|
||||
"core-js": "3.20.2"
|
||||
"core-js": "3.20.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.10"
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export * from './unclock';
|
||||
export * from './readFile';
|
||||
export * from './lockfile';
|
||||
// callback support
|
||||
export * from './legacy/unclock';
|
||||
export * from './legacy/readFile';
|
||||
export * from './legacy/lockfile';
|
||||
// promsie support
|
||||
export { readFileNext } from './readFile';
|
||||
export { lockFileNext } from './lockfile';
|
||||
export { unlockFileNext } from './utils';
|
||||
|
||||
29
packages/core/file-locking/src/legacy/lockfile.ts
Normal file
29
packages/core/file-locking/src/legacy/lockfile.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Callback } from '@verdaccio/types';
|
||||
|
||||
import { lockfile, statDir, statfile } from './utils';
|
||||
|
||||
/**
|
||||
* locks a file by creating a lock file
|
||||
* @param name
|
||||
* @param callback
|
||||
*/
|
||||
const lockFile = function (name: string, callback: Callback): void {
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
return statDir(name);
|
||||
})
|
||||
.then(() => {
|
||||
return statfile(name);
|
||||
})
|
||||
.then(() => {
|
||||
return lockfile(name);
|
||||
})
|
||||
.then(() => {
|
||||
callback(null);
|
||||
})
|
||||
.catch((err) => {
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
|
||||
export { lockFile };
|
||||
87
packages/core/file-locking/src/legacy/readFile.ts
Normal file
87
packages/core/file-locking/src/legacy/readFile.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/* eslint-disable no-undef */
|
||||
import fs from 'fs';
|
||||
|
||||
import { Callback } from '@verdaccio/types';
|
||||
|
||||
import { lockFile } from './lockfile';
|
||||
|
||||
export type ReadFileOptions = {
|
||||
parse?: boolean;
|
||||
lock?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads a local file, which involves
|
||||
* optionally taking a lock
|
||||
* reading the file contents
|
||||
* optionally parsing JSON contents
|
||||
* @param {*} name
|
||||
* @param {*} options
|
||||
* @param {*} callback
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
function readFile(
|
||||
name: string,
|
||||
options: ReadFileOptions = {},
|
||||
callback: Callback = (): void => {}
|
||||
): void {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options.lock = options.lock || false;
|
||||
options.parse = options.parse || false;
|
||||
|
||||
const lock = function (options: ReadFileOptions): Promise<null | NodeJS.ErrnoException> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
if (!options.lock) {
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
lockFile(name, function (err: NodeJS.ErrnoException | null) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
return resolve(null);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const read = function (): Promise<NodeJS.ErrnoException | string> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
fs.readFile(name, 'utf8', function (err, contents) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(contents);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const parseJSON = function (contents: string): Promise<unknown> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
if (!options.parse) {
|
||||
return resolve(contents);
|
||||
}
|
||||
try {
|
||||
contents = JSON.parse(contents);
|
||||
return resolve(contents);
|
||||
} catch (err: any) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => lock(options))
|
||||
.then(() => read())
|
||||
.then((content) => parseJSON(content as string))
|
||||
.then(
|
||||
(result) => callback(null, result),
|
||||
(err) => callback(err)
|
||||
);
|
||||
}
|
||||
|
||||
export { readFile };
|
||||
55
packages/core/file-locking/src/legacy/utils.ts
Normal file
55
packages/core/file-locking/src/legacy/utils.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import fs from 'fs';
|
||||
import locker from 'lockfile';
|
||||
import path from 'path';
|
||||
|
||||
export const statDir = (name: string): Promise<Error | null> => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
// test to see if the directory exists
|
||||
const dirPath = path.dirname(name);
|
||||
fs.stat(dirPath, function (err, stats) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
} else if (!stats.isDirectory()) {
|
||||
return resolve(new Error(`${path.dirname(name)} is not a directory`));
|
||||
} else {
|
||||
return resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const statfile = (name: string): Promise<Error | null> => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
// test to see if the directory exists
|
||||
fs.stat(name, function (err, stats) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
} else if (!stats.isFile()) {
|
||||
return resolve(new Error(`${path.dirname(name)} is not a file`));
|
||||
} else {
|
||||
return resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const lockfile = (name: string): Promise<unknown> => {
|
||||
return new Promise<void>((resolve): void => {
|
||||
const lockOpts = {
|
||||
// time (ms) to wait when checking for stale locks
|
||||
wait: 1000,
|
||||
// how often (ms) to re-check stale locks
|
||||
pollPeriod: 100,
|
||||
// locks are considered stale after 5 minutes
|
||||
stale: 5 * 60 * 1000,
|
||||
// number of times to attempt to create a lock
|
||||
retries: 100,
|
||||
// time (ms) between tries
|
||||
retryWait: 100,
|
||||
};
|
||||
const lockFileName = `${name}.lock`;
|
||||
locker.lock(lockFileName, lockOpts, () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1,29 +1,15 @@
|
||||
import { Callback } from '@verdaccio/types';
|
||||
|
||||
import { lockfile, statDir, statfile } from './utils';
|
||||
import { lockFileWithOptions, statDir, statFile } from './utils';
|
||||
|
||||
/**
|
||||
* locks a file by creating a lock file
|
||||
* @param name
|
||||
* @param callback
|
||||
*/
|
||||
const lockFile = function (name: string, callback: Callback): void {
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
return statDir(name);
|
||||
})
|
||||
.then(() => {
|
||||
return statfile(name);
|
||||
})
|
||||
.then(() => {
|
||||
return lockfile(name);
|
||||
})
|
||||
.then(() => {
|
||||
callback(null);
|
||||
})
|
||||
.catch((err) => {
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
|
||||
export { lockFile };
|
||||
export async function lockFileNext(name: string): Promise<void> {
|
||||
// check if dir exist
|
||||
await statDir(name);
|
||||
// check if file exist
|
||||
await statFile(name);
|
||||
// lock fhe the file
|
||||
await lockFileWithOptions(name);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,37 @@
|
||||
/* eslint-disable no-undef */
|
||||
import fs from 'fs';
|
||||
|
||||
import { Callback } from '@verdaccio/types';
|
||||
|
||||
import { lockFile } from './lockfile';
|
||||
import { lockFileNext } from './lockfile';
|
||||
import { readFile } from './utils';
|
||||
|
||||
export type ReadFileOptions = {
|
||||
parse?: boolean;
|
||||
lock?: boolean;
|
||||
};
|
||||
|
||||
async function lock(name: string, options: ReadFileOptions): Promise<null | void> {
|
||||
if (!options.lock) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await lockFileNext(name);
|
||||
}
|
||||
|
||||
async function read(name: string): Promise<string> {
|
||||
return readFile(name, 'utf8');
|
||||
}
|
||||
|
||||
function parseJSON(contents: string, options): Promise<unknown> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
if (!options.parse) {
|
||||
return resolve(contents);
|
||||
}
|
||||
try {
|
||||
contents = JSON.parse(contents);
|
||||
return resolve(contents);
|
||||
} catch (err: any) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a local file, which involves
|
||||
* optionally taking a lock
|
||||
@@ -19,69 +41,13 @@ export type ReadFileOptions = {
|
||||
* @param {*} options
|
||||
* @param {*} callback
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
function readFile(
|
||||
name: string,
|
||||
options: ReadFileOptions = {},
|
||||
callback: Callback = (): void => {}
|
||||
): void {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options.lock = options.lock || false;
|
||||
options.parse = options.parse || false;
|
||||
|
||||
const lock = function (options: ReadFileOptions): Promise<null | NodeJS.ErrnoException> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
if (!options.lock) {
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
lockFile(name, function (err: NodeJS.ErrnoException | null) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
return resolve(null);
|
||||
});
|
||||
});
|
||||
export async function readFileNext<T>(name: string, options: ReadFileOptions = {}): Promise<T> {
|
||||
const _options = {
|
||||
lock: options?.lock || false,
|
||||
parse: options?.parse || false,
|
||||
};
|
||||
|
||||
const read = function (): Promise<NodeJS.ErrnoException | string> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
fs.readFile(name, 'utf8', function (err, contents) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(contents);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const parseJSON = function (contents: string): Promise<unknown> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
if (!options.parse) {
|
||||
return resolve(contents);
|
||||
}
|
||||
try {
|
||||
contents = JSON.parse(contents);
|
||||
return resolve(contents);
|
||||
} catch (err: any) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => lock(options))
|
||||
.then(() => read())
|
||||
.then((content) => parseJSON(content as string))
|
||||
.then(
|
||||
(result) => callback(null, result),
|
||||
(err) => callback(err)
|
||||
);
|
||||
await lock(name, _options);
|
||||
const content = await read(name);
|
||||
const parsed = await parseJSON(content, options);
|
||||
return parsed as T;
|
||||
}
|
||||
|
||||
export { readFile };
|
||||
|
||||
@@ -1,55 +1,64 @@
|
||||
import fs from 'fs';
|
||||
import fs from 'fs/promises';
|
||||
import locker from 'lockfile';
|
||||
import path from 'path';
|
||||
import { promisify } from 'util';
|
||||
|
||||
export const statDir = (name: string): Promise<Error | null> => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
// test to see if the directory exists
|
||||
const dirPath = path.dirname(name);
|
||||
fs.stat(dirPath, function (err, stats) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
} else if (!stats.isDirectory()) {
|
||||
return resolve(new Error(`${path.dirname(name)} is not a directory`));
|
||||
} else {
|
||||
return resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
export const readFile = fs.readFile;
|
||||
const statPromise = fs.stat;
|
||||
// https://github.com/npm/lockfile/issues/33
|
||||
const lfLock = promisify(locker.lock);
|
||||
const lfUnlock = promisify(locker.unlock);
|
||||
|
||||
export const statfile = (name: string): Promise<Error | null> => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
// test to see if the directory exists
|
||||
fs.stat(name, function (err, stats) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
} else if (!stats.isFile()) {
|
||||
return resolve(new Error(`${path.dirname(name)} is not a file`));
|
||||
} else {
|
||||
return resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Test to see if the directory exists
|
||||
* @param name
|
||||
* @returns
|
||||
*/
|
||||
export async function statDir(name: string): Promise<void> {
|
||||
const dirPath = path.dirname(name);
|
||||
const stats = await statPromise(dirPath);
|
||||
if (!stats.isDirectory()) {
|
||||
throw new Error(`${path.dirname(name)} is not a directory`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
export const lockfile = (name: string): Promise<unknown> => {
|
||||
return new Promise<void>((resolve): void => {
|
||||
const lockOpts = {
|
||||
// time (ms) to wait when checking for stale locks
|
||||
wait: 1000,
|
||||
// how often (ms) to re-check stale locks
|
||||
pollPeriod: 100,
|
||||
// locks are considered stale after 5 minutes
|
||||
stale: 5 * 60 * 1000,
|
||||
// number of times to attempt to create a lock
|
||||
retries: 100,
|
||||
// time (ms) between tries
|
||||
retryWait: 100,
|
||||
};
|
||||
const lockFileName = `${name}.lock`;
|
||||
locker.lock(lockFileName, lockOpts, () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
/**
|
||||
* test to see if the directory exists
|
||||
* @param name
|
||||
* @returns
|
||||
*/
|
||||
export async function statFile(name: string): Promise<void> {
|
||||
const stats = await statPromise(name);
|
||||
if (!stats.isFile()) {
|
||||
throw new Error(`${path.dirname(name)} is not a file`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a file
|
||||
* @param name name of the file to lock
|
||||
*/
|
||||
export async function lockFileWithOptions(name: string, options?: any): Promise<void> {
|
||||
const lockOpts = {
|
||||
// time (ms) to wait when checking for stale locks
|
||||
wait: 1000,
|
||||
// how often (ms) to re-check stale locks
|
||||
pollPeriod: 100,
|
||||
// locks are considered stale after 5 minutes
|
||||
stale: 5 * 60 * 1000,
|
||||
// number of times to attempt to create a lock
|
||||
retries: 100,
|
||||
// time (ms) between tries
|
||||
retryWait: 100,
|
||||
...options,
|
||||
};
|
||||
await lfLock(`${name}.lock`, lockOpts);
|
||||
}
|
||||
|
||||
// unlocks file by removing existing lock file
|
||||
export async function unlockFileNext(name: string): Promise<void> {
|
||||
const lockFileName = `${name}.lock`;
|
||||
return lfUnlock(lockFileName);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "assets",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "assets",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "assets",
|
||||
"version": "0.0.1",
|
||||
}
|
||||
138
packages/core/file-locking/tests/lock.legacy.spec.ts
Normal file
138
packages/core/file-locking/tests/lock.legacy.spec.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { lockFile, readFile, unlockFile } from '../src/index';
|
||||
|
||||
interface Error {
|
||||
message: string;
|
||||
}
|
||||
|
||||
const getFilePath = (filename: string): string => {
|
||||
return path.resolve(__dirname, `assets/legacy/${filename}`);
|
||||
};
|
||||
|
||||
const removeTempFile = (filename: string): void => {
|
||||
const filepath = getFilePath(filename);
|
||||
fs.unlink(filepath, (error) => {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
describe('testing locking', () => {
|
||||
describe('lockFile', () => {
|
||||
test('file should be found to be locked', (done) => {
|
||||
lockFile(getFilePath('package.json'), (error: Error) => {
|
||||
expect(error).toBeNull();
|
||||
removeTempFile('package.json.lock');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('file should fail to be found to be locked', (done) => {
|
||||
lockFile(getFilePath('package.fail.json'), (error: Error) => {
|
||||
expect(error.message).toMatch(
|
||||
/ENOENT: no such file or directory, stat '(.*)package.fail.json'/
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockFile', () => {
|
||||
test('file should to be found to be unLock', (done) => {
|
||||
unlockFile(getFilePath('package.json.lock'), (error: Error) => {
|
||||
expect(error).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('readFile', () => {
|
||||
test('read file with no options should to be found to be read it as string', (done) => {
|
||||
readFile(getFilePath('package.json'), {}, (error: Error, data: string) => {
|
||||
expect(error).toBeNull();
|
||||
expect(data).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"name\\": \\"assets\\",
|
||||
\\"version\\": \\"0.0.1\\"
|
||||
}
|
||||
"
|
||||
`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('read file with no options should to be found to be read it as object', (done) => {
|
||||
const options = {
|
||||
parse: true,
|
||||
};
|
||||
readFile(getFilePath('package.json'), options, (error: Error, data: string) => {
|
||||
expect(error).toBeNull();
|
||||
expect(data).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"name": "assets",
|
||||
"version": "0.0.1",
|
||||
}
|
||||
`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('read file with options (parse) should to be not found to be read it', (done) => {
|
||||
const options = {
|
||||
parse: true,
|
||||
};
|
||||
readFile(getFilePath('package.fail.json'), options, (error: Error) => {
|
||||
expect(error.message).toMatch(
|
||||
/ENOENT: no such file or directory, open '(.*)package.fail.json'/
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('read file with options should be found to be read it and fails to be parsed', (done) => {
|
||||
const options = {
|
||||
parse: true,
|
||||
};
|
||||
readFile(getFilePath('wrong.package.json'), options, (error: Error) => {
|
||||
expect(error.message).toMatch(/Unexpected token } in JSON at position \d+/);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('read file with options (parse, lock) should be found to be read it as object', (done) => {
|
||||
const options = {
|
||||
parse: true,
|
||||
lock: true,
|
||||
};
|
||||
readFile(getFilePath('package2.json'), options, (error: Error, data: string) => {
|
||||
expect(error).toBeNull();
|
||||
expect(data).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"name": "assets",
|
||||
"version": "0.0.1",
|
||||
}
|
||||
`);
|
||||
removeTempFile('package2.json.lock');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
'read file with options (parse, lock) should be found to be read and ' + 'fails to be parsed',
|
||||
(done) => {
|
||||
const options = {
|
||||
parse: true,
|
||||
lock: true,
|
||||
};
|
||||
readFile(getFilePath('wrong.package.json'), options, (error: Error) => {
|
||||
expect(error.message).toMatch(/Unexpected token } in JSON at position \d+/);
|
||||
removeTempFile('wrong.package.json.lock');
|
||||
done();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,8 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { lockFile, readFile, unlockFile } from '../src/index';
|
||||
|
||||
interface Error {
|
||||
message: string;
|
||||
}
|
||||
import { lockFileNext, readFileNext, unlockFileNext } from '../src/index';
|
||||
import { statDir, statFile } from '../src/utils';
|
||||
|
||||
const getFilePath = (filename: string): string => {
|
||||
return path.resolve(__dirname, `assets/${filename}`);
|
||||
@@ -22,116 +19,110 @@ const removeTempFile = (filename: string): void => {
|
||||
|
||||
describe('testing locking', () => {
|
||||
describe('lockFile', () => {
|
||||
test('file should be found to be locked', (done) => {
|
||||
lockFile(getFilePath('package.json'), (error: Error) => {
|
||||
expect(error).toBeNull();
|
||||
removeTempFile('package.json.lock');
|
||||
done();
|
||||
});
|
||||
test('file should be found to be locked', async () => {
|
||||
await lockFileNext(getFilePath('package.json'));
|
||||
removeTempFile('package.json.lock');
|
||||
});
|
||||
|
||||
test('file should fail to be found to be locked', (done) => {
|
||||
lockFile(getFilePath('package.fail.json'), (error: Error) => {
|
||||
expect(error.message).toMatch(
|
||||
/ENOENT: no such file or directory, stat '(.*)package.fail.json'/
|
||||
);
|
||||
done();
|
||||
});
|
||||
test('file should fail to be found to be locked', async () => {
|
||||
await expect(lockFileNext(getFilePath('package.fail.json'))).rejects.toThrow(
|
||||
'ENOENT: no such file or directory'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unlockFile', () => {
|
||||
test('file should to be found to be unLock', (done) => {
|
||||
unlockFile(getFilePath('package.json.lock'), (error: Error) => {
|
||||
expect(error).toBeNull();
|
||||
done();
|
||||
});
|
||||
test('file should to be found to be unLock', async () => {
|
||||
await expect(unlockFileNext(getFilePath('package.json.lock'))).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('statDir', () => {
|
||||
test('error on missing dir', async () => {
|
||||
await expect(statDir(getFilePath('package.json/package.json'))).rejects.toThrow(
|
||||
'is not a directory'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('statFile', () => {
|
||||
test('error on missing dir', async () => {
|
||||
await expect(statFile(getFilePath(''))).rejects.toThrow('is not a file');
|
||||
});
|
||||
});
|
||||
|
||||
describe('readFile', () => {
|
||||
test('read file with no options should to be found to be read it as string', (done) => {
|
||||
readFile(getFilePath('package.json'), {}, (error: Error, data: string) => {
|
||||
expect(error).toBeNull();
|
||||
expect(data).toMatchInlineSnapshot(`
|
||||
test('read file with no options should to be found to be read it as string', async () => {
|
||||
const data = await readFileNext(getFilePath('package.json'), {});
|
||||
expect(data).toMatchInlineSnapshot(`
|
||||
"{
|
||||
\\"name\\": \\"assets\\",
|
||||
\\"version\\": \\"0.0.1\\"
|
||||
}
|
||||
"
|
||||
`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('read file with no options should to be found to be read it as object', (done) => {
|
||||
test('read file with no options should to be found to be read it as object', async () => {
|
||||
const options = {
|
||||
parse: true,
|
||||
};
|
||||
readFile(getFilePath('package.json'), options, (error: Error, data: string) => {
|
||||
expect(error).toBeNull();
|
||||
expect(data).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"name": "assets",
|
||||
"version": "0.0.1",
|
||||
}
|
||||
`);
|
||||
done();
|
||||
});
|
||||
const data = await readFileNext(getFilePath('package.json'), options);
|
||||
expect(data).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"name": "assets",
|
||||
"version": "0.0.1",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('read file with options (parse) should to be not found to be read it', (done) => {
|
||||
test('read file with options (parse) should to be not found to be read it', async () => {
|
||||
const options = {
|
||||
parse: true,
|
||||
};
|
||||
readFile(getFilePath('package.fail.json'), options, (error: Error) => {
|
||||
expect(error.message).toMatch(
|
||||
/ENOENT: no such file or directory, open '(.*)package.fail.json'/
|
||||
);
|
||||
done();
|
||||
});
|
||||
await expect(readFileNext(getFilePath('package.fail.json'), options)).rejects.toThrow(
|
||||
/ENOENT: no such file or directory, open '(.*)package.fail.json'/
|
||||
);
|
||||
});
|
||||
|
||||
test('read file with options should be found to be read it and fails to be parsed', (done) => {
|
||||
test('read file with options should be found to be read it and fails to be parsed', async () => {
|
||||
const options = {
|
||||
parse: true,
|
||||
};
|
||||
readFile(getFilePath('wrong.package.json'), options, (error: Error) => {
|
||||
expect(error.message).toMatch(/Unexpected token } in JSON at position \d+/);
|
||||
done();
|
||||
});
|
||||
await expect(readFileNext(getFilePath('wrong.package.json'), options)).rejects.toThrow(
|
||||
'Unexpected token } in JSON at position 44'
|
||||
);
|
||||
});
|
||||
|
||||
test('read file with options (parse, lock) should be found to be read it as object', (done) => {
|
||||
test('read file with options (parse, lock) should be found to be read it as object', async () => {
|
||||
const options = {
|
||||
parse: true,
|
||||
lock: true,
|
||||
};
|
||||
readFile(getFilePath('package2.json'), options, (error: Error, data: string) => {
|
||||
expect(error).toBeNull();
|
||||
expect(data).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"name": "assets",
|
||||
"version": "0.0.1",
|
||||
}
|
||||
`);
|
||||
removeTempFile('package2.json.lock');
|
||||
done();
|
||||
});
|
||||
await expect(
|
||||
readFileNext(getFilePath('package2.json'), options)
|
||||
).resolves.toMatchInlineSnapshot(
|
||||
`
|
||||
Object {
|
||||
"name": "assets",
|
||||
"version": "0.0.1",
|
||||
}
|
||||
`
|
||||
);
|
||||
removeTempFile('package2.json.lock');
|
||||
});
|
||||
|
||||
test(
|
||||
'read file with options (parse, lock) should be found to be read and ' + 'fails to be parsed',
|
||||
(done) => {
|
||||
async () => {
|
||||
const options = {
|
||||
parse: true,
|
||||
lock: true,
|
||||
};
|
||||
readFile(getFilePath('wrong.package.json'), options, (error: Error) => {
|
||||
expect(error.message).toMatch(/Unexpected token } in JSON at position \d+/);
|
||||
removeTempFile('wrong.package.json.lock');
|
||||
done();
|
||||
});
|
||||
await expect(readFileNext(getFilePath('wrong.package.json'), options)).rejects.toThrow(
|
||||
'Unexpected token } in JSON at position 44'
|
||||
);
|
||||
removeTempFile('wrong.package.json.lock');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @verdaccio/fastify-migration
|
||||
|
||||
## 6.0.0-6-next.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b78f3525]
|
||||
- @verdaccio/logger@6.0.0-6-next.10
|
||||
- @verdaccio/auth@6.0.0-6-next.18
|
||||
- @verdaccio/store@6.0.0-6-next.19
|
||||
|
||||
## 6.0.0-6-next.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/fastify-migration",
|
||||
"version": "6.0.0-6-next.18",
|
||||
"version": "6.0.0-6-next.19",
|
||||
"description": "Fastify server migration package",
|
||||
"keywords": [
|
||||
"private",
|
||||
@@ -36,22 +36,22 @@
|
||||
"dependencies": {
|
||||
"@verdaccio/core": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.17",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.9",
|
||||
"@verdaccio/store": "workspace:6.0.0-6-next.18",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.18",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.10",
|
||||
"@verdaccio/store": "workspace:6.0.0-6-next.19",
|
||||
"@verdaccio/tarball": "workspace:11.0.0-6-next.11",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.10",
|
||||
"@verdaccio/readme": "workspace:11.0.0-6-next.4",
|
||||
"abortcontroller-polyfill": "1.7.3",
|
||||
"core-js": "3.20.2",
|
||||
"core-js": "3.20.3",
|
||||
"debug": "4.3.3",
|
||||
"fastify": "3.25.3",
|
||||
"fastify": "3.27.0",
|
||||
"fastify-plugin": "3.0.0",
|
||||
"lodash": "4.17.21",
|
||||
"semver": "7.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.11.19",
|
||||
"@types/node": "16.11.21",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.10",
|
||||
"ts-node": "10.4.0"
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ async function manifestRoute(fastify: FastifyInstance) {
|
||||
const { packageName } = request.params;
|
||||
const storage = fastify.storage;
|
||||
debug('pkg name %s ', packageName);
|
||||
const data = await storage?.getPackageNext({
|
||||
const data = await storage?.getPackageByOptions({
|
||||
name: packageName,
|
||||
req: request.raw,
|
||||
uplinksLook: true,
|
||||
@@ -30,7 +30,7 @@ async function manifestRoute(fastify: FastifyInstance) {
|
||||
const { packageName, version } = request.params;
|
||||
const storage = fastify.storage;
|
||||
debug('pkg name %s, with version / tag: %s ', packageName, version);
|
||||
const data = await storage?.getPackageNext({
|
||||
const data = await storage?.getPackageByOptions({
|
||||
name: packageName,
|
||||
req: request.raw,
|
||||
version,
|
||||
|
||||
30
packages/core/server/src/endpoints/publish.ts
Normal file
30
packages/core/server/src/endpoints/publish.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// import buildDebug from 'debug';
|
||||
// import { FastifyInstance } from 'fastify';
|
||||
|
||||
// import { Package, Version } from '@verdaccio/types';
|
||||
|
||||
// const debug = buildDebug('verdaccio:web:api:sidebar');
|
||||
// export type $SidebarPackage = Package & { latest: Version };
|
||||
|
||||
// async function manifestRoute(fastify: FastifyInstance) {
|
||||
// // TODO: review // :_rev?/:revision?
|
||||
// fastify.put('/:packageName', async (request) => {
|
||||
// // @ts-ignore
|
||||
// const { packageName } = request.params;
|
||||
// const storage = fastify.storage;
|
||||
// debug('pkg name %s ', packageName);
|
||||
// // const data = await storage?.getPackageNext({
|
||||
// // name: packageName,
|
||||
// // req: request.raw,
|
||||
// // uplinksLook: true,
|
||||
// // requestOptions: {
|
||||
// // protocol: request.protocol,
|
||||
// // headers: request.headers as any,
|
||||
// // host: request.hostname,
|
||||
// // },
|
||||
// // });
|
||||
// // return data;
|
||||
// });
|
||||
// }
|
||||
|
||||
// export default manifestRoute;
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Package } from '@verdaccio/types';
|
||||
import { Package, Version } from '@verdaccio/types';
|
||||
import { RequestOptions } from '@verdaccio/url';
|
||||
|
||||
import { getLocalRegistryTarballUri } from './getLocalRegistryTarballUri';
|
||||
@@ -17,19 +17,47 @@ export function convertDistRemoteToLocalTarballUrls(
|
||||
request: RequestOptions,
|
||||
urlPrefix: string | void
|
||||
): Package {
|
||||
const { name, versions } = pkg;
|
||||
const convertedPkg = { ...pkg };
|
||||
const convertedVersions = versions;
|
||||
for (const ver in pkg.versions) {
|
||||
if (Object.prototype.hasOwnProperty.call(pkg.versions, ver)) {
|
||||
const distName = pkg.versions[ver].dist;
|
||||
|
||||
if (_.isNull(distName) === false && _.isNull(distName.tarball) === false) {
|
||||
distName.tarball = getLocalRegistryTarballUri(
|
||||
distName.tarball,
|
||||
pkg.name,
|
||||
request,
|
||||
urlPrefix
|
||||
);
|
||||
}
|
||||
const version = versions[ver];
|
||||
convertedVersions[ver] = convertDistVersionToLocalTarballsUrl(
|
||||
name,
|
||||
version,
|
||||
request,
|
||||
urlPrefix
|
||||
);
|
||||
}
|
||||
}
|
||||
return pkg;
|
||||
|
||||
return {
|
||||
...convertedPkg,
|
||||
versions: convertedVersions,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert single Version disst tarball
|
||||
* @param name
|
||||
* @param version
|
||||
* @param request
|
||||
* @param urlPrefix
|
||||
* @returns
|
||||
*/
|
||||
export function convertDistVersionToLocalTarballsUrl(name, version: Version, request, urlPrefix) {
|
||||
const distName = version.dist;
|
||||
|
||||
if (_.isNull(distName) === false && _.isNull(distName.tarball) === false) {
|
||||
return {
|
||||
...version,
|
||||
dist: {
|
||||
...distName,
|
||||
tarball: getLocalRegistryTarballUri(distName.tarball, name, request, urlPrefix),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { RequestOptions } from '@verdaccio/url';
|
||||
|
||||
export { convertDistRemoteToLocalTarballUrls } from './convertDistRemoteToLocalTarballUrls';
|
||||
export {
|
||||
convertDistRemoteToLocalTarballUrls,
|
||||
convertDistVersionToLocalTarballsUrl,
|
||||
} from './convertDistRemoteToLocalTarballUrls';
|
||||
export { getLocalRegistryTarballUri } from './getLocalRegistryTarballUri';
|
||||
|
||||
export { RequestOptions };
|
||||
|
||||
25
packages/core/types/index.d.ts
vendored
25
packages/core/types/index.d.ts
vendored
@@ -178,6 +178,9 @@ declare module '@verdaccio/types' {
|
||||
[key: string]: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use Manifest instead
|
||||
*/
|
||||
interface Package {
|
||||
_id?: string;
|
||||
name: string;
|
||||
@@ -192,6 +195,20 @@ declare module '@verdaccio/types' {
|
||||
_rev: string;
|
||||
}
|
||||
|
||||
interface Manifest {
|
||||
_id?: string;
|
||||
name: string;
|
||||
versions: Versions;
|
||||
'dist-tags': GenericBody;
|
||||
time: GenericBody;
|
||||
readme?: string;
|
||||
users?: PackageUsers;
|
||||
_distfiles: DistFiles;
|
||||
_attachments: AttachMents;
|
||||
_uplinks: UpLinks;
|
||||
_rev: string;
|
||||
}
|
||||
|
||||
interface IUploadTarball extends PassThrough {
|
||||
abort(): void;
|
||||
done(): void;
|
||||
@@ -477,6 +494,7 @@ declare module '@verdaccio/types' {
|
||||
createPackage(pkgName: string, value: Package, cb: CallbackAction): void;
|
||||
deletePackage(fileName: string): Promise<void>;
|
||||
removePackage(): Promise<void>;
|
||||
// @deprecated
|
||||
updatePackage(
|
||||
pkgFileName: string,
|
||||
updateHandler: StorageUpdateCallback,
|
||||
@@ -484,7 +502,14 @@ declare module '@verdaccio/types' {
|
||||
transformPackage: PackageTransformer,
|
||||
onEnd: Callback
|
||||
): void;
|
||||
// @deprecated
|
||||
savePackage(fileName: string, json: Package, callback: CallbackAction): void;
|
||||
// next packages migration (this list is meant to replace the callback parent functions)
|
||||
updatePackageNext(
|
||||
packageName: string,
|
||||
handleUpdate: (manifest: Package) => Promise<Package>
|
||||
): Promise<Package>;
|
||||
savePackageNext(name: string, value: Package): Promise<void>;
|
||||
}
|
||||
|
||||
interface TarballActions {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"build": "exit 0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.11.19",
|
||||
"@types/node": "16.11.21",
|
||||
"tsd": "0.19.1"
|
||||
},
|
||||
"funding": {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @verdaccio/hooks
|
||||
|
||||
## 6.0.0-6-next.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b78f3525]
|
||||
- @verdaccio/logger@6.0.0-6-next.10
|
||||
|
||||
## 6.0.0-6-next.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/hooks",
|
||||
"version": "6.0.0-6-next.11",
|
||||
"version": "6.0.0-6-next.12",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -31,16 +31,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/core": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.9",
|
||||
"core-js": "3.20.2",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.10",
|
||||
"core-js": "3.20.3",
|
||||
"debug": "4.3.3",
|
||||
"handlebars": "4.7.7",
|
||||
"undici": "4.12.2",
|
||||
"undici-fetch": "1.0.0-rc.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.11.19",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.17",
|
||||
"@types/node": "16.11.21",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.18",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.10"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @verdaccio/loaders
|
||||
|
||||
## 6.0.0-6-next.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b78f3525]
|
||||
- @verdaccio/logger@6.0.0-6-next.10
|
||||
|
||||
## 6.0.0-6-next.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/loaders",
|
||||
"version": "6.0.0-6-next.9",
|
||||
"version": "6.0.0-6-next.10",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -13,7 +13,7 @@
|
||||
"url": "https://github.com/verdaccio/verdaccio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.9",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.10",
|
||||
"debug": "4.3.3",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"prettier-bytes": "1.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pino": "7.6.3"
|
||||
"pino": "7.6.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @verdaccio/logger
|
||||
|
||||
## 6.0.0-6-next.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b78f3525: Fix re-opening log files using SIGUSR2
|
||||
|
||||
## 6.0.0-6-next.9
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/logger",
|
||||
"version": "6.0.0-6-next.9",
|
||||
"version": "6.0.0-6-next.10",
|
||||
"description": "logger",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -43,7 +43,7 @@
|
||||
"@verdaccio/logger-prettify": "workspace:6.0.0-6-next.6",
|
||||
"debug": "4.3.3",
|
||||
"lodash": "4.17.21",
|
||||
"pino": "7.6.3"
|
||||
"pino": "7.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.10"
|
||||
|
||||
@@ -141,7 +141,9 @@ export function setup(options: LoggerConfig | LoggerConfigItem = DEFAULT_LOGGER_
|
||||
const pinoConfig = { level: loggerConfig.level };
|
||||
if (loggerConfig.type === 'file') {
|
||||
debug('logging file enabled');
|
||||
logger = createLogger(pinoConfig, pino.destination(loggerConfig.path), loggerConfig.format);
|
||||
const destination = pino.destination(loggerConfig.path);
|
||||
process.on('SIGUSR2', () => destination.reopen());
|
||||
logger = createLogger(pinoConfig, destination, loggerConfig.format);
|
||||
} else if (loggerConfig.type === 'rotating-file') {
|
||||
warningUtils.emit(warningUtils.Codes.VERWAR003);
|
||||
debug('logging stdout enabled');
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @verdaccio/middleware
|
||||
|
||||
## 6.0.0-6-next.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b78f3525]
|
||||
- @verdaccio/logger@6.0.0-6-next.10
|
||||
- @verdaccio/auth@6.0.0-6-next.18
|
||||
|
||||
## 6.0.0-6-next.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/middleware",
|
||||
"version": "6.0.0-6-next.17",
|
||||
"version": "6.0.0-6-next.18",
|
||||
"description": "loaders logic",
|
||||
"main": "./build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -40,9 +40,9 @@
|
||||
"dependencies": {
|
||||
"debug": "4.3.3",
|
||||
"body-parser": "1.19.1",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.17",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.18",
|
||||
"@verdaccio/core": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.9",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.10",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.10",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @verdaccio/node-api
|
||||
|
||||
## 6.0.0-6-next.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b78f3525]
|
||||
- @verdaccio/logger@6.0.0-6-next.10
|
||||
- @verdaccio/server@6.0.0-6-next.26
|
||||
|
||||
## 6.0.0-6-next.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/node-api",
|
||||
"version": "6.0.0-6-next.26",
|
||||
"version": "6.0.0-6-next.27",
|
||||
"description": "node API",
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
@@ -41,19 +41,19 @@
|
||||
"dependencies": {
|
||||
"@verdaccio/core": "workspace:6.0.0-6-next.4",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.9",
|
||||
"@verdaccio/server": "workspace:6.0.0-6-next.25",
|
||||
"core-js": "3.20.2",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.10",
|
||||
"@verdaccio/server": "workspace:6.0.0-6-next.26",
|
||||
"core-js": "3.20.3",
|
||||
"debug": "4.3.3",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.11.19",
|
||||
"@types/node": "16.11.21",
|
||||
"@verdaccio/mock": "workspace:6.0.0-6-next.13",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.10",
|
||||
"jest-mock-process": "1.4.1",
|
||||
"selfsigned": "1.10.14",
|
||||
"supertest": "6.2.1"
|
||||
"supertest": "6.2.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"devDependencies": {
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.10",
|
||||
"nock": "12.0.3",
|
||||
"supertest": "6.2.1"
|
||||
"supertest": "6.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
|
||||
@@ -314,7 +314,7 @@ export default class S3PackageManager implements ILocalPackageManager {
|
||||
const error: HttpError = convertS3Error(err);
|
||||
this.logger.error(
|
||||
{ error: error.message },
|
||||
`s3: [S3PackageManager writeTarball managedUpload send]
|
||||
`s3: [S3PackageManager writeTarball managedUpload send]
|
||||
emit error @{error}`
|
||||
);
|
||||
|
||||
@@ -363,7 +363,7 @@ export default class S3PackageManager implements ILocalPackageManager {
|
||||
} else {
|
||||
this.logger.trace(
|
||||
{ name },
|
||||
`s3: [S3PackageManager writeTarball uploadStream] streamEnded
|
||||
`s3: [S3PackageManager writeTarball uploadStream] streamEnded
|
||||
false emit end @{name}`
|
||||
);
|
||||
uploadStream.on('end', onEnd);
|
||||
@@ -386,7 +386,7 @@ export default class S3PackageManager implements ILocalPackageManager {
|
||||
} finally {
|
||||
this.logger.debug(
|
||||
{ name, baseS3Params },
|
||||
`s3: [S3PackageManager writeTarball uploadStream abort]
|
||||
`s3: [S3PackageManager writeTarball uploadStream abort]
|
||||
s3.deleteObject @{name}/@baseS3Params`
|
||||
);
|
||||
|
||||
@@ -495,4 +495,24 @@ export default class S3PackageManager implements ILocalPackageManager {
|
||||
|
||||
return readTarballStream;
|
||||
}
|
||||
|
||||
// migration pending
|
||||
public async updatePackageNext(
|
||||
packageName: string,
|
||||
handleUpdate: (manifest: Package) => Promise<Package>
|
||||
): Promise<Package> {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(packageName);
|
||||
// @ts-expect-error
|
||||
await handleUpdate({});
|
||||
// @ts-expect-error
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
public async savePackageNext(name: string, value: Package): Promise<void> {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(name);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,6 +406,26 @@ class GoogleCloudStorageHandler implements IPackageStorageManager {
|
||||
.pipe(localReadStream);
|
||||
return localReadStream;
|
||||
}
|
||||
|
||||
// migration pending
|
||||
public async updatePackageNext(
|
||||
packageName: string,
|
||||
handleUpdate: (manifest: Package) => Promise<Package>
|
||||
): Promise<Package> {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(packageName);
|
||||
// @ts-expect-error
|
||||
await handleUpdate({});
|
||||
// @ts-expect-error
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
public async savePackageNext(name: string, value: Package): Promise<void> {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(name);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(value);
|
||||
}
|
||||
}
|
||||
|
||||
export default GoogleCloudStorageHandler;
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@verdaccio/file-locking": "workspace:11.0.0-6-next.4",
|
||||
"apache-md5": "1.1.7",
|
||||
"bcryptjs": "2.4.3",
|
||||
"core-js": "3.20.2",
|
||||
"core-js": "3.20.3",
|
||||
"http-errors": "1.8.1",
|
||||
"unix-crypt-td-js": "1.1.4"
|
||||
},
|
||||
|
||||
@@ -209,6 +209,9 @@ describe('HTPasswd', () => {
|
||||
test('reload should fails on check file', (done) => {
|
||||
jest.doMock('fs', () => {
|
||||
return {
|
||||
readFile: (_name, callback): void => {
|
||||
callback(new Error('stat error'), null);
|
||||
},
|
||||
stat: (_name, callback): void => {
|
||||
callback(new Error('stat error'), null);
|
||||
},
|
||||
@@ -228,6 +231,9 @@ describe('HTPasswd', () => {
|
||||
test('reload times match', (done) => {
|
||||
jest.doMock('fs', () => {
|
||||
return {
|
||||
readFile: (_name, callback): void => {
|
||||
callback(new Error('stat error'), null);
|
||||
},
|
||||
stat: (_name, callback): void => {
|
||||
callback(null, {
|
||||
mtime: null,
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"@verdaccio/file-locking": "workspace:11.0.0-6-next.4",
|
||||
"@verdaccio/streams": "workspace:11.0.0-6-next.5",
|
||||
"async": "3.2.3",
|
||||
"core-js": "3.20.2",
|
||||
"core-js": "3.20.3",
|
||||
"debug": "4.3.3",
|
||||
"globby": "11.1.0",
|
||||
"lockfile": "1.0.4",
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import fs from 'fs/promises';
|
||||
|
||||
// FUTURE: when v15 is min replace by fs/promises
|
||||
const readFile = promisify(fs.readFile);
|
||||
const mkdirPromise = promisify(fs.mkdir);
|
||||
const writeFilePromise = promisify(fs.writeFile);
|
||||
const readdirPromise = promisify(fs.readdir);
|
||||
const statPromise = promisify(fs.stat);
|
||||
const unlinkPromise = promisify(fs.unlink);
|
||||
const rmdirPromise = promisify(fs.rmdir);
|
||||
const readFile = fs.readFile;
|
||||
const mkdirPromise = fs.mkdir;
|
||||
const writeFilePromise = fs.writeFile;
|
||||
const readdirPromise = fs.readdir;
|
||||
const statPromise = fs.stat;
|
||||
const unlinkPromise = fs.unlink;
|
||||
const rmdirPromise = fs.rmdir;
|
||||
const renamePromise = fs.rename;
|
||||
|
||||
export const readFilePromise = async (path) => {
|
||||
return await readFile(path, 'utf8');
|
||||
};
|
||||
|
||||
export { mkdirPromise, writeFilePromise, readdirPromise, statPromise, unlinkPromise, rmdirPromise };
|
||||
export {
|
||||
renamePromise,
|
||||
mkdirPromise,
|
||||
writeFilePromise,
|
||||
readdirPromise,
|
||||
statPromise,
|
||||
unlinkPromise,
|
||||
rmdirPromise,
|
||||
};
|
||||
|
||||
@@ -5,11 +5,18 @@ import _ from 'lodash';
|
||||
import path from 'path';
|
||||
|
||||
import { VerdaccioError, errorUtils } from '@verdaccio/core';
|
||||
import { readFile, unlockFile } from '@verdaccio/file-locking';
|
||||
import { readFile, readFileNext, unlockFile, unlockFileNext } from '@verdaccio/file-locking';
|
||||
import { ReadTarball, UploadTarball } from '@verdaccio/streams';
|
||||
import { Callback, ILocalPackageManager, IUploadTarball, Logger, Package } from '@verdaccio/types';
|
||||
|
||||
import { readFilePromise, rmdirPromise, unlinkPromise } from './fs';
|
||||
import {
|
||||
mkdirPromise,
|
||||
readFilePromise,
|
||||
renamePromise,
|
||||
rmdirPromise,
|
||||
unlinkPromise,
|
||||
writeFilePromise,
|
||||
} from './fs';
|
||||
|
||||
export const fileExist = 'EEXISTS';
|
||||
export const noSuchFile = 'ENOENT';
|
||||
@@ -54,6 +61,21 @@ const renameTmp = function (src, dst, _cb): void {
|
||||
});
|
||||
};
|
||||
|
||||
export async function renameTmpNext(src: string, dst: string): Promise<void> {
|
||||
if (process.platform !== 'win32') {
|
||||
await renamePromise(src, dst);
|
||||
await unlinkPromise(src);
|
||||
} else {
|
||||
// TODO: review if this still the cases
|
||||
// windows can't remove opened file,
|
||||
// but it seem to be able to rename it
|
||||
const tmp = tempFile(dst);
|
||||
await renamePromise(dst, tmp);
|
||||
await renamePromise(src, dst);
|
||||
await unlinkPromise(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
export type ILocalFSPackageManager = ILocalPackageManager & { path: string };
|
||||
|
||||
export default class LocalFS implements ILocalFSPackageManager {
|
||||
@@ -110,6 +132,7 @@ export default class LocalFS implements ILocalFSPackageManager {
|
||||
onEnd(..._args);
|
||||
}
|
||||
};
|
||||
// //////////////////////////////////////
|
||||
|
||||
if (!err) {
|
||||
locked = true;
|
||||
@@ -136,6 +159,74 @@ export default class LocalFS implements ILocalFSPackageManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows to update the package
|
||||
* This function handle the update package logic, for this plugin
|
||||
* we need to lock/unlock handlers for thread-safely and then apply
|
||||
* the `handleUpdate` and return the result.
|
||||
*
|
||||
* The lock could fail on several steps so we need to ensure the
|
||||
* file does not get locked if the whole process fails.
|
||||
*
|
||||
Algorithm:
|
||||
1. lock package.json for writing
|
||||
2. read package.json
|
||||
3. apply external update package handler
|
||||
4. return manifest (write is being hanlded into the core)
|
||||
5. rename package.json.tmp package.json
|
||||
* @param {*} packageName
|
||||
* The update package handler could be different based
|
||||
* on the action and handled into the core.
|
||||
* @param {*} handleUpdate
|
||||
*/
|
||||
public async updatePackageNext(
|
||||
packageName: string,
|
||||
handleUpdate: (manifest: Package) => Promise<Package>
|
||||
): Promise<Package> {
|
||||
// this plugin lock files on write, we handle all possible scenarios
|
||||
let locked = false;
|
||||
let manifestUpdated: Package;
|
||||
try {
|
||||
const manifest = await this._lockAndReadJSONNext(packageJSONFileName);
|
||||
locked = true;
|
||||
manifestUpdated = await handleUpdate(manifest);
|
||||
if (locked) {
|
||||
debug('unlock %s', packageJSONFileName);
|
||||
await this._unlockJSONNext(packageJSONFileName);
|
||||
this.logger.debug({ packageName }, 'the package @{packageName} has been updated');
|
||||
return manifestUpdated;
|
||||
} else {
|
||||
this.logger.debug({ packageName }, 'the package @{packageName} has been updated');
|
||||
return manifestUpdated;
|
||||
}
|
||||
} catch (err: any) {
|
||||
// we ensure lock the file
|
||||
this.logger.error(
|
||||
{ err, packageName },
|
||||
'error @{err.message} on update package @{packageName}'
|
||||
);
|
||||
if (locked) {
|
||||
// eslint-disable-next-line no-useless-catch
|
||||
try {
|
||||
await this._unlockJSONNext(packageJSONFileName);
|
||||
// after unlock bubble up error.
|
||||
throw err;
|
||||
} catch (err: any) {
|
||||
// unlock could fail, we bubble up error
|
||||
throw errorUtils.getInternalError('resource temporarily unavailable');
|
||||
}
|
||||
} else {
|
||||
if (err.code === resourceNotAvailable) {
|
||||
throw errorUtils.getInternalError('resource temporarily unavailable');
|
||||
} else if (err.code === noSuchFile) {
|
||||
throw errorUtils.getNotFound();
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async deletePackage(packageName: string): Promise<void> {
|
||||
debug('delete a file/package %o', packageName);
|
||||
|
||||
@@ -160,6 +251,12 @@ export default class LocalFS implements ILocalFSPackageManager {
|
||||
this._writeFile(this._getStorage(packageJSONFileName), this._convertToString(value), cb);
|
||||
}
|
||||
|
||||
public async savePackageNext(name: string, value: Package): Promise<void> {
|
||||
debug('save a package %o', name);
|
||||
|
||||
await this.writeFileNext(this._getStorage(packageJSONFileName), this._convertToString(value));
|
||||
}
|
||||
|
||||
public async readPackageNext(name: string): Promise<Package> {
|
||||
debug('read a package %o', name);
|
||||
try {
|
||||
@@ -371,6 +468,35 @@ export default class LocalFS implements ILocalFSPackageManager {
|
||||
});
|
||||
}
|
||||
|
||||
private async writeTempFileAndRename(dest: string, fileContent: string): Promise<any> {
|
||||
const tempFilePath = tempFile(dest);
|
||||
try {
|
||||
// write file on temp location
|
||||
await writeFilePromise(tempFilePath, fileContent);
|
||||
debug('creating a new file:: %o', dest);
|
||||
// rename tmp file to original
|
||||
await renameTmpNext(tempFilePath, dest);
|
||||
} catch (err: any) {
|
||||
debug('error on write the file: %o', dest);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private async writeFileNext(destiny: string, fileContent: string): Promise<void> {
|
||||
try {
|
||||
await this.writeTempFileAndRename(destiny, fileContent);
|
||||
} catch (err: any) {
|
||||
if (err && err.code === noSuchFile) {
|
||||
// if fails, we create the folder for the package
|
||||
await mkdirPromise(path.dirname(destiny), { recursive: true });
|
||||
// we try again create the temp file
|
||||
await this.writeTempFileAndRename(destiny, fileContent);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _lockAndReadJSON(name: string, cb: Function): void {
|
||||
const fileName: string = this._getStorage(name);
|
||||
debug('lock and read a file %o', fileName);
|
||||
@@ -397,4 +523,25 @@ export default class LocalFS implements ILocalFSPackageManager {
|
||||
private _unlockJSON(name: string, cb: Function): void {
|
||||
unlockFile(this._getStorage(name), cb);
|
||||
}
|
||||
|
||||
private async _lockAndReadJSONNext(name: string): Promise<Package> {
|
||||
const fileName: string = this._getStorage(name);
|
||||
debug('lock and read a file %o', fileName);
|
||||
try {
|
||||
const data = await readFileNext<Package>(fileName, {
|
||||
lock: true,
|
||||
parse: true,
|
||||
});
|
||||
return data;
|
||||
} catch (err) {
|
||||
this.logger.error({ err }, 'error on lock file @{err.message}');
|
||||
debug('error on lock and read json for file: %o', name);
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private async _unlockJSONNext(name: string): Promise<void> {
|
||||
await unlockFileNext(this._getStorage(name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +193,23 @@ class MemoryHandler implements IPackageStorageManager {
|
||||
debug('get storage %o', name);
|
||||
return this.data[name];
|
||||
}
|
||||
|
||||
// migration pending
|
||||
public async updatePackageNext(
|
||||
packageName: string,
|
||||
handleUpdate: (manifest: Package) => Promise<Package>
|
||||
): Promise<Package> {
|
||||
debug(packageName);
|
||||
// @ts-expect-error
|
||||
await handleUpdate({});
|
||||
// @ts-expect-error
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
public async savePackageNext(name: string, value: Package): Promise<void> {
|
||||
debug(name);
|
||||
debug(value);
|
||||
}
|
||||
}
|
||||
|
||||
export default MemoryHandler;
|
||||
|
||||
4
packages/plugins/ui-theme/.npmignore
Normal file
4
packages/plugins/ui-theme/.npmignore
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
!/static/**/*
|
||||
!index.js
|
||||
!README.md
|
||||
@@ -1,5 +1,23 @@
|
||||
# @verdaccio/ui-theme
|
||||
|
||||
## 6.0.0-6-next.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c9089631: fix: specific version package detail page not showing
|
||||
|
||||
## 6.0.0-6-next.16
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- ad3151c3: fix: remove engines from ui-theme
|
||||
|
||||
## 6.0.0-6-next.15
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 7ff4808b: feat: improve registry info dialog and language switch
|
||||
|
||||
## 6.0.0-6-next.14
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -35,5 +35,7 @@ module.exports = Object.assign({}, config, {
|
||||
'verdaccio-ui/utils/(.*)': '<rootDir>/src/utils/$1',
|
||||
'verdaccio-ui/providers/(.*)': '<rootDir>/src/providers/$1',
|
||||
'verdaccio-ui/design-tokens/(.*)': '<rootDir>/src/design-tokens/$1',
|
||||
'react-markdown': '<rootDir>/src/__mocks__/react-markdown.tsx',
|
||||
'remark-*': '<rootDir>/src/__mocks__/remark-plugin.ts',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/ui-theme",
|
||||
"version": "6.0.0-6-next.14",
|
||||
"version": "6.0.0-6-next.17",
|
||||
"description": "Verdaccio User Interface",
|
||||
"author": {
|
||||
"name": "Verdaccio Contributors",
|
||||
@@ -12,32 +12,28 @@
|
||||
},
|
||||
"homepage": "https://verdaccio.org",
|
||||
"main": "index.js",
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "17.0.38",
|
||||
"@types/react-autosuggest": "10.1.5",
|
||||
"@types/react-dom": "17.0.11",
|
||||
"@types/react-helmet": "6.1.5",
|
||||
"@types/redux": "3.6.0",
|
||||
"@types/react-router-dom": "5.3.2",
|
||||
"@types/react-router-dom": "5.3.3",
|
||||
"@types/react-virtualized": "9.21.16",
|
||||
"@emotion/react": "11.7.1",
|
||||
"@emotion/jest": "11.7.1",
|
||||
"@emotion/styled": "11.6.0",
|
||||
"@emotion/css": "11.7.1",
|
||||
"@emotion/babel-plugin": "11.7.2",
|
||||
"@mui/icons-material": "5.2.5",
|
||||
"@mui/material": "5.2.8",
|
||||
"@mui/styles": "5.2.3",
|
||||
"@mui/icons-material": "5.3.0",
|
||||
"@mui/material": "5.3.0",
|
||||
"@mui/styles": "5.3.0",
|
||||
"@rematch/core": "2.2.0",
|
||||
"@rematch/loading": "2.1.2",
|
||||
"@testing-library/dom": "8.11.1",
|
||||
"@testing-library/dom": "8.11.2",
|
||||
"@testing-library/jest-dom": "5.16.1",
|
||||
"@testing-library/react": "12.1.2",
|
||||
"@verdaccio/node-api": "workspace:6.0.0-6-next.26",
|
||||
"@verdaccio/node-api": "workspace:6.0.0-6-next.27",
|
||||
"@verdaccio/types": "workspace:*",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-dynamic-import-node": "2.3.3",
|
||||
@@ -56,15 +52,17 @@
|
||||
"js-yaml": "3.14.1",
|
||||
"localstorage-memory": "1.0.3",
|
||||
"lodash": "4.17.21",
|
||||
"mini-css-extract-plugin": "2.4.6",
|
||||
"mini-css-extract-plugin": "2.5.2",
|
||||
"mutationobserver-shim": "0.3.7",
|
||||
"node-mocks-http": "1.11.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"react-markdown": "8.0.0",
|
||||
"remark-gfm": "3.0.1",
|
||||
"optimize-css-assets-webpack-plugin": "6.0.1",
|
||||
"ora": "5.4.1",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-hook-form": "7.23.0",
|
||||
"react-hook-form": "7.25.0",
|
||||
"react-hot-loader": "4.13.0",
|
||||
"react-i18next": "11.15.3",
|
||||
"react-router": "5.2.1",
|
||||
@@ -73,18 +71,18 @@
|
||||
"react-redux": "7.2.6",
|
||||
"redux": "4.1.2",
|
||||
"rimraf": "3.0.2",
|
||||
"msw": "0.36.4",
|
||||
"msw": "0.36.5",
|
||||
"style-loader": "3.3.1",
|
||||
"stylelint": "13.13.1",
|
||||
"stylelint-config-recommended": "5.0.0",
|
||||
"stylelint": "14.3.0",
|
||||
"stylelint-config-recommended": "6.0.0",
|
||||
"stylelint-config-styled-components": "0.1.1",
|
||||
"stylelint-processor-styled-components": "1.10.0",
|
||||
"stylelint-webpack-plugin": "3.1.0",
|
||||
"supertest": "6.2.1",
|
||||
"stylelint-webpack-plugin": "3.1.1",
|
||||
"supertest": "6.2.2",
|
||||
"terser-webpack-plugin": "5.3.0",
|
||||
"url-loader": "4.1.1",
|
||||
"validator": "13.7.0",
|
||||
"webpack": "5.66.0",
|
||||
"webpack": "5.67.0",
|
||||
"webpack-bundle-analyzer": "4.5.0",
|
||||
"webpack-bundle-size-analyzer": "3.1.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
|
||||
@@ -23,7 +23,6 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
|
||||
const [showMobileNavBar, setShowMobileNavBar] = useState<boolean>(false);
|
||||
const [showLoginModal, setShowLoginModal] = useState<boolean>(false);
|
||||
const loginStore = useSelector((state: RootState) => state.login);
|
||||
const configStore = useSelector((state: RootState) => state.configuration.config);
|
||||
const { configOptions } = useConfig();
|
||||
const dispatch = useDispatch<Dispatch>();
|
||||
const handleLogout = () => {
|
||||
@@ -45,12 +44,12 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
|
||||
withoutSearch={withoutSearch}
|
||||
/>
|
||||
</InnerNavBar>
|
||||
<HeaderInfoDialog
|
||||
isOpen={isInfoDialogOpen}
|
||||
onCloseDialog={() => setOpenInfoDialog(false)}
|
||||
registryUrl={configOptions.base}
|
||||
scope={configStore.scope}
|
||||
/>
|
||||
{
|
||||
<HeaderInfoDialog
|
||||
isOpen={isInfoDialogOpen}
|
||||
onCloseDialog={() => setOpenInfoDialog(false)}
|
||||
/>
|
||||
}
|
||||
</NavBar>
|
||||
{showMobileNavBar && !withoutSearch && (
|
||||
<MobileNavBar>
|
||||
|
||||
@@ -1,19 +1,82 @@
|
||||
/* eslint-disable verdaccio/jsx-spread */
|
||||
import styled from '@emotion/styled';
|
||||
import Box from '@mui/material/Box';
|
||||
import Tab from '@mui/material/Tab';
|
||||
import Tabs from '@mui/material/Tabs';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { useSelector } from 'react-redux';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
|
||||
import { RootState } from '../../store/store';
|
||||
import LanguageSwitch from './LanguageSwitch';
|
||||
import RegistryInfoContent from './RegistryInfoContent';
|
||||
import RegistryInfoDialog from './RegistryInfoDialog';
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onCloseDialog: () => void;
|
||||
registryUrl: string;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
const HeaderInfoDialog: React.FC<Props> = ({ onCloseDialog, isOpen, registryUrl, scope }) => (
|
||||
<RegistryInfoDialog onClose={onCloseDialog} open={isOpen}>
|
||||
<RegistryInfoContent registryUrl={registryUrl} scope={scope} />
|
||||
</RegistryInfoDialog>
|
||||
);
|
||||
function a11yProps(index) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
function TabPanel(props) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
role="tabpanel"
|
||||
{...other}
|
||||
>
|
||||
{value === index && <Box sx={{ paddingTop: 3 }}>{children}</Box>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const TextContent = styled('div')<{ theme?: Theme }>(({ theme }) => ({
|
||||
padding: '10px 0',
|
||||
backgroundColor: theme?.palette.background.default,
|
||||
}));
|
||||
|
||||
const HeaderInfoDialog: React.FC<Props> = ({ onCloseDialog, isOpen }) => {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
const configStore = useSelector((state: RootState) => state.configuration.config);
|
||||
const { scope, base } = configStore;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<RegistryInfoDialog onClose={onCloseDialog} open={isOpen}>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs aria-label="basic tabs example" onChange={handleChange} value={value}>
|
||||
<Tab label={t('packageManagers.title')} {...a11yProps(0)} />
|
||||
<Tab label={t('language.title')} {...a11yProps(1)} />
|
||||
</Tabs>
|
||||
</Box>
|
||||
<TabPanel index={0} value={value}>
|
||||
<RegistryInfoContent registryUrl={base} scope={scope} />
|
||||
</TabPanel>
|
||||
<TabPanel index={1} value={value}>
|
||||
<TextContent>{t('language.description')}</TextContent>
|
||||
<LanguageSwitch />
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{t('language.contribute')}</ReactMarkdown>
|
||||
</TabPanel>
|
||||
</Box>
|
||||
</RegistryInfoDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderInfoDialog;
|
||||
|
||||
@@ -52,12 +52,7 @@ const HeaderMenu: React.FC<Props> = ({
|
||||
<MenuItem>
|
||||
<HeaderGreetings username={username} />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
button={true}
|
||||
data-testid="logOutDialogIcon"
|
||||
id="logOutDialogIcon"
|
||||
onClick={onLogout}
|
||||
>
|
||||
<MenuItem data-testid="logOutDialogIcon" id="logOutDialogIcon" onClick={onLogout}>
|
||||
{t('button.logout')}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -5,7 +5,6 @@ import ThemeContext from 'verdaccio-ui/design-tokens/ThemeContext';
|
||||
|
||||
import HeaderMenu from './HeaderMenu';
|
||||
import HeaderToolTip from './HeaderToolTip';
|
||||
import LanguageSwitch from './LanguageSwitch';
|
||||
import { RightSide } from './styles';
|
||||
|
||||
interface Props {
|
||||
@@ -79,7 +78,6 @@ const HeaderRight: React.FC<Props> = ({
|
||||
tooltipIconType={'search'}
|
||||
/>
|
||||
)}
|
||||
<LanguageSwitch />
|
||||
<HeaderToolTip title={t('header.documentation')} tooltipIconType={'help'} />
|
||||
<HeaderToolTip
|
||||
onClick={onOpenRegistryInfoDialog}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import Tooltip from 'verdaccio-ui/components/Tooltip';
|
||||
|
||||
import HeaderToolTipIcon, { TooltipIconType } from './HeaderToolTipIcon';
|
||||
|
||||
@@ -9,10 +8,8 @@ interface Props {
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const HeaderToolTip: React.FC<Props> = ({ tooltipIconType, title, onClick }) => (
|
||||
<Tooltip disableFocusListener={true} title={title}>
|
||||
<HeaderToolTipIcon onClick={onClick} tooltipIconType={tooltipIconType} />
|
||||
</Tooltip>
|
||||
const HeaderToolTip: React.FC<Props> = ({ tooltipIconType, onClick }) => (
|
||||
<HeaderToolTipIcon onClick={onClick} tooltipIconType={tooltipIconType} />
|
||||
);
|
||||
|
||||
export default HeaderToolTip;
|
||||
|
||||
@@ -50,7 +50,7 @@ const HeaderToolTipIcon = forwardRef<HeaderToolTipIconRef, Props>(function Heade
|
||||
);
|
||||
case 'search':
|
||||
return (
|
||||
<IconSearchButton color="inherit" onClick={onClick}>
|
||||
<IconSearchButton color="inherit" onClick={onClick} ref={ref}>
|
||||
<Search />
|
||||
</IconSearchButton>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
import styled from '@emotion/styled';
|
||||
import LanguageIcon from '@mui/icons-material/Language';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import i18next from 'i18next';
|
||||
import React, { useCallback, useContext, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useContext, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AutoComplete } from 'verdaccio-ui/components/AutoComplete/AutoCompleteV2';
|
||||
import MenuItem from 'verdaccio-ui/components/MenuItem';
|
||||
import ThemeContext from 'verdaccio-ui/design-tokens/ThemeContext';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
|
||||
@@ -20,26 +20,42 @@ const listConverted = listLanguages.reduce((prev, item) => {
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
export const CardSelected = styled(Card)<{ theme?: Theme }>(({ theme }) => {
|
||||
return {
|
||||
backgroundColor: theme?.palette?.grey['600'],
|
||||
opacity: '0.9',
|
||||
color: theme?.palette?.error.contrastText,
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
});
|
||||
|
||||
export const CardUnSelected = styled(Card)<{ theme?: Theme }>(({ theme }) => {
|
||||
return {
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
backgroundColor: theme?.palette.greyGainsboro,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const LanguageContent = ({ translation, icon }) => (
|
||||
<>
|
||||
<CardContent>
|
||||
<Typography display="block" gutterBottom={true} variant="caption">
|
||||
{translation}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardContent>{icon}</CardContent>
|
||||
</>
|
||||
);
|
||||
|
||||
const LanguageSwitch = () => {
|
||||
const themeContext = useContext(ThemeContext);
|
||||
const themeContext = useContext(ThemeContext) as any;
|
||||
const [languages] = useState<Language[]>(
|
||||
Object.keys(i18next.options?.resources || {}) as Language[]
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!themeContext) {
|
||||
throw Error(t('theme-context-not-correct-used'));
|
||||
}
|
||||
|
||||
const currentLanguage = themeContext.language;
|
||||
|
||||
const switchLanguage = useCallback(
|
||||
({ language }: { language: Language }) => {
|
||||
themeContext.setLanguage(language);
|
||||
},
|
||||
[themeContext]
|
||||
);
|
||||
|
||||
const getCurrentLngDetails = useCallback(
|
||||
(language: Language) => {
|
||||
const lng = listConverted[language] || listConverted['en-US'];
|
||||
@@ -50,75 +66,35 @@ const LanguageSwitch = () => {
|
||||
},
|
||||
[t]
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
languages.map((language) => {
|
||||
const { icon, translation } = getCurrentLngDetails(language);
|
||||
return {
|
||||
language,
|
||||
icon,
|
||||
translation,
|
||||
};
|
||||
}),
|
||||
[languages, getCurrentLngDetails]
|
||||
);
|
||||
|
||||
const option = useCallback(
|
||||
({ icon, translation }: ReturnType<typeof getCurrentLngDetails>) => (
|
||||
<StyledMenuItem component="div">
|
||||
{icon}
|
||||
{translation}
|
||||
</StyledMenuItem>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
const optionLabel = useCallback(
|
||||
({ translation }: ReturnType<typeof getCurrentLngDetails>) => translation,
|
||||
[]
|
||||
const handleChangeLanguage = useCallback(
|
||||
(language: Language) => {
|
||||
themeContext.setLanguage(language);
|
||||
},
|
||||
[themeContext]
|
||||
);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<AutoComplete
|
||||
defaultValue={getCurrentLngDetails(currentLanguage).translation}
|
||||
getOptionLabel={optionLabel}
|
||||
hasClearIcon={true}
|
||||
inputStartAdornment={<StyledLanguageIcon />}
|
||||
onClick={switchLanguage}
|
||||
options={options}
|
||||
renderOption={option}
|
||||
variant="outlined"
|
||||
/>
|
||||
</Wrapper>
|
||||
<div>
|
||||
<Grid columns={{ xs: 12, sm: 12, md: 12 }} container={true} spacing={{ xs: 1, md: 1 }}>
|
||||
{languages.map((language, index) => {
|
||||
const { icon, translation } = getCurrentLngDetails(language);
|
||||
return (
|
||||
<Grid item={true} key={index} sm={2} xs={6}>
|
||||
{language === currentLanguage ? (
|
||||
<CardSelected sx={{ maxWidth: 80 }}>
|
||||
<LanguageContent icon={icon} translation={translation} />
|
||||
</CardSelected>
|
||||
) : (
|
||||
<CardUnSelected onClick={() => handleChangeLanguage(language)}>
|
||||
<LanguageContent icon={icon} translation={translation} />
|
||||
</CardUnSelected>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSwitch;
|
||||
|
||||
const StyledLanguageIcon = styled(LanguageIcon)<{ theme?: Theme }>(({ theme }) => ({
|
||||
color: theme?.palette.white,
|
||||
}));
|
||||
|
||||
const Wrapper = styled('div')<{ theme?: Theme }>(({ theme }) => ({
|
||||
width: 220,
|
||||
display: 'none',
|
||||
[`@media screen and (min-width: ${theme?.breakPoints.medium}px)`]: {
|
||||
display: 'inline-block',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledMenuItem = withStyles((theme: Theme) => ({
|
||||
root: {
|
||||
display: 'grid',
|
||||
cursor: 'pointer',
|
||||
gridGap: theme?.spacing(0.5),
|
||||
gridTemplateColumns: '20px 1fr',
|
||||
alignItems: 'center',
|
||||
'&:first-child': {
|
||||
borderTopLeftRadius: 4,
|
||||
borderTopRightRadius: 4,
|
||||
},
|
||||
},
|
||||
}))(MenuItem);
|
||||
|
||||
@@ -9,8 +9,10 @@ describe('<RegistryInfoContent /> component', () => {
|
||||
});
|
||||
|
||||
test('should load the component with no data', () => {
|
||||
render(<RegistryInfoContent registryUrl={''} scope={''} />);
|
||||
expect(screen.getByText('No configurations available')).toBeInTheDocument();
|
||||
render(<RegistryInfoContent registryUrl="http://localhost:4873" scope={''} />);
|
||||
expect(
|
||||
screen.getByText(/This is the configuration details for the registry./)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should load the appropiate tab content when the tab is clicked', () => {
|
||||
|
||||
@@ -9,18 +9,23 @@ import Typography from '@mui/material/Typography';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import React, { FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import CopyToClipBoard from 'verdaccio-ui/components/CopyToClipBoard';
|
||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
||||
import {
|
||||
getCLIChangePassword,
|
||||
getCLISBerryYamlRegistry,
|
||||
getCLISetConfigRegistry,
|
||||
getCLISetRegistry,
|
||||
} from 'verdaccio-ui/utils/cli-utils';
|
||||
import { NODE_MANAGER } from 'verdaccio-ui/utils/constants';
|
||||
|
||||
const renderNpmTab = (scope: string, registryUrl: string): JSX.Element => {
|
||||
import { Description, TextContent } from './styles';
|
||||
|
||||
const renderNpmTab = (scope: string | undefined, registryUrl: string): JSX.Element => {
|
||||
return (
|
||||
<Box display="flex" flexDirection="column" p={1}>
|
||||
<Box display="flex" flexDirection="column">
|
||||
<CopyToClipBoard
|
||||
text={getCLISetConfigRegistry(`${NODE_MANAGER.npm} set`, scope, registryUrl)}
|
||||
/>
|
||||
@@ -30,9 +35,9 @@ const renderNpmTab = (scope: string, registryUrl: string): JSX.Element => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderPnpmTab = (scope: string, registryUrl: string): JSX.Element => {
|
||||
const renderPnpmTab = (scope: string | undefined, registryUrl: string): JSX.Element => {
|
||||
return (
|
||||
<Box display="flex" flexDirection="column" p={1}>
|
||||
<Box display="flex" flexDirection="column">
|
||||
<CopyToClipBoard
|
||||
text={getCLISetConfigRegistry(`${NODE_MANAGER.pnpm} set`, scope, registryUrl)}
|
||||
/>
|
||||
@@ -42,9 +47,9 @@ const renderPnpmTab = (scope: string, registryUrl: string): JSX.Element => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderYarnTab = (scope: string, registryUrl: string): JSX.Element => {
|
||||
const renderYarnTab = (scope: string | undefined, registryUrl: string): JSX.Element => {
|
||||
return (
|
||||
<Box display="flex" flexDirection="column" p={1}>
|
||||
<Box display="flex" flexDirection="column">
|
||||
<CopyToClipBoard
|
||||
text={getCLISetConfigRegistry(`${NODE_MANAGER.yarn} config set`, scope, registryUrl)}
|
||||
/>
|
||||
@@ -52,6 +57,14 @@ const renderYarnTab = (scope: string, registryUrl: string): JSX.Element => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderYarnBerryTab = (scope: string | undefined, registryUrl: string): JSX.Element => {
|
||||
return (
|
||||
<Box display="flex" flexDirection="column">
|
||||
<CopyToClipBoard text={getCLISBerryYamlRegistry(scope, registryUrl)} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
width: '100%',
|
||||
@@ -63,7 +76,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
}));
|
||||
|
||||
export const AccordionContainer = styled('div')({
|
||||
padding: 30,
|
||||
padding: 0,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
});
|
||||
@@ -79,7 +92,7 @@ export const LinkContainer = styled('div')({
|
||||
|
||||
export type Props = {
|
||||
registryUrl: string;
|
||||
scope: string;
|
||||
scope: string | undefined;
|
||||
};
|
||||
|
||||
const RegistryInfoContent: FC<Props> = ({ scope, registryUrl }) => {
|
||||
@@ -90,15 +103,13 @@ const RegistryInfoContent: FC<Props> = ({ scope, registryUrl }) => {
|
||||
const hasNpm = configOptions?.pkgManagers?.includes('npm');
|
||||
const hasYarn = configOptions?.pkgManagers?.includes('yarn');
|
||||
const hasPnpm = configOptions?.pkgManagers?.includes('pnpm');
|
||||
const hasPkgManagers = hasNpm | hasPnpm | hasYarn;
|
||||
if (!hasPkgManagers || !scope || !registryUrl) {
|
||||
return <AccordionContainer>{t('header.registry-no-conf')}</AccordionContainer>;
|
||||
}
|
||||
|
||||
return hasPkgManagers ? (
|
||||
<AccordionContainer>
|
||||
{hasNpm && (
|
||||
<Accordion>
|
||||
// TODO: we can improve this logic, expanding only one accordion based on which package manager is enabled
|
||||
// feel free to contribute here
|
||||
return (
|
||||
<>
|
||||
<TextContent>{t('packageManagers.description')}</TextContent>
|
||||
<AccordionContainer>
|
||||
<Accordion disabled={!hasNpm}>
|
||||
<AccordionSummary
|
||||
aria-controls="panel1a-content"
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
@@ -112,9 +123,7 @@ const RegistryInfoContent: FC<Props> = ({ scope, registryUrl }) => {
|
||||
</CommandContainer>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
{hasYarn && (
|
||||
<Accordion>
|
||||
<Accordion disabled={!hasYarn}>
|
||||
<AccordionSummary
|
||||
aria-controls="panel2a-content"
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
@@ -123,14 +132,26 @@ const RegistryInfoContent: FC<Props> = ({ scope, registryUrl }) => {
|
||||
<Typography className={classes.heading}>{'yarn'}</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Description>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{t('packageManagers.yarnclassicDetails')}
|
||||
</ReactMarkdown>
|
||||
</Description>
|
||||
<CommandContainer data-testid={'tab-content'}>
|
||||
{renderYarnTab(scope, registryUrl)}
|
||||
</CommandContainer>
|
||||
<Description>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{t('packageManagers.yarnBerryDetails')}
|
||||
</ReactMarkdown>
|
||||
</Description>
|
||||
<CommandContainer data-testid={'tab-content'}>
|
||||
{renderYarnBerryTab(scope, registryUrl)}
|
||||
</CommandContainer>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
{hasPnpm && (
|
||||
<Accordion>
|
||||
|
||||
<Accordion disabled={!hasPnpm}>
|
||||
<AccordionSummary
|
||||
aria-controls="panel3a-content"
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
@@ -144,14 +165,15 @@ const RegistryInfoContent: FC<Props> = ({ scope, registryUrl }) => {
|
||||
</CommandContainer>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
<LinkContainer>
|
||||
<Link href="https://verdaccio.org/docs/en/cli-registry" target="_blank">
|
||||
<Typography>{t('header.registry-info-link')}</Typography>
|
||||
</Link>
|
||||
</LinkContainer>
|
||||
</AccordionContainer>
|
||||
) : null;
|
||||
|
||||
<LinkContainer>
|
||||
<Link href="https://verdaccio.org/docs/en/cli-registry" target="_blank">
|
||||
<Typography>{t('header.registry-info-link')}</Typography>
|
||||
</Link>
|
||||
</LinkContainer>
|
||||
</AccordionContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegistryInfoContent;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
|
||||
export const TextContent = styled('div')<{ theme?: Theme }>(({ theme }) => ({
|
||||
paddingBottom: '10px',
|
||||
backgroundColor: theme?.palette.background.default,
|
||||
}));
|
||||
|
||||
export const Description = styled('div')<{ theme?: Theme }>(() => ({
|
||||
fontSize: '1rem',
|
||||
fontStyle: 'italic',
|
||||
}));
|
||||
@@ -16,7 +16,7 @@ const RegistryInfoDialog: React.FC<Props> = ({ open = false, children, onClose }
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
>
|
||||
<Title disableTypography={true}>{t('dialog.registry-info.title')}</Title>
|
||||
<Title>{t('dialog.registry-info.title')}</Title>
|
||||
<Content>{children}</Content>
|
||||
<DialogActions>
|
||||
<Button color="inherit" id="registryInfo--dialog-close" onClick={onClose}>
|
||||
|
||||
@@ -13,3 +13,8 @@ export const Content = styled(DialogContent)<{ theme?: Theme }>(({ theme }) => (
|
||||
padding: '0 24px',
|
||||
backgroundColor: theme?.palette.background.default,
|
||||
}));
|
||||
|
||||
export const TextContent = styled('div')<{ theme?: Theme }>(({ theme }) => ({
|
||||
padding: '10px 24px',
|
||||
backgroundColor: theme?.palette.background.default,
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// eslint-disable
|
||||
import React from 'react';
|
||||
|
||||
// @ts-ignore
|
||||
export default function ({ children }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
// eslint-enable
|
||||
1
packages/plugins/ui-theme/src/__mocks__/remark-plugin.ts
Normal file
1
packages/plugins/ui-theme/src/__mocks__/remark-plugin.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default function () {}
|
||||
@@ -1,300 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
KeyboardEvent,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside';
|
||||
import IconButton from '../IconButton';
|
||||
import MenuItem from '../MenuItem';
|
||||
import Paper from '../Paper';
|
||||
import TextField from '../TextField';
|
||||
import { createFilterOptions } from './useAutoComplete';
|
||||
|
||||
const defaultFilterOptions = createFilterOptions();
|
||||
|
||||
type TextFieldProps = React.ComponentProps<typeof TextField>;
|
||||
|
||||
interface Props<Option extends {}> extends Pick<TextFieldProps, 'variant'> {
|
||||
options: Option[];
|
||||
getOptionLabel: (option: Option) => string;
|
||||
renderOption?: (option: Option) => React.ReactNode;
|
||||
placeholder?: string;
|
||||
label?: React.ReactNode;
|
||||
defaultValue?: string;
|
||||
inputStartAdornment?: React.ReactNode;
|
||||
hasClearIcon?: boolean;
|
||||
className?: string;
|
||||
onClick?: (option: any) => void;
|
||||
}
|
||||
|
||||
const AutoComplete = <Option extends {}>({
|
||||
placeholder,
|
||||
defaultValue = '',
|
||||
label,
|
||||
variant,
|
||||
inputStartAdornment,
|
||||
options: suggestions,
|
||||
getOptionLabel,
|
||||
renderOption: renderOptionProp,
|
||||
className,
|
||||
onClick,
|
||||
hasClearIcon,
|
||||
}: Props<Option>) => {
|
||||
const { t } = useTranslation();
|
||||
const [searchTerm, setSearchTerm] = useState(defaultValue);
|
||||
const [options, setOptions] = useState(suggestions);
|
||||
const [activeOption, setActiveOption] = useState(0);
|
||||
const [showOptions, setShowOptions] = useState(false);
|
||||
|
||||
// refs
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const clickOutside = useCallback(() => {
|
||||
setShowOptions(false);
|
||||
if (!searchTerm.trim()) {
|
||||
setSearchTerm(defaultValue);
|
||||
}
|
||||
}, [searchTerm, defaultValue]);
|
||||
|
||||
const filterOptions = useCallback(() => {
|
||||
const filteredOptions = defaultFilterOptions(suggestions, {
|
||||
inputValue: searchTerm,
|
||||
getOptionLabel,
|
||||
});
|
||||
setOptions(filteredOptions);
|
||||
}, [suggestions, searchTerm, getOptionLabel]);
|
||||
|
||||
const scrollIntoOption = useCallback(() => {
|
||||
const option = contentRef?.current?.children[activeOption];
|
||||
if (option) {
|
||||
option.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
});
|
||||
}
|
||||
}, [activeOption]);
|
||||
|
||||
useOnClickOutside(wrapperRef, useCallback(clickOutside, [wrapperRef, searchTerm]));
|
||||
|
||||
useEffect(filterOptions, [searchTerm]);
|
||||
|
||||
useEffect(scrollIntoOption, [activeOption]);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchTerm(defaultValue);
|
||||
}, [defaultValue]);
|
||||
|
||||
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchTerm(event.target.value);
|
||||
}, []);
|
||||
|
||||
const handleToggleShowOptions = useCallback(() => {
|
||||
setShowOptions(!showOptions);
|
||||
}, [showOptions]);
|
||||
|
||||
const handleClear = useCallback(() => {
|
||||
setSearchTerm('');
|
||||
setOptions([]);
|
||||
setShowOptions(true);
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClickOption = useCallback(
|
||||
(option: Option) => () => {
|
||||
if (onClick) {
|
||||
onClick(option);
|
||||
}
|
||||
setSearchTerm(getOptionLabel(option));
|
||||
setShowOptions(false);
|
||||
if (inputRef.current) {
|
||||
inputRef.current.blur();
|
||||
}
|
||||
},
|
||||
[getOptionLabel, onClick]
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
setShowOptions(true);
|
||||
}, []);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
// User pressed the enter key
|
||||
if (event.keyCode === 13) {
|
||||
setActiveOption(0);
|
||||
setShowOptions(false);
|
||||
handleClickOption(options[activeOption])();
|
||||
return;
|
||||
}
|
||||
|
||||
// User pressed the up arrow
|
||||
if (event.keyCode === 38) {
|
||||
if (activeOption === 0) {
|
||||
return;
|
||||
}
|
||||
setActiveOption(activeOption - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// User pressed the down arrow
|
||||
if (event.keyCode === 40) {
|
||||
if (activeOption + 1 >= options.length) {
|
||||
return;
|
||||
}
|
||||
setActiveOption(activeOption + 1);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[activeOption, handleClickOption, options]
|
||||
);
|
||||
|
||||
const renderOptions = useCallback(() => {
|
||||
if (renderOptionProp) {
|
||||
return options.map((option, index) => (
|
||||
<Option
|
||||
isSelected={index === activeOption}
|
||||
key={index}
|
||||
onClick={handleClickOption(option)}
|
||||
tabIndex={0}
|
||||
>
|
||||
{renderOptionProp(option)}
|
||||
</Option>
|
||||
));
|
||||
}
|
||||
|
||||
return options.map((option, index) => (
|
||||
<MenuItem
|
||||
component="div"
|
||||
key={index}
|
||||
onClick={handleClickOption(option)}
|
||||
selected={index === activeOption}
|
||||
tabIndex={0}
|
||||
>
|
||||
{getOptionLabel(option)}
|
||||
</MenuItem>
|
||||
));
|
||||
}, [options, activeOption, getOptionLabel, renderOptionProp, handleClickOption]);
|
||||
|
||||
return (
|
||||
<Wrapper className={className} ref={wrapperRef}>
|
||||
<StyledTextField
|
||||
autoComplete="off"
|
||||
InputProps={{
|
||||
startAdornment: inputStartAdornment,
|
||||
endAdornment: (
|
||||
<EndAdornment>
|
||||
{hasClearIcon && searchTerm.length > 0 && (
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={handleClear}
|
||||
size="small"
|
||||
title={t('autoComplete.clear')}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
<ExpandButton
|
||||
color="inherit"
|
||||
onClick={handleToggleShowOptions}
|
||||
showOptions={showOptions}
|
||||
size="small"
|
||||
title={showOptions ? t('autoComplete.collapse') : t('autoComplete.expand')}
|
||||
>
|
||||
<ExpandMoreIcon fontSize="small" />
|
||||
</ExpandButton>
|
||||
</EndAdornment>
|
||||
),
|
||||
}}
|
||||
inputRef={inputRef}
|
||||
label={label}
|
||||
onChange={handleChange}
|
||||
onFocus={handleFocus}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={placeholder}
|
||||
value={searchTerm}
|
||||
variant={variant}
|
||||
/>
|
||||
{showOptions && (
|
||||
<StyledPaper ref={contentRef} square={true}>
|
||||
{renderOptions()}
|
||||
</StyledPaper>
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const MemoizedAutoComplete = memo(AutoComplete) as typeof AutoComplete;
|
||||
|
||||
export { MemoizedAutoComplete as AutoComplete };
|
||||
|
||||
const Wrapper = styled('div')`
|
||||
position: relative;
|
||||
height: 40px;
|
||||
`;
|
||||
|
||||
const EndAdornment = styled('div')({
|
||||
display: 'flex',
|
||||
});
|
||||
|
||||
const StyledTextField = styled(TextField)<{ theme?: Theme }>(({ theme }) => ({
|
||||
height: 40,
|
||||
color: theme?.palette.white,
|
||||
['& .MuiInputBase-root']: {
|
||||
height: 40,
|
||||
color: theme?.palette.white,
|
||||
},
|
||||
['& .MuiInputBase-inputAdornedStart']: {
|
||||
paddingLeft: theme?.spacing(2),
|
||||
},
|
||||
['& .MuiInputBase-input']: {
|
||||
paddingTop: theme?.spacing(1),
|
||||
paddingBottom: theme?.spacing(1),
|
||||
},
|
||||
['& .MuiOutlinedInput-notchedOutline']: {
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
['& :hover .MuiOutlinedInput-notchedOutline']: {
|
||||
borderColor: theme?.palette.white,
|
||||
},
|
||||
['& :active .MuiOutlinedInput-notchedOutline']: {
|
||||
borderColor: theme?.palette.white,
|
||||
},
|
||||
}));
|
||||
|
||||
const ExpandButton = styled(IconButton, {
|
||||
shouldForwardProp: (prop) => prop !== 'showOptions',
|
||||
})<{ showOptions: boolean }>(({ showOptions }) => ({
|
||||
transform: showOptions ? 'rotate(180deg)' : 'none',
|
||||
}));
|
||||
|
||||
const Option = styled('div')<{ isSelected: boolean }>(({ isSelected }) => ({
|
||||
background: isSelected ? 'rgba(0, 0, 0, 0.08)' : 'none',
|
||||
}));
|
||||
|
||||
const StyledPaper = withStyles((theme: Theme) => ({
|
||||
root: {
|
||||
marginTop: theme?.spacing(0.5),
|
||||
position: 'absolute',
|
||||
borderRadius: 3,
|
||||
maxHeight: 165,
|
||||
overflowY: 'scroll',
|
||||
zIndex: 10000,
|
||||
overflowX: 'hidden',
|
||||
},
|
||||
}))(Paper);
|
||||
@@ -1,61 +0,0 @@
|
||||
// https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
|
||||
// Give up on IE 11 support for this feature
|
||||
function stripDiacritics(value: string) {
|
||||
return typeof value.normalize !== 'undefined'
|
||||
? value.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
||||
: value;
|
||||
}
|
||||
|
||||
// Based on https://github.com/netochaves/material-ui/tree/test/useAutocomplete/packages/material-ui-lab/src/useAutocomplete
|
||||
type CreateFilterOptionsConfig = {
|
||||
ignoreAccents?: boolean;
|
||||
ignoreCase?: boolean;
|
||||
limit?: number;
|
||||
matchFrom?: 'any' | 'start';
|
||||
trim?: boolean;
|
||||
};
|
||||
|
||||
export interface FilterOptionsState<Option> {
|
||||
inputValue: string;
|
||||
getOptionLabel: (option: Option) => string;
|
||||
}
|
||||
|
||||
function createFilterOptions(config?: CreateFilterOptionsConfig) {
|
||||
const {
|
||||
ignoreAccents = true,
|
||||
ignoreCase = true,
|
||||
trim = false,
|
||||
limit,
|
||||
matchFrom = 'any',
|
||||
} = config || {};
|
||||
|
||||
return <Option extends {}>(
|
||||
options: Option[],
|
||||
{ inputValue, getOptionLabel }: FilterOptionsState<Option>
|
||||
): Option[] => {
|
||||
let input = trim ? inputValue.trim() : inputValue;
|
||||
if (ignoreCase) {
|
||||
input = input.toLowerCase();
|
||||
}
|
||||
|
||||
if (ignoreAccents) {
|
||||
input = stripDiacritics(input);
|
||||
}
|
||||
|
||||
const filteredOptions = options.filter((option) => {
|
||||
let candidate = getOptionLabel(option);
|
||||
if (ignoreCase) {
|
||||
candidate = candidate.toLowerCase();
|
||||
}
|
||||
if (ignoreAccents) {
|
||||
candidate = stripDiacritics(candidate);
|
||||
}
|
||||
|
||||
return matchFrom === 'start' ? candidate.indexOf(input) === 0 : candidate.indexOf(input) > -1;
|
||||
});
|
||||
|
||||
return typeof limit === 'number' ? filteredOptions.slice(0, limit) : filteredOptions;
|
||||
};
|
||||
}
|
||||
|
||||
export { createFilterOptions };
|
||||
@@ -3,8 +3,8 @@ import React, { forwardRef } from 'react';
|
||||
|
||||
type CardContentRef = HTMLElementTagNameMap[keyof HTMLElementTagNameMap];
|
||||
|
||||
const CardContent = forwardRef<CardContentRef, CardContentProps>(function CardContent(props, ref) {
|
||||
return <MaterialUICardContent {...props} innerRef={ref} />;
|
||||
const CardContent = forwardRef<CardContentRef, CardContentProps>(function CardContent(props) {
|
||||
return <MaterialUICardContent {...props} />;
|
||||
});
|
||||
|
||||
export default CardContent;
|
||||
|
||||
@@ -3,8 +3,8 @@ import React, { forwardRef } from 'react';
|
||||
|
||||
type ChipRef = HTMLElementTagNameMap[keyof HTMLElementTagNameMap];
|
||||
|
||||
const Chip = forwardRef<ChipRef, ChipProps>(function Chip(props, ref) {
|
||||
return <MaterialUIChip {...props} innerRef={ref} />;
|
||||
const Chip = forwardRef<ChipRef, ChipProps>(function Chip(props) {
|
||||
return <MaterialUIChip {...props} />;
|
||||
});
|
||||
|
||||
export default Chip;
|
||||
|
||||
@@ -37,8 +37,8 @@ const Wrapper = styled('div')({
|
||||
const Content = styled('span')({
|
||||
display: 'inline-block',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
height: '21px',
|
||||
height: 'auto',
|
||||
whiteSpace: 'break-spaces',
|
||||
fontSize: '1rem',
|
||||
});
|
||||
|
||||
@@ -11,12 +11,12 @@ type Props = Omit<TooltipProps, 'title'> & {
|
||||
title: TFunctionResult;
|
||||
};
|
||||
|
||||
const Tooltip = forwardRef<TooltipRef, Props>(function ToolTip({ title, children, ...props }, ref) {
|
||||
const Tooltip = forwardRef<TooltipRef, Props>(function ToolTip({ title, children, ...props }) {
|
||||
if (!title) {
|
||||
return children;
|
||||
}
|
||||
return (
|
||||
<MaterialUITooltip {...props} title={title} innerRef={ref}>
|
||||
<MaterialUITooltip {...props} title={title}>
|
||||
{children}
|
||||
</MaterialUITooltip>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { adaptV4Theme, createTheme } from '@mui/material/styles';
|
||||
import { createTheme } from '@mui/material/styles';
|
||||
import { PRIMARY_COLOR } from 'verdaccio-ui/utils/colors';
|
||||
|
||||
const colors = {
|
||||
@@ -92,30 +92,28 @@ type CustomizedTheme = typeof customizedTheme;
|
||||
|
||||
export const getTheme = (themeMode: ThemeMode, primaryColor: string) => {
|
||||
const palette = applyPrimaryColor(themeMode, primaryColor);
|
||||
return createTheme(
|
||||
adaptV4Theme({
|
||||
typography: {
|
||||
fontFamily: [
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'sans-serif',
|
||||
].join(','),
|
||||
return createTheme({
|
||||
typography: {
|
||||
fontFamily: [
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'sans-serif',
|
||||
].join(','),
|
||||
},
|
||||
palette: {
|
||||
mode: themeMode,
|
||||
...palette,
|
||||
primary: { main: palette.primary },
|
||||
secondary: { main: palette.secondary },
|
||||
error: { main: palette.red },
|
||||
background: {
|
||||
default: palette.background,
|
||||
},
|
||||
palette: {
|
||||
mode: themeMode,
|
||||
...palette,
|
||||
primary: { main: palette.primary },
|
||||
secondary: { main: palette.secondary },
|
||||
error: { main: palette.red },
|
||||
background: {
|
||||
default: palette.background,
|
||||
},
|
||||
},
|
||||
...customizedTheme,
|
||||
})
|
||||
);
|
||||
},
|
||||
...customizedTheme,
|
||||
});
|
||||
};
|
||||
|
||||
export type Theme = ReturnType<typeof getTheme>;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"dialog": {
|
||||
"registry-info": {
|
||||
"title": "Registry Info"
|
||||
"title": "Configuration Details"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
@@ -166,5 +166,16 @@
|
||||
"china": "China",
|
||||
"germany": "Germany",
|
||||
"taiwan": "Taiwan"
|
||||
},
|
||||
"packageManagers": {
|
||||
"title": "Package Managers",
|
||||
"description": "This is the configuration details for the registry. Each package manager could have different configuration, expand each section for more details. If the section is disable review your configuration.",
|
||||
"yarnclassicDetails": "Yarn classic configuration differs from Yarn 2+ configuration. For more details, please visit [Yarn Classic](https://verdaccio.org/docs/cli-registry#yarn-1x).",
|
||||
"yarnBerryDetails": "Yarn Berry does not support the `--registry` flag, instead all configurarion should be defined on the `yarnrc.yalm` file in the root of your project. For more details, please visit [Yarn Berry](https://verdaccio.org/docs/cli-registry#yarn-berry-2x)."
|
||||
},
|
||||
"language": {
|
||||
"title": "Translations",
|
||||
"contribute": "If your language is not listed here or is not fully translated, please contribute to the translation. You can find the source code on [our main repository](https://github.com/verdaccio/verdaccio/blob/master/packages/plugins/ui-theme/src/i18n/ABOUT_TRANSLATIONS.md)",
|
||||
"description": "This is the configuration details for the language. Select a language to select a client side translation."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,9 +66,9 @@ exports[`<Help /> component should load the component in default state 1`] = `
|
||||
.emotion-9 {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
height: 21px;
|
||||
height: auto;
|
||||
white-space: break-spaces;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,15 +67,18 @@ export const manifest = createModel<RootModel>()({
|
||||
async getManifest({ packageName, packageVersion }, state) {
|
||||
const basePath = state.configuration.config.base;
|
||||
try {
|
||||
if (!isPackageVersionValid(packageName, packageVersion)) {
|
||||
throw new Error('not found');
|
||||
}
|
||||
|
||||
const manifest: Package = await API.request(
|
||||
`${basePath}-/verdaccio/data/sidebar/${packageName}${
|
||||
packageVersion ? `?v=${packageVersion}` : ''
|
||||
}`
|
||||
);
|
||||
|
||||
// FIXME: update types accordingly
|
||||
// @ts-ignore
|
||||
if (!isPackageVersionValid(manifest, packageVersion)) {
|
||||
throw new Error('not found');
|
||||
}
|
||||
|
||||
const readme: string = await API.request<string>(
|
||||
`${basePath}-/verdaccio/data/package/readme/${packageName}${
|
||||
packageVersion ? `?v=${packageVersion}` : ''
|
||||
|
||||
@@ -22,11 +22,11 @@ export const copyToClipBoardUtility =
|
||||
|
||||
export function getCLISetConfigRegistry(
|
||||
command: string,
|
||||
scope: string,
|
||||
scope: string | undefined,
|
||||
registryUrl: string
|
||||
): string {
|
||||
// if there is a scope defined there needs to be a ":" separator between the scope and the registry
|
||||
return `${command} ${scope}${scope !== '' ? ':' : ''}registry ${registryUrl}`;
|
||||
return `${command} ${scope ? `${scope}:` : ''}registry ${registryUrl}`;
|
||||
}
|
||||
|
||||
export function getCLISetRegistry(command: string, registryUrl: string): string {
|
||||
@@ -36,3 +36,18 @@ export function getCLISetRegistry(command: string, registryUrl: string): string
|
||||
export function getCLIChangePassword(command: string, registryUrl: string): string {
|
||||
return `${command} profile set password --registry ${registryUrl}`;
|
||||
}
|
||||
|
||||
export function getCLISBerryYamlRegistry(scope: string | undefined, registryUrl: string): string {
|
||||
return !scope
|
||||
? `// .yarnrc.yml
|
||||
npmRegistryServer: "${registryUrl}"
|
||||
unsafeHttpWhitelist:
|
||||
- ${new URL(registryUrl).host}`
|
||||
: `
|
||||
// .yarnrc.yml
|
||||
npmRegistryServer: "${registryUrl}"
|
||||
npmScopes:
|
||||
${scope.replace('@', '')}:
|
||||
npmRegistryServer: ${registryUrl}
|
||||
npmPublishRegistry: ${registryUrl}`;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ web:
|
||||
title: Verdaccio Local Dev
|
||||
sort_packages: asc
|
||||
primary_color: #CCC
|
||||
login: true
|
||||
login: false
|
||||
pkgManagers:
|
||||
- npm
|
||||
- yarn
|
||||
- pnpm
|
||||
|
||||
plugins: ../
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user