Compare commits

...

76 Commits

Author SHA1 Message Date
github-actions[bot]
4fc14eab02 chore: update versions (6-next) (#3053)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-03-12 22:48:42 +01:00
Juan Picado
a2b69a08e2 add banner support ukraine (#3060)
* add banner support ukraine

* fix test

* add blood link

* Update packages/plugins/ui-theme/src/App/Header/Support/Support.tsx

Co-authored-by: Daniel Ruf <827205+DanielRuf@users.noreply.github.com>

Co-authored-by: Daniel Ruf <827205+DanielRuf@users.noreply.github.com>
2022-03-12 22:42:05 +01:00
verdacciobot
6eea70a6dd chore: updated contributors list 2022-03-10 00:10:59 +00:00
Juan Picado
e533f1d500 chore: force banner keep open 2022-03-09 21:36:32 +01:00
Juan Picado
45cc559f47 chore: add donate button to redcrossredcrescent on home page 2022-03-09 21:16:03 +01:00
dependabot[bot]
ce81b3e96d chore(deps): bump actions/github-script from 3 to 6 (#2989)
Bumps [actions/github-script](https://github.com/actions/github-script) from 3 to 6.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v3...v6)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-09 21:09:11 +01:00
Xingwang Liao
31d661c7bd fix(loaders): always create plugin instance with new (#3052)
* class plugin transpile with babel contains a function '_classCallCheck',
* will throw an error when a class is invoked without 'new'
* see: https://babeljs.io/docs/en/babel-plugin-transform-classes#examples
2022-03-09 19:45:21 +01:00
Juan Picado
b69333778d chore: update redcross links to be more inclusive 2022-03-09 07:47:36 +01:00
github-actions[bot]
4dcc250fdb chore: update versions (6-next) (#3044)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-03-08 17:58:19 +01:00
verdacciobot
756a47aee0 chore: updated contributors list 2022-03-08 16:55:14 +00:00
Juan Picado
2c45bc6e82 chore: fix script 2022-03-08 17:51:34 +01:00
dependabot[bot]
31050ee369 chore(deps): bump actions/checkout from 2 to 3 (#3045)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-07 20:44:27 +01:00
Juan Picado
9652472780 Update README.md 2022-03-07 20:11:00 +01:00
Juan Picado
87f903d530 chore: update links 2022-03-07 19:54:58 +01:00
Juan Picado
8d625d25d2 add banner for Ukraine 2022-03-07 16:59:50 +01:00
Xingwang Liao
a179f1fd43 fix(ui-theme): show default logo in footer (#3031)
* fix(ui-theme): show default logo in footer

The link of the footer logo is verdaccio website, so the logo should be verdaccio's

* fixup! fix(ui-theme): show default logo in footer

* fixup! fixup! fix(ui-theme): show default logo in footer
2022-03-07 08:53:48 +01:00
Juan Picado
a049eaa38f chore: upgrade docusaurus 2.0.0-beta.17 (#3040) 2022-03-05 21:43:58 +01:00
Juan Picado
d2dd76260b chore: migrate to fully undici fetch (#3037)
* chore: migrate to fully undici fetch

* chore: update warnings

* remove debug

* restore dep

* chore:  update implementation

* fix lint

* update node

* update pnpm

* disable e2e cli

* disable cli
2022-03-05 18:44:10 +01:00
dependabot[bot]
f38c759cb2 chore(deps): bump pnpm/action-setup from 2.1.0 to 2.2.1 (#3022)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2.1.0 to 2.2.1.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v2.1.0...v2.2.1)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Juan Picado <juanpicado19@gmail.com>
2022-03-05 09:32:33 +01:00
Juan Picado
28c3aad443 chore: update e2e docs 2022-03-05 09:14:43 +01:00
Juan Picado
a020d4f580 chore: clean up warnings on ui tests (#3034)
* fix: remove duplicated call to fetch manifest

* fix: clean forward ref warnings

* clean button warnings

* clean box warnings

* SnackbarContent

* InputAdornment

* more migrations dialog, etc

* dialog cleanup

* clean up text component

* remove forward ref

* restore website components

* clean up

* IconButton

* format

* restore config
2022-03-05 00:12:37 +01:00
Juan Picado
160c25ddc5 Update benchmark.yml 2022-03-04 20:50:52 +01:00
Juan Picado
1debee3e9d chore: add new logger props 2022-03-04 19:52:10 +01:00
(H)eDoCode
76d78b0328 Node JS API Documentation : Working Implementation Code (#3032)
* Working node JS Code

Maybe add where do the `addrs` comes from ?
Added host and port display cause otherwise user doesn't know which default address and port are.

* Removing comments 

because I'm unsure about these / they are incorrect

Please complete it if you know how to make it work with a configFilePath as third parameter

* chore: fix website config

Co-authored-by: Juan Picado <juanpicado19@gmail.com>
2022-03-04 19:22:02 +01:00
github-actions[bot]
61bbede301 chore: update versions (6-next) (#3030)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-03-03 22:25:35 +01:00
Juan Picado
635ca3f924 chore: upload changeset 2022-03-03 22:07:26 +01:00
Ed Clement
aeff267d94 fix: refactor htpasswd plugin to use the bcryptjs 'compare' api call instead of 'compareSync' (#3025)
feat: add a new configuration value named 'slow_verify_ms' to the htpasswd plugin that when exceeded during password verification will log a warning message
chore: update README.md for htpasswd plugin to add additional information about the 'rounds' configuration value and also include the new 'slow_verify_ms' configuration value
2022-03-03 21:57:19 +01:00
Juan Picado
a0dca6e927 chore: fix lint 2022-03-03 19:30:19 +01:00
github-actions[bot]
71e5fb0221 chore: update versions (6-next) (#3027)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-03-03 08:30:03 +01:00
Juan Picado
aa0b2aa9df fix: replace ts icon by td and fix commonjs icon 2022-03-03 00:05:36 +01:00
dependabot[bot]
498afcd222 chore(deps): bump actions/setup-node from 2 to 3 (#3023)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-02 22:41:55 +01:00
github-actions[bot]
83561bc52d chore: update versions (6-next) (#3016)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-02-26 22:52:13 +01:00
dependabot[bot]
67b4681ce7 chore(deps): bump treosh/lighthouse-ci-action from 3 to 9.3.0 (#3008)
* chore(deps): bump treosh/lighthouse-ci-action from 3 to 9.3.0

Bumps [treosh/lighthouse-ci-action](https://github.com/treosh/lighthouse-ci-action) from 3 to 9.3.0.
- [Release notes](https://github.com/treosh/lighthouse-ci-action/releases)
- [Commits](https://github.com/treosh/lighthouse-ci-action/compare/v3...9.3.0)

---
updated-dependencies:
- dependency-name: treosh/lighthouse-ci-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* test

* test

* test

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Juan Picado <juanpicado19@gmail.com>
2022-02-26 22:51:51 +01:00
Juan Picado
df53f61c64 feat: add icons for module and typescript support (#3015)
* feat: add icons for module and typescript support

* feat: add types and package module icons on sidebar

* chore: add tests

* chore: restore test
2022-02-26 22:39:13 +01:00
github-actions[bot]
64c8221d35 chore: update versions (6-next) (#2968)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-02-26 17:43:06 +01:00
Juan Picado
c908963132 fix: specific version package detail page not showing (#3013) 2022-02-26 17:31:48 +01:00
Juan Picado
46db10fc4f chore: upgrade contributors script 2022-02-26 16:34:36 +01:00
verdacciobot
fadba40a9f chore: updated contributors list 2022-02-25 20:07:08 +00:00
Simon Knott
02023afd74 fix: typo in config template (#3012) 2022-02-25 14:33:40 +01:00
verdacciobot
f130817828 chore: updated contributors list 2022-02-24 00:09:49 +00:00
verdacciobot
d2f9013ece chore: updated contributors list 2022-02-21 00:10:42 +00:00
Juan Picado
b8554c8935 refactor: promisfy packages (#2767)
* refactor: better performance and structure for get package

refactor file-locking promise taste

refactor updatePackageNext method

update lock file

apply missing interfaces

add version method

fix lint

fix test

chore: remove promisify

refactor publish progress

* migrate methods utilities publish

* Update index.d.ts

* restore publish
2022-02-20 18:39:38 +01:00
Juan Picado
8ea24df16a chore: add article 2022-02-17 20:25:38 +01:00
verdacciobot
67bbd93379 chore: updated contributors list 2022-02-17 00:08:54 +00:00
verdacciobot
d09ea21bc9 chore: updated contributors list 2022-02-15 21:40:39 +00:00
Juan Picado
0b72c32400 chore: script to update contributors 2022-02-15 22:36:40 +01:00
Juan Picado
2d137274c4 chore: script to update contributors 2022-02-15 22:29:59 +01:00
Juan Picado
cc30a85d3d chore: script to update contributors (#3001)
* chore: script to update contributors

* chore: script to update contributors

* chore: script to update contributors

* chore: script to update contributors

* chore: script to update contributors

* chore: script to update contributors

* chore: script to update contributors

* chore: script to update contributors

* chore: script to update contributors

* chore: script to update contributors

* chore: script to update contributors

* chore: script to update contributors
2022-02-15 22:18:20 +01:00
Juan Picado
85a01746a5 add VERDACCIO_STORAGE_PATH documentation 2022-02-15 19:50:56 +01:00
dependabot[bot]
94f5e765a8 chore(deps): bump pnpm/action-setup from 2.0.1 to 2.1.0 (#2990)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2.0.1 to 2.1.0.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v2.0.1...v2.1.0)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-14 17:46:37 +01:00
Juan Picado
4cd71c6409 Merge branch 'master' of github.com:verdaccio/verdaccio 2022-02-06 20:57:17 +01:00
Juan Picado
be71d77511 chore: remove banner 2022-02-06 20:54:51 +01:00
Juan Picado
92a6f9a3fd Update benchmark.yml 2022-02-06 16:37:47 +01:00
Juan Picado
b9a947aa2f chore: add nodecongress banner
temporary, will be removed after conf :)
2022-02-05 20:05:55 +01:00
Juan Picado
110d90d888 update e2e website page 2022-02-05 19:39:12 +01:00
renovate[bot]
6bed458925 fix(deps): update all linting dependencies (#2971)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-02-04 08:55:22 +01:00
Juan Picado
73b3479a3a Update package.json 2022-01-30 23:03:39 +01:00
Juan Picado
459af1cd39 add heroku docs 2022-01-30 21:36:51 +01:00
Juan Picado
e0d639a1a6 update readme 2022-01-28 09:06:00 +01:00
Martin Sander
b78f35257e Fix re-opening log files using SIGUSR2 (#2967) 2022-01-27 16:40:51 +01:00
github-actions[bot]
06e400cb34 chore: update versions (6-next) (#2964)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-01-24 21:11:26 +01:00
Varun Gandhi
dd380c5a5e fix: links to configuration in source tree. (#2962)
These were broken by a70454c7b2.
2022-01-24 21:07:09 +01:00
Juan Picado
ad3151c3f3 fix: remove engines from ui-theme 2022-01-24 21:04:45 +01:00
Juan Picado
cca1f7f32a chore: update renovate schedule 2022-01-23 17:10:10 +01:00
renovate[bot]
3d86b836c3 fix(deps): update all linting dependencies (#2959) 2022-01-23 15:58:36 +01:00
Juan Picado
7f48edc254 chore: update renovate 2022-01-23 13:06:47 +01:00
Juan Picado
9245b4d39b chore: update renovate 2022-01-23 13:05:04 +01:00
renovate[bot]
3b363fb531 fix(deps): update all non-major linting dependencies (#2953)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-01-23 12:39:39 +01:00
Juan Picado
5b39aeb623 chore: update renovate 2022-01-23 12:38:23 +01:00
Juan Picado
edc7a538f3 chore: fix some major dependencies renovate 2022-01-23 11:59:14 +01:00
Juan Picado
d585216153 chore: enable major renovate 2022-01-23 11:58:24 +01:00
Juan Picado
1367beae51 chore: update settings 2022-01-22 21:28:31 +01:00
renovate[bot]
944ecf6874 fix(deps): update all non-major core dependencies (#2942)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-01-22 19:12:49 +01:00
github-actions[bot]
2bd10d6010 chore: update versions (6-next) (#2948) 2022-01-22 14:01:47 +01:00
Juan Picado
7ff4808be6 feat: improve language switch ui and package manager info (#2936)
* feat: improve language switch ui and package manager info

* feat: improve registry info dialog and language switch

* add description

* update text

* update npmignore

* chore: update test expect
2022-01-22 13:54:00 +01:00
renovate[bot]
7bb3c2bf0e fix(deps): update all non-major core dependencies (#2910)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-01-18 08:35:07 +01:00
277 changed files with 11309 additions and 7216 deletions

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/ui-theme': patch
---
add banner support ukraine

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/ui-theme': patch
---
show verdaccio logo in the footer even when custom brand is set

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/ui-theme': minor
---
fix: remove engines from ui-theme

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/ui-theme': patch
---
feat: add types and package module icons on sidebar

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/ui-theme': patch
---
chore: force publish

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/loaders': patch
---
always create plugin instance with new

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/ui-theme': patch
---
fix: replace ts icon by td and fix commonjs icon

View File

@@ -50,14 +50,17 @@
"afraid-mice-obey",
"big-lobsters-sin",
"bright-poems-obey",
"brown-pandas-wink",
"calm-pants-impress",
"dry-planes-tap",
"dull-monkeys-search",
"eleven-brooms-hunt",
"eleven-spoons-matter",
"fair-lemons-beam",
"few-cooks-destroy",
"few-mangos-grow",
"fifty-jars-rest",
"fuzzy-drinks-taste",
"fuzzy-onions-draw",
"gentle-parrots-lay",
"gentle-trains-switch",
@@ -66,13 +69,17 @@
"healthy-poets-compare",
"heavy-ravens-lay",
"hip-hounds-destroy",
"kind-bears-nail",
"late-adults-love",
"late-parents-act",
"light-walls-begin",
"little-stingrays-rule",
"loud-shoes-jog",
"lovely-drinks-argue",
"many-vans-care",
"modern-spies-tell",
"neat-toes-report",
"neat-toys-float",
"olive-candles-speak",
"perfect-candles-clap",
"perfect-emus-clean",
@@ -83,6 +90,7 @@
"pretty-hounds-tap",
"proud-jeans-walk",
"red-chefs-float",
"red-yaks-sell",
"rich-ghosts-rule",
"shaggy-carrots-unite",
"shaggy-parrots-smash",
@@ -90,11 +98,14 @@
"smart-apricots-kneel",
"sour-buses-shout",
"spicy-frogs-press",
"spicy-snakes-sip",
"swift-pumpkins-knock",
"ten-parents-breathe",
"tender-bags-call",
"thick-countries-move",
"three-moles-drop",
"three-pots-sit",
"tiny-seals-join",
"two-dolls-check",
"wild-jokes-beam"
]

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/logger': patch
---
Fix re-opening log files using SIGUSR2

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/ui-theme': patch
---
fix: specific version package detail page not showing

View File

@@ -0,0 +1,6 @@
---
'@verdaccio/auth': patch
'verdaccio-htpasswd': patch
---
Refactor htpasswd plugin to use the bcryptjs 'compare' api call instead of 'comparSync'. Add a new configuration value named 'slow_verify_ms' to the htpasswd plugin that when exceeded during password verification will log a warning message.

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/ui-theme': minor
---
feat: improve registry info dialog and language switch

View File

@@ -15,8 +15,8 @@ jobs:
name: Prepare build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14.x
- name: install pnpm
@@ -57,13 +57,13 @@ jobs:
# - local
- 3.13.1
- 4.12.2
- 5.3.0
- 6.0.0-6-next.27
- 5.7.0
- 6.0.0-6-next.35
name: Benchmark autocannon
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14.x
- uses: actions/download-artifact@v2
@@ -118,13 +118,13 @@ 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.7.0
- 6.0.0-6-next.35
name: Benchmark hyperfine
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14.x
- uses: actions/download-artifact@v2

View File

@@ -20,12 +20,12 @@ jobs:
if: github.ref == 'refs/heads/master' && github.repository == 'verdaccio/verdaccio'
steps:
- name: checkout code repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: setup node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 14
registry-url: 'https://registry.npmjs.org'

View File

@@ -24,9 +24,9 @@ jobs:
ports:
- 4873:4873
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- name: Use Node 16
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install pnpm
@@ -49,9 +49,9 @@ jobs:
name: Lint
needs: prepare
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- name: Use Node 16
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install pnpm
@@ -69,9 +69,9 @@ jobs:
name: Format
needs: prepare
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- name: Use Node 16
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install pnpm
@@ -95,9 +95,9 @@ jobs:
name: ${{ matrix.os }} / Node ${{ matrix.node_version }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- name: Use Node ${{ matrix.node_version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node_version }}
- name: Install pnpm
@@ -117,12 +117,12 @@ jobs:
runs-on: ubuntu-latest
name: UI Test E2E Node 16
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/setup-node@v1
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install pnpm
run: npm i pnpm@6.24.1 -g
run: npm i pnpm@latest -g
- uses: actions/cache@v2
with:
path: ~/.pnpm-store
@@ -136,39 +136,41 @@ jobs:
run: pnpm test:e2e:ui
# env:
# DEBUG: verdaccio:e2e*
ci-e2e-cli:
needs: [format, lint]
runs-on: ubuntu-latest
# TODO: fails on migrate to node 16, we need to check why
name: CLI Test E2E Node 14
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/setup-node@v1
with:
node-version: 14
- name: Install pnpm
run: npm i pnpm@6.24.1 -g
- uses: actions/cache@v2
with:
path: ~/.pnpm-store
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install
## we need scripts, pupetter downloads aditional content
run: pnpm recursive install --frozen-lockfile
- name: build
run: pnpm build
- name: Test CLI
run: pnpm test:e2e:cli
env:
DEBUG: verdaccio*
# FIXME verify why fails on Node 16 (locally works fine)
# ci-e2e-cli:
# needs: [format, lint]
# runs-on: ubuntu-latest
# # TODO: fails on migrate to node 16, we need to check why
# name: CLI Test E2E Node 16
# steps:
# - uses: actions/checkout@v3
# - uses: actions/setup-node@v3
# with:
# node-version: 16
# - name: Install pnpm
# run: npm i pnpm@latest -g
# - uses: actions/cache@v2
# with:
# path: ~/.pnpm-store
# key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
# - name: Install
# ## we need scripts, pupetter downloads aditional content
# run: pnpm recursive install --frozen-lockfile
# - name: build
# run: pnpm build
# - name: Test CLI
# run: pnpm test:e2e:cli
# env:
# DEBUG: verdaccio*
sync-translations:
needs: [ci-e2e-cli, ci-e2e-ui]
# needs: [ci-e2e-cli, ci-e2e-ui]
needs: [ci-e2e-ui]
runs-on: ubuntu-latest
name: synchronize translations
if: (github.event_name == 'push' && github.ref == 'refs/heads/master') || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/setup-node@v1
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install pnpm

View File

@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.

48
.github/workflows/contributors.yml vendored Normal file
View 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@v3
with:
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@v3
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

View File

@@ -19,7 +19,7 @@ jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
with:

View File

@@ -24,10 +24,10 @@ jobs:
env:
NODE_OPTIONS: --max_old_space_size=4096
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/checkout@v3
- name: Use Node 14
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 14
@@ -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.2.1
with:
version: 6.10.2
run_install: |
@@ -97,7 +97,7 @@ jobs:
- name: Audit preview URL with Lighthouse
id: lighthouse_audit
uses: treosh/lighthouse-ci-action@v3
uses: treosh/lighthouse-ci-action@9.3.0
with:
urls: |
${{ steps.netlify_preview.outputs.preview-url }}
@@ -106,7 +106,7 @@ jobs:
- name: Format lighthouse score
id: format_lighthouse_score
uses: actions/github-script@v3
uses: actions/github-script@v6
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |

View File

@@ -1,3 +1,7 @@
[![BannerUK](https://cdn.verdaccio.dev/readme/banner-uk.svg)](https://donate.redcrossredcrescent.org/ua/donate/~my-donation?_cv=1)
> Verdaccio stands for **peace**, stop the war, we will be yellow / blue 🇺🇦 until that happens.
![verdaccio logo](https://cdn.verdaccio.dev/readme/verdaccio@2x.png)
![verdaccio gif](https://cdn.verdaccio.dev/readme/readme-website.png)
@@ -26,6 +30,7 @@ Google Cloud Storage** or create your own plugin.
[![Twitter followers](https://img.shields.io/twitter/follow/verdaccio_npm.svg?style=social&label=Follow)](https://twitter.com/verdaccio_npm)
[![Github](https://img.shields.io/github/stars/verdaccio/verdaccio.svg?style=social&label=Stars)](https://github.com/verdaccio/verdaccio/stargazers)
[![StandWithUkraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
## Install
@@ -45,7 +50,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 +78,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 +114,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 +138,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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 42 \"**/*.{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",

View File

@@ -1,5 +1,34 @@
# @verdaccio/api
## 6.0.0-6-next.23
### Patch Changes
- @verdaccio/auth@6.0.0-6-next.20
- @verdaccio/store@6.0.0-6-next.20
- @verdaccio/hooks@6.0.0-6-next.12
- @verdaccio/middleware@6.0.0-6-next.20
## 6.0.0-6-next.22
### Patch Changes
- Updated dependencies [aeff267d]
- @verdaccio/auth@6.0.0-6-next.19
- @verdaccio/hooks@6.0.0-6-next.12
- @verdaccio/middleware@6.0.0-6-next.19
## 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

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/api",
"version": "6.0.0-6-next.20",
"version": "6.0.0-6-next.23",
"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.20",
"@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.20",
"@verdaccio/store": "workspace:6.0.0-6-next.20",
"@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.28",
"@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",

View File

@@ -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,

View File

@@ -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');

View File

@@ -1,5 +1,28 @@
# @verdaccio/auth
## 6.0.0-6-next.20
### Patch Changes
- Updated dependencies [31d661c7]
- @verdaccio/loaders@6.0.0-6-next.11
## 6.0.0-6-next.19
### Patch Changes
- aeff267d: Refactor htpasswd plugin to use the bcryptjs 'compare' api call instead of 'comparSync'. Add a new configuration value named 'slow_verify_ms' to the htpasswd plugin that when exceeded during password verification will log a warning message.
- Updated dependencies [aeff267d]
- verdaccio-htpasswd@11.0.0-6-next.12
## 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

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/auth",
"version": "6.0.0-6-next.17",
"version": "6.0.0-6-next.20",
"description": "logger",
"main": "./build/index.js",
"types": "build/index.d.ts",
@@ -41,14 +41,14 @@
"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.11",
"@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",
"jsonwebtoken": "8.5.1",
"lodash": "4.17.21",
"verdaccio-htpasswd": "workspace:11.0.0-6-next.11"
"verdaccio-htpasswd": "workspace:11.0.0-6-next.12"
},
"devDependencies": {
"@verdaccio/mock": "workspace:6.0.0-6-next.13",

View File

@@ -111,7 +111,7 @@ class Auth implements IAuth {
};
let authPlugin;
try {
authPlugin = new HTPasswd(plugingConf, pluginOptions);
authPlugin = new HTPasswd(plugingConf, pluginOptions as any as PluginOptions<HTPasswdConfig>);
} catch (error: any) {
debug('error on loading auth htpasswd plugin stack: %o', error);
return [];

View File

@@ -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;
}

View File

@@ -1,5 +1,28 @@
# @verdaccio/cli
## 6.0.0-6-next.30
### Patch Changes
- @verdaccio/fastify-migration@6.0.0-6-next.21
- @verdaccio/node-api@6.0.0-6-next.29
## 6.0.0-6-next.29
### Patch Changes
- @verdaccio/fastify-migration@6.0.0-6-next.20
- @verdaccio/node-api@6.0.0-6-next.28
## 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

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/cli",
"version": "6.0.0-6-next.27",
"version": "6.0.0-6-next.30",
"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.29",
"@verdaccio/fastify-migration": "workspace:6.0.0-6-next.21",
"clipanion": "3.1.0",
"envinfo": "7.8.1",
"kleur": "3.0.3",

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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';

View 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 };

View 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 };

View 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();
});
});
};

View File

@@ -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);
}

View File

@@ -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 };

View File

@@ -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);
}

View File

@@ -0,0 +1,4 @@
{
"name": "assets",
"version": "0.0.1"
}

View File

@@ -0,0 +1,4 @@
{
"name": "assets",
"version": "0.0.1"
}

View File

@@ -0,0 +1,4 @@
{
"name": "assets",
"version": "0.0.1",
}

View 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();
});
}
);
});
});

View File

@@ -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');
}
);
});

View File

@@ -1,5 +1,28 @@
# @verdaccio/fastify-migration
## 6.0.0-6-next.21
### Patch Changes
- @verdaccio/auth@6.0.0-6-next.20
- @verdaccio/store@6.0.0-6-next.20
## 6.0.0-6-next.20
### Patch Changes
- Updated dependencies [aeff267d]
- @verdaccio/auth@6.0.0-6-next.19
## 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

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/fastify-migration",
"version": "6.0.0-6-next.18",
"version": "6.0.0-6-next.21",
"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.20",
"@verdaccio/logger": "workspace:6.0.0-6-next.10",
"@verdaccio/store": "workspace:6.0.0-6-next.20",
"@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"
},

View File

@@ -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,

View 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;

View File

@@ -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;
}

View File

@@ -1,6 +1,9 @@
import { RequestOptions } from '@verdaccio/url';
export { convertDistRemoteToLocalTarballUrls } from './convertDistRemoteToLocalTarballUrls';
export {
convertDistRemoteToLocalTarballUrls,
convertDistVersionToLocalTarballsUrl,
} from './convertDistRemoteToLocalTarballUrls';
export { getLocalRegistryTarballUri } from './getLocalRegistryTarballUri';
export { RequestOptions };

View File

@@ -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 {

View File

@@ -38,7 +38,7 @@
"build": "exit 0"
},
"devDependencies": {
"@types/node": "16.11.19",
"@types/node": "16.11.21",
"tsd": "0.19.1"
},
"funding": {

View File

@@ -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

View File

@@ -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,15 @@
},
"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"
"undici": "4.15.0"
},
"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.20",
"@verdaccio/config": "workspace:6.0.0-6-next.12",
"@verdaccio/types": "workspace:11.0.0-6-next.10"
},

View File

@@ -1,10 +1,10 @@
import buildDebug from 'debug';
import { fetch } from 'undici';
import { HTTP_STATUS } from '@verdaccio/core';
import { logger } from '@verdaccio/logger';
const debug = buildDebug('verdaccio:hooks:request');
const fetch = require('undici-fetch');
export type FetchOptions = {
body: string;

View File

@@ -1,3 +1,5 @@
import { MockAgent, setGlobalDispatcher } from 'undici';
import { createRemoteUser, parseConfigFile } from '@verdaccio/config';
import { setup } from '@verdaccio/logger';
import { Config } from '@verdaccio/types';
@@ -16,8 +18,6 @@ const multiNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('mu
setup([]);
const domain = 'http://slack-service';
const { MockAgent } = require('undici');
const { setGlobalDispatcher } = require('undici-fetch');
const options = {
path: '/foo?auth_token=mySecretToken',

View File

@@ -1,5 +1,18 @@
# @verdaccio/loaders
## 6.0.0-6-next.11
### Patch Changes
- 31d661c7: always create plugin instance with new
## 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

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/loaders",
"version": "6.0.0-6-next.9",
"version": "6.0.0-6-next.11",
"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"
},

View File

@@ -121,7 +121,7 @@ export function loadPlugin<T extends IPlugin<T>>(
try {
plugin = isES6(plugin)
? new plugin.default(mergeConfig(config, pluginConfigs[pluginId]), params)
: plugin(pluginConfigs[pluginId], params);
: new plugin(pluginConfigs[pluginId], params);
} catch (error: any) {
plugin = null;
logger.error({ error, pluginId }, 'error loading a plugin @{pluginId}: @{error}');

View File

@@ -46,7 +46,7 @@
"prettier-bytes": "1.0.4"
},
"devDependencies": {
"pino": "7.6.3"
"pino": "7.6.4"
},
"funding": {
"type": "opencollective",

View File

@@ -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

View File

@@ -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"

View File

@@ -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');

View File

@@ -1,5 +1,26 @@
# @verdaccio/middleware
## 6.0.0-6-next.20
### Patch Changes
- @verdaccio/auth@6.0.0-6-next.20
## 6.0.0-6-next.19
### Patch Changes
- Updated dependencies [aeff267d]
- @verdaccio/auth@6.0.0-6-next.19
## 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

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/middleware",
"version": "6.0.0-6-next.17",
"version": "6.0.0-6-next.20",
"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.20",
"@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"
},

View File

@@ -1,5 +1,25 @@
# @verdaccio/node-api
## 6.0.0-6-next.29
### Patch Changes
- @verdaccio/server@6.0.0-6-next.28
## 6.0.0-6-next.28
### Patch Changes
- @verdaccio/server@6.0.0-6-next.27
## 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

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/node-api",
"version": "6.0.0-6-next.26",
"version": "6.0.0-6-next.29",
"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.28",
"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"

View File

@@ -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",

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -1,5 +1,11 @@
# Change Log
## 11.0.0-6-next.12
### Patch Changes
- aeff267d: Refactor htpasswd plugin to use the bcryptjs 'compare' api call instead of 'comparSync'. Add a new configuration value named 'slow_verify_ms' to the htpasswd plugin that when exceeded during password verification will log a warning message.
## 11.0.0-6-next.11
### Patch Changes

View File

@@ -30,7 +30,26 @@ As simple as running:
# Hash algorithm, possible options are: "bcrypt", "md5", "sha1", "crypt".
#algorithm: bcrypt
# Rounds number for "bcrypt", will be ignored for other algorithms.
# Setting this value higher will result in password verification taking longer.
#rounds: 10
# Log a warning if the password takes more then this duration in milliseconds to verify.
#slow_verify_ms: 200
### Bcrypt rounds
It is important to note that when using the default `bcrypt` algorithm and setting
the `rounds` configuration value to a higher number then the default of `10`, that
verification of a user password can cause significantly increased CPU usage and
additional latency in processing requests.
If your Verdaccio instance handles a large number of authenticated requests using
username and password for authentication, the `rounds` configuration value may need
to be decreased to prevent excessive CPU usage and request latency.
Also note that setting the `rounds` configuration value to a value that is too small
increases the risk of successful brute force attack. Auth0 has a
[blog article](https://auth0.com/blog/hashing-in-action-understanding-bcrypt)
that provides an overview of how `bcrypt` hashing works and some best practices.
## Logging In

View File

@@ -1,6 +1,6 @@
{
"name": "verdaccio-htpasswd",
"version": "11.0.0-6-next.11",
"version": "11.0.0-6-next.12",
"description": "htpasswd auth plugin for Verdaccio",
"keywords": [
"private",
@@ -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"
},

View File

@@ -19,9 +19,12 @@ export type HTPasswdConfig = {
file: string;
algorithm?: HtpasswdHashAlgorithm;
rounds?: number;
max_users?: number;
slow_verify_ms?: number;
} & Config;
export const DEFAULT_BCRYPT_ROUNDS = 10;
export const DEFAULT_SLOW_VERIFY_MS = 200;
/**
* HTPasswd - Verdaccio auth class
@@ -30,30 +33,21 @@ export default class HTPasswd implements IPluginAuth<HTPasswdConfig> {
/**
*
* @param {*} config htpasswd file
* @param {object} stuff config.yaml in object from
* @param {object} options config.yaml in object from
*/
private users: {};
private stuff: {};
private config: {};
private verdaccioConfig: Config;
private maxUsers: number;
private hashConfig: HtpasswdHashConfig;
private path: string;
private slowVerifyMs: number;
private logger: Logger;
private lastTime: any;
// constructor
public constructor(config: HTPasswdConfig, stuff: PluginOptions<{}>) {
public constructor(config: HTPasswdConfig, options: PluginOptions<HTPasswdConfig>) {
this.users = {};
// config for this module
this.config = config;
this.stuff = stuff;
// verdaccio logger
this.logger = stuff.logger;
// verdaccio main config object
this.verdaccioConfig = stuff.config;
this.logger = options.logger;
// all this "verdaccio_config" stuff is for b/w compatibility only
this.maxUsers = config.max_users ? config.max_users : Infinity;
@@ -88,25 +82,41 @@ export default class HTPasswd implements IPluginAuth<HTPasswdConfig> {
throw new Error('should specify "file" in config');
}
this.path = Path.resolve(Path.dirname(this.verdaccioConfig.config_path), file);
this.path = Path.resolve(Path.dirname(options.config.config_path), file);
this.slowVerifyMs = config.slow_verify_ms || DEFAULT_SLOW_VERIFY_MS;
}
/**
* authenticate - Authenticate user.
* @param {string} user
* @param {string} password
* @param {function} cd
* @returns {function}
* @param {function} cb
* @returns {void}
*/
public authenticate(user: string, password: string, cb: Callback): void {
this.reload((err) => {
this.reload(async (err) => {
if (err) {
return cb(err.code === 'ENOENT' ? null : err);
}
if (!this.users[user]) {
return cb(null, false);
}
if (!verifyPassword(password, this.users[user])) {
let passwordValid = false;
try {
const start = new Date();
passwordValid = await verifyPassword(password, this.users[user]);
const durationMs = new Date().getTime() - start.getTime();
if (durationMs > this.slowVerifyMs) {
this.logger.warn(
{ user, durationMs },
'Password for user "@{user}" took @{durationMs}ms to verify'
);
}
} catch ({ message }) {
this.logger.error({ message }, 'Unable to verify user password: @{message}');
}
if (!passwordValid) {
return cb(null, false);
}
@@ -130,11 +140,11 @@ export default class HTPasswd implements IPluginAuth<HTPasswdConfig> {
* @param {string} user
* @param {string} password
* @param {function} realCb
* @returns {function}
* @returns {Promise<any>}
*/
public adduser(user: string, password: string, realCb: Callback): any {
public async adduser(user: string, password: string, realCb: Callback): Promise<any> {
const pathPass = this.path;
let sanity = sanityCheck(user, password, verifyPassword, this.users, this.maxUsers);
let sanity = await sanityCheck(user, password, verifyPassword, this.users, this.maxUsers);
// preliminary checks, just to ensure that file won't be reloaded if it's
// not needed
@@ -142,7 +152,7 @@ export default class HTPasswd implements IPluginAuth<HTPasswdConfig> {
return realCb(sanity, false);
}
lockAndRead(pathPass, (err, res): void => {
lockAndRead(pathPass, async (err, res): Promise<void> => {
let locked = false;
// callback that cleans up lock first
@@ -170,7 +180,7 @@ export default class HTPasswd implements IPluginAuth<HTPasswdConfig> {
// real checks, to prevent race conditions
// parsing users after reading file.
sanity = sanityCheck(user, password, verifyPassword, this.users, this.maxUsers);
sanity = await sanityCheck(user, password, verifyPassword, this.users, this.maxUsers);
if (sanity) {
return cb(sanity);
@@ -230,7 +240,8 @@ export default class HTPasswd implements IPluginAuth<HTPasswdConfig> {
* changePassword - change password for existing user.
* @param {string} user
* @param {string} password
* @param {function} cd
* @param {string} newPassword
* @param {function} realCb
* @returns {function}
*/
public changePassword(
@@ -239,7 +250,7 @@ export default class HTPasswd implements IPluginAuth<HTPasswdConfig> {
newPassword: string,
realCb: Callback
): void {
lockAndRead(this.path, (err, res) => {
lockAndRead(this.path, async (err, res) => {
let locked = false;
const pathPassFile = this.path;
@@ -266,13 +277,9 @@ export default class HTPasswd implements IPluginAuth<HTPasswdConfig> {
const body = this._stringToUt8(res);
this.users = parseHTPasswd(body);
if (!this.users[user]) {
return cb(new Error('User not found'));
}
try {
this._writeFile(
changePasswordToHTPasswd(body, user, password, newPassword, this.hashConfig),
await changePasswordToHTPasswd(body, user, password, newPassword, this.hashConfig),
cb
);
} catch (err: any) {

View File

@@ -39,8 +39,9 @@ export function lockAndRead(name: string, cb: Callback): void {
* @returns {object}
*/
export function parseHTPasswd(input: string): Record<string, any> {
return input.split('\n').reduce((result, line) => {
const args = line.split(':', 3);
// The input is split on line ending styles that are both windows and unix compatible
return input.split(/[\r]?[\n]/).reduce((result, line) => {
const args = line.split(':', 3).map((str) => str.trim());
if (args.length > 1) {
result[args[0]] = args[1];
}
@@ -52,11 +53,13 @@ export function parseHTPasswd(input: string): Record<string, any> {
* verifyPassword - matches password and it's hash.
* @param {string} passwd
* @param {string} hash
* @returns {boolean}
* @returns {Promise<boolean>}
*/
export function verifyPassword(passwd: string, hash: string): boolean {
if (hash.match(/^\$2(a|b|y)\$/)) {
return bcrypt.compareSync(passwd, hash);
export async function verifyPassword(passwd: string, hash: string): Promise<boolean> {
if (hash.match(/^\$2([aby])\$/)) {
return new Promise((resolve, reject) =>
bcrypt.compare(passwd, hash, (error, result) => (error ? reject(error) : resolve(result)))
);
} else if (hash.indexOf('{PLAIN}') === 0) {
return passwd === hash.substr(7);
} else if (hash.indexOf('{SHA}') === 0) {
@@ -112,6 +115,7 @@ export function generateHtpasswdLine(
* @param {string} body
* @param {string} user
* @param {string} passwd
* @param {HtpasswdHashConfig} hashConfig
* @returns {string}
*/
export function addUserToHTPasswd(
@@ -139,16 +143,18 @@ export function addUserToHTPasswd(
* Sanity check for a user
* @param {string} user
* @param {object} users
* @param {string} password
* @param {Callback} verifyFn
* @param {number} maxUsers
* @returns {object}
*/
export function sanityCheck(
export async function sanityCheck(
user: string,
password: string,
verifyFn: Callback,
users: {},
maxUsers: number
): HttpError | null {
): Promise<HttpError | null> {
let err;
// check for user or password
@@ -167,7 +173,7 @@ export function sanityCheck(
}
if (hash) {
const auth = verifyFn(password, users[user]);
const auth = await verifyFn(password, users[user]);
if (auth) {
err = Error(API_ERROR.USERNAME_ALREADY_REGISTERED);
err.status = HTTP_STATUS.CONFLICT;
@@ -191,28 +197,27 @@ export function sanityCheck(
* @param {string} user
* @param {string} passwd
* @param {string} newPasswd
* @param {HtpasswdHashConfig} hashConfig
* @returns {string}
*/
export function changePasswordToHTPasswd(
export async function changePasswordToHTPasswd(
body: string,
user: string,
passwd: string,
newPasswd: string,
hashConfig: HtpasswdHashConfig
): string {
): Promise<string> {
let lines = body.split('\n');
lines = lines.map((line) => {
const [username, hash] = line.split(':', 3);
if (username === user) {
if (verifyPassword(passwd, hash)) {
line = generateHtpasswdLine(user, newPasswd, hashConfig);
} else {
throw new Error('Invalid old Password');
}
}
return line;
});
const userLineIndex = lines.findIndex((line) => line.split(':', 1).shift() === user);
if (userLineIndex === -1) {
throw new Error(`Unable to change password for user '${user}': user does not currently exist`);
}
const [username, hash] = lines[userLineIndex].split(':', 2);
const passwordValid = await verifyPassword(passwd, hash);
if (!passwordValid) {
throw new Error(`Unable to change password for user '${user}': invalid old password`);
}
const updatedUserLine = generateHtpasswdLine(username, newPasswd, hashConfig);
lines.splice(userLineIndex, 1, updatedUserLine);
return lines.join('\n');
}

View File

@@ -1,2 +1,3 @@
test:$6FrCaT/v0dwE:autocreated 2018-01-17T03:40:22.958Z
username:$66to3JK5RgZM:autocreated 2018-01-17T03:40:46.315Z
bcrypt:$2y$04$K2Cn3StiXx4CnLmcTW/ymekOrj7WlycZZF9xgmoJ/U0zGPqSLPVBe

View File

@@ -1 +0,0 @@
export default class Logger {}

View File

@@ -1,36 +1,35 @@
/* eslint-disable jest/no-mocks-import */
// @ts-ignore: Module has no default export
import bcrypt from 'bcryptjs';
// @ts-ignore: Module has no default export
import crypto from 'crypto';
// @ts-ignore
// @ts-ignore: Module has no default export
import fs from 'fs';
import MockDate from 'mockdate';
import HTPasswd, { VerdaccioConfigApp } from '../src/htpasswd';
import { PluginOptions } from '@verdaccio/types';
import HTPasswd, { DEFAULT_SLOW_VERIFY_MS, HTPasswdConfig } from '../src/htpasswd';
import { HtpasswdHashAlgorithm } from '../src/utils';
import Config from './__mocks__/Config';
// FIXME: remove this mocks imports
import Logger from './__mocks__/Logger';
const stuff = {
logger: new Logger(),
const options = {
logger: { warn: jest.fn() },
config: new Config(),
};
} as any as PluginOptions<HTPasswdConfig>;
const config = {
file: './htpasswd',
max_users: 1000,
};
const getDefaultConfig = (): VerdaccioConfigApp => ({
file: './htpasswd',
max_users: 1000,
});
} as HTPasswdConfig;
describe('HTPasswd', () => {
let wrapper;
beforeEach(() => {
wrapper = new HTPasswd(getDefaultConfig(), stuff as unknown as VerdaccioConfigApp);
wrapper = new HTPasswd(config, options);
jest.resetModules();
jest.clearAllMocks();
crypto.randomBytes = jest.fn(() => {
return {
@@ -40,47 +39,72 @@ describe('HTPasswd', () => {
});
describe('constructor()', () => {
const emptyPluginOptions = { config: {} } as VerdaccioConfigApp;
const emptyPluginOptions = { config: {} } as any as PluginOptions<HTPasswdConfig>;
test('should files whether file path does not exist', () => {
test('should ensure file path configuration exists', () => {
expect(function () {
new HTPasswd({}, emptyPluginOptions);
new HTPasswd({} as HTPasswdConfig, emptyPluginOptions);
}).toThrow(/should specify "file" in config/);
});
test('should throw error about incorrect algorithm', () => {
expect(function () {
let config = getDefaultConfig();
config.algorithm = 'invalid' as any;
new HTPasswd(config, emptyPluginOptions);
let invalidConfig = { algorithm: 'invalid', ...config } as HTPasswdConfig;
new HTPasswd(invalidConfig, emptyPluginOptions);
}).toThrow(/Invalid algorithm "invalid"/);
});
});
describe('authenticate()', () => {
test('it should authenticate user with given credentials', (done) => {
const callbackTest = (a, b): void => {
expect(a).toBeNull();
expect(b).toContain('test');
done();
const users = [
{ username: 'test', password: 'test' },
{ username: 'username', password: 'password' },
{ username: 'bcrypt', password: 'password' },
];
let usersAuthenticated = 0;
const generateCallback = (username) => (error, userGroups) => {
usersAuthenticated += 1;
expect(error).toBeNull();
expect(userGroups).toContain(username);
usersAuthenticated === users.length && done();
};
const callbackUsername = (a, b): void => {
expect(a).toBeNull();
expect(b).toContain('username');
done();
};
wrapper.authenticate('test', 'test', callbackTest);
wrapper.authenticate('username', 'password', callbackUsername);
users.forEach(({ username, password }) =>
wrapper.authenticate(username, password, generateCallback(username))
);
});
test('it should not authenticate user with given credentials', (done) => {
const users = ['test', 'username', 'bcrypt'];
let usersAuthenticated = 0;
const generateCallback = () => (error, userGroups) => {
usersAuthenticated += 1;
expect(error).toBeNull();
expect(userGroups).toBeFalsy();
usersAuthenticated === users.length && done();
};
users.forEach((username) =>
wrapper.authenticate(username, 'somerandompassword', generateCallback())
);
});
test('it should warn on slow password verification', (done) => {
bcrypt.compare = jest.fn((passwd, hash, callback) => {
setTimeout(() => callback(null, true), DEFAULT_SLOW_VERIFY_MS + 1);
});
const callback = (a, b): void => {
expect(a).toBeNull();
expect(b).toBeFalsy();
expect(b).toContain('bcrypt');
const mockWarn = options.logger.warn as jest.MockedFn<jest.MockableFunction>;
expect(mockWarn.mock.calls.length).toBe(1);
const [{ user, durationMs }, message] = mockWarn.mock.calls[0];
expect(user).toEqual('bcrypt');
expect(durationMs).toBeGreaterThan(DEFAULT_SLOW_VERIFY_MS);
expect(message).toEqual('Password for user "@{user}" took @{durationMs}ms to verify');
done();
};
wrapper.authenticate('test', 'somerandompassword', callback);
});
wrapper.authenticate('bcrypt', 'password', callback);
}, 15000);
});
describe('addUser()', () => {
@@ -122,7 +146,7 @@ describe('HTPasswd', () => {
});
const HTPasswd = require('../src/htpasswd.ts').default;
const wrapper = new HTPasswd(config, stuff);
const wrapper = new HTPasswd(config, options);
wrapper.adduser('sanityCheck', 'test', (sanity) => {
expect(sanity.message).toBeDefined();
expect(sanity.message).toMatch('some error');
@@ -140,7 +164,7 @@ describe('HTPasswd', () => {
});
const HTPasswd = require('../src/htpasswd.ts').default;
const wrapper = new HTPasswd(config, stuff);
const wrapper = new HTPasswd(config, options);
wrapper.adduser('lockAndRead', 'test', (sanity) => {
expect(sanity.message).toBeDefined();
expect(sanity.message).toMatch('lock error');
@@ -160,7 +184,7 @@ describe('HTPasswd', () => {
});
const HTPasswd = require('../src/htpasswd.ts').default;
const wrapper = new HTPasswd(config, stuff);
const wrapper = new HTPasswd(config, options);
wrapper.adduser('addUserToHTPasswd', 'test', () => {
done();
});
@@ -187,7 +211,7 @@ describe('HTPasswd', () => {
});
const HTPasswd = require('../src/htpasswd.ts').default;
const wrapper = new HTPasswd(config, stuff);
const wrapper = new HTPasswd(config, options);
wrapper.adduser('addUserToHTPasswd', 'test', (err) => {
expect(err).not.toBeNull();
expect(err.message).toMatch('write error');
@@ -198,7 +222,11 @@ describe('HTPasswd', () => {
describe('reload()', () => {
test('it should read the file and set the users', (done) => {
const output = { test: '$6FrCaT/v0dwE', username: '$66to3JK5RgZM' };
const output = {
test: '$6FrCaT/v0dwE',
username: '$66to3JK5RgZM',
bcrypt: '$2y$04$K2Cn3StiXx4CnLmcTW/ymekOrj7WlycZZF9xgmoJ/U0zGPqSLPVBe',
};
const callback = (): void => {
expect(wrapper.users).toEqual(output);
done();
@@ -209,6 +237,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);
},
@@ -221,13 +252,16 @@ describe('HTPasswd', () => {
};
const HTPasswd = require('../src/htpasswd.ts').default;
const wrapper = new HTPasswd(config, stuff);
const wrapper = new HTPasswd(config, options);
wrapper.reload(callback);
});
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,
@@ -241,7 +275,7 @@ describe('HTPasswd', () => {
};
const HTPasswd = require('../src/htpasswd.ts').default;
const wrapper = new HTPasswd(config, stuff);
const wrapper = new HTPasswd(config, options);
wrapper.reload(callback);
});
@@ -261,7 +295,7 @@ describe('HTPasswd', () => {
};
const HTPasswd = require('../src/htpasswd.ts').default;
const wrapper = new HTPasswd(config, stuff);
const wrapper = new HTPasswd(config, options);
wrapper.reload(callback);
});
});
@@ -270,7 +304,9 @@ describe('HTPasswd', () => {
test('changePassword - it should throw an error for user not found', (done) => {
const callback = (error, isSuccess): void => {
expect(error).not.toBeNull();
expect(error.message).toBe('User not found');
expect(error.message).toBe(
`Unable to change password for user 'usernotpresent': user does not currently exist`
);
expect(isSuccess).toBeFalsy();
done();
};
@@ -280,7 +316,9 @@ describe('HTPasswd', () => {
test('changePassword - it should throw an error for wrong password', (done) => {
const callback = (error, isSuccess): void => {
expect(error).not.toBeNull();
expect(error.message).toBe('Invalid old Password');
expect(error.message).toBe(
`Unable to change password for user 'username': invalid old password`
);
expect(isSuccess).toBeFalsy();
done();
};

View File

@@ -1,3 +1,4 @@
// @ts-ignore: Module has no default export
import crypto from 'crypto';
import MockDate from 'mockdate';
@@ -66,40 +67,40 @@ user4:$6FrCasdvppdwE:autocreated 2017-12-14T13:30:20.838Z`;
});
describe('verifyPassword', () => {
it('should verify the MD5/Crypt3 password with true', () => {
it('should verify the MD5/Crypt3 password with true', async () => {
const input = ['test', '$apr1$sKXK9.lG$rZ4Iy63Vtn8jF9/USc4BV0'];
expect(verifyPassword(input[0], input[1])).toBeTruthy();
expect(await verifyPassword(input[0], input[1])).toBeTruthy();
});
it('should verify the MD5/Crypt3 password with false', () => {
it('should verify the MD5/Crypt3 password with false', async () => {
const input = ['testpasswordchanged', '$apr1$sKXK9.lG$rZ4Iy63Vtn8jF9/USc4BV0'];
expect(verifyPassword(input[0], input[1])).toBeFalsy();
expect(await verifyPassword(input[0], input[1])).toBeFalsy();
});
it('should verify the plain password with true', () => {
it('should verify the plain password with true', async () => {
const input = ['testpasswordchanged', '{PLAIN}testpasswordchanged'];
expect(verifyPassword(input[0], input[1])).toBeTruthy();
expect(await verifyPassword(input[0], input[1])).toBeTruthy();
});
it('should verify the plain password with false', () => {
it('should verify the plain password with false', async () => {
const input = ['testpassword', '{PLAIN}testpasswordchanged'];
expect(verifyPassword(input[0], input[1])).toBeFalsy();
expect(await verifyPassword(input[0], input[1])).toBeFalsy();
});
it('should verify the crypto SHA password with true', () => {
it('should verify the crypto SHA password with true', async () => {
const input = ['testpassword', '{SHA}i7YRj4/Wk1rQh2o740pxfTJwj/0='];
expect(verifyPassword(input[0], input[1])).toBeTruthy();
expect(await verifyPassword(input[0], input[1])).toBeTruthy();
});
it('should verify the crypto SHA password with false', () => {
it('should verify the crypto SHA password with false', async () => {
const input = ['testpasswordchanged', '{SHA}i7YRj4/Wk1rQh2o740pxfTJwj/0='];
expect(verifyPassword(input[0], input[1])).toBeFalsy();
expect(await verifyPassword(input[0], input[1])).toBeFalsy();
});
it('should verify the bcrypt password with true', () => {
it('should verify the bcrypt password with true', async () => {
const input = ['testpassword', '$2y$04$Wqed4yN0OktGbiUdxSTwtOva1xfESfkNIZfcS9/vmHLsn3.lkFxJO'];
expect(verifyPassword(input[0], input[1])).toBeTruthy();
expect(await verifyPassword(input[0], input[1])).toBeTruthy();
});
it('should verify the bcrypt password with false', () => {
it('should verify the bcrypt password with false', async () => {
const input = [
'testpasswordchanged',
'$2y$04$Wqed4yN0OktGbiUdxSTwtOva1xfESfkNIZfcS9/vmHLsn3.lkFxJO',
];
expect(verifyPassword(input[0], input[1])).toBeFalsy();
expect(await verifyPassword(input[0], input[1])).toBeFalsy();
});
});
@@ -170,58 +171,58 @@ describe('sanityCheck', () => {
users = { test: '$6FrCaT/v0dwE' };
});
test('should throw error for user already exists', () => {
test('should throw error for user already exists', async () => {
const verifyFn = jest.fn();
const input = sanityCheck('test', users.test, verifyFn, users, Infinity);
const input = await sanityCheck('test', users.test, verifyFn, users, Infinity);
expect(input.status).toEqual(401);
expect(input.message).toEqual('unauthorized access');
expect(verifyFn).toHaveBeenCalled();
});
test('should throw error for registration disabled of users', () => {
test('should throw error for registration disabled of users', async () => {
const verifyFn = (): void => {};
const input = sanityCheck('username', users.test, verifyFn, users, -1);
const input = await sanityCheck('username', users.test, verifyFn, users, -1);
expect(input.status).toEqual(409);
expect(input.message).toEqual('user registration disabled');
});
test('should throw error max number of users', () => {
test('should throw error max number of users', async () => {
const verifyFn = (): void => {};
const input = sanityCheck('username', users.test, verifyFn, users, 1);
const input = await sanityCheck('username', users.test, verifyFn, users, 1);
expect(input.status).toEqual(403);
expect(input.message).toEqual('maximum amount of users reached');
});
test('should not throw anything and sanity check', () => {
test('should not throw anything and sanity check', async () => {
const verifyFn = (): void => {};
const input = sanityCheck('username', users.test, verifyFn, users, 2);
const input = await sanityCheck('username', users.test, verifyFn, users, 2);
expect(input).toBeNull();
});
test('should throw error for required username field', () => {
test('should throw error for required username field', async () => {
const verifyFn = (): void => {};
const input = sanityCheck(undefined, users.test, verifyFn, users, 2);
const input = await sanityCheck(undefined, users.test, verifyFn, users, 2);
expect(input.message).toEqual('username and password is required');
expect(input.status).toEqual(400);
});
test('should throw error for required password field', () => {
test('should throw error for required password field', async () => {
const verifyFn = (): void => {};
const input = sanityCheck('username', undefined, verifyFn, users, 2);
const input = await sanityCheck('username', undefined, verifyFn, users, 2);
expect(input.message).toEqual('username and password is required');
expect(input.status).toEqual(400);
});
test('should throw error for required username & password fields', () => {
test('should throw error for required username & password fields', async () => {
const verifyFn = (): void => {};
const input = sanityCheck(undefined, undefined, verifyFn, users, 2);
const input = await sanityCheck(undefined, undefined, verifyFn, users, 2);
expect(input.message).toEqual('username and password is required');
expect(input.status).toEqual(400);
});
test('should throw error for existing username and password', () => {
test('should throw error for existing username and password', async () => {
const verifyFn = jest.fn(() => true);
const input = sanityCheck('test', users.test, verifyFn, users, 2);
const input = await sanityCheck('test', users.test, verifyFn, users, 2);
expect(input.status).toEqual(409);
expect(input.message).toEqual('username is already registered');
expect(verifyFn).toHaveBeenCalledTimes(1);
@@ -229,9 +230,9 @@ describe('sanityCheck', () => {
test(
'should throw error for existing username and password with max number ' + 'of users reached',
() => {
async () => {
const verifyFn = jest.fn(() => true);
const input = sanityCheck('test', users.test, verifyFn, users, 1);
const input = await sanityCheck('test', users.test, verifyFn, users, 1);
expect(input.status).toEqual(409);
expect(input.message).toEqual('username is already registered');
expect(verifyFn).toHaveBeenCalledTimes(1);
@@ -240,11 +241,11 @@ describe('sanityCheck', () => {
});
describe('changePasswordToHTPasswd', () => {
test('should throw error for wrong password', () => {
test('should throw error for wrong password', async () => {
const body = 'test:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z';
try {
changePasswordToHTPasswd(
await changePasswordToHTPasswd(
body,
'test',
'somerandompassword',
@@ -252,15 +253,35 @@ describe('changePasswordToHTPasswd', () => {
defaultHashConfig
);
} catch (error: any) {
expect(error.message).toEqual('Invalid old Password');
expect(error.message).toEqual(
`Unable to change password for user 'test': invalid old password`
);
}
});
test('should change the password', () => {
test('should throw error when user does not exist', async () => {
const body = 'test:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z';
try {
await changePasswordToHTPasswd(
body,
'test2',
'somerandompassword',
'newPassword',
defaultHashConfig
);
} catch (error: any) {
expect(error.message).toEqual(
`Unable to change password for user 'test2': user does not currently exist`
);
}
});
test('should change the password', async () => {
const body = 'root:$6qLTHoPfGLy2:autocreated 2018-08-20T13:38:12.164Z';
expect(
changePasswordToHTPasswd(body, 'root', 'demo123', 'newPassword', defaultHashConfig)
await changePasswordToHTPasswd(body, 'root', 'demo123', 'newPassword', defaultHashConfig)
).toMatchSnapshot();
});
});

View File

@@ -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",

View File

@@ -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,
};

View File

@@ -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));
}
}

View File

@@ -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;

View File

@@ -0,0 +1,4 @@
/*
!/static/**/*
!index.js
!README.md

View File

@@ -1,5 +1,53 @@
# @verdaccio/ui-theme
## 6.0.0-6-next.22
### Patch Changes
- a2b69a08: add banner support ukraine
## 6.0.0-6-next.21
### Patch Changes
- a179f1fd: show verdaccio logo in the footer even when custom brand is set
## 6.0.0-6-next.20
### Patch Changes
- 635ca3f9: chore: force publish
## 6.0.0-6-next.19
### Patch Changes
- aa0b2aa9: fix: replace ts icon by td and fix commonjs icon
## 6.0.0-6-next.18
### Patch Changes
- df53f61c: feat: add types and package module icons on sidebar
## 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

View File

@@ -4,7 +4,7 @@ module.exports = Object.assign({}, config, {
name: 'verdaccio-ui-jest',
verbose: true,
automock: false,
collectCoverage: true,
collectCoverage: false,
testEnvironment: 'jest-environment-jsdom-global',
transform: {
'^.+\\.(js|ts|tsx)$': 'babel-jest',
@@ -29,11 +29,14 @@ module.exports = Object.assign({}, config, {
'\\.(s?css)$': '<rootDir>/jest/identity.js',
'\\.(png)$': '<rootDir>/jest/identity.js',
'\\.(svg)$': '<rootDir>/jest/unit/empty.ts',
'\\.(jpg)$': '<rootDir>/jest/unit/empty.ts',
'github-markdown-css': '<rootDir>/jest/identity.js',
// note: this section has to be on sync with webpack configuration
'verdaccio-ui/components/(.*)': '<rootDir>/src/components/$1',
'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',
},
});

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/ui-theme",
"version": "6.0.0-6-next.14",
"version": "6.0.0-6-next.22",
"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.29",
"@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",

View File

@@ -1,8 +1,8 @@
/* eslint-disable react/jsx-max-depth */
import styled from '@emotion/styled';
import Box from '@mui/material/Box';
import React, { Suspense, useEffect } from 'react';
import { Router } from 'react-router-dom';
import Box from 'verdaccio-ui/components/Box';
import Loading from 'verdaccio-ui/components/Loading';
import StyleBaseline from 'verdaccio-ui/design-tokens/StyleBaseline';
import loadDayJSLocale from 'verdaccio-ui/design-tokens/load-dayjs-locale';

View File

@@ -56,7 +56,7 @@ const Footer = () => {
{configOptions?.version && (
<>
{t('footer.powered-by')}
<Logo onClick={goToVerdaccioWebsite} size="x-small" />
<Logo isDefault={true} onClick={goToVerdaccioWebsite} size="x-small" />
{`/ ${configOptions.version}`}
</>
)}

View File

@@ -1,7 +1,7 @@
import Button from '@mui/material/Button';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import Button from 'verdaccio-ui/components/Button';
import { useConfig } from 'verdaccio-ui/providers/config';
import { Dispatch, RootState } from '../../store/store';
@@ -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>

View File

@@ -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;

Some files were not shown because too many files have changed in this diff Show More