Compare commits

...

316 Commits

Author SHA1 Message Date
semantic-release-bot
0c9a515874 Release 4.1.3
[skip ci]

## [4.1.3](https://github.com/cnpm/cnpmcore/compare/v4.1.2...v4.1.3) (2025-03-16)

### Performance Improvements

* refactor AsyncTimer advice to Singleton ([#775](https://github.com/cnpm/cnpmcore/issues/775)) ([884ff50](884ff50a2f))
2025-03-16 01:50:28 +00:00
killa
884ff50a2f perf: refactor AsyncTimer advice to Singleton (#775)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Refactor**
- Streamlined asynchronous operation tracking for improved performance
monitoring.
- Enhanced event handling configuration to provide more consistent and
reliable system behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-03-16 09:48:56 +08:00
fengmk2
1517750513 test: disable oss tests 2025-03-14 20:53:57 +08:00
semantic-release-bot
64beee1ba1 Release 4.1.2
[skip ci]

## [4.1.2](https://github.com/cnpm/cnpmcore/compare/v4.1.1...v4.1.2) (2025-03-14)

### Bug Fixes

* use tsconfig.json for tsc ([#773](https://github.com/cnpm/cnpmcore/issues/773)) ([acb988b](acb988b553))
2025-03-14 12:09:48 +00:00
fengmk2
acb988b553 fix: use tsconfig.json for tsc (#773)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Chores**
- Updated the build command to use revised TypeScript configuration
settings, ensuring a streamlined and consistent build process.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-03-14 20:08:49 +08:00
fengmk2
ffe723e65f refactor: use oxlint instead of eslint (#772)
say goodbye to eslint
2025-03-13 23:31:13 +08:00
semantic-release-bot
ed4d5d07ad Release 4.1.1
[skip ci]

## [4.1.1](https://github.com/cnpm/cnpmcore/compare/v4.1.0...v4.1.1) (2025-03-11)

### Bug Fixes

* don't block tgz ([#770](https://github.com/cnpm/cnpmcore/issues/770)) ([b92354d](b92354d280))
2025-03-11 14:42:50 +00:00
fengmk2
b92354d280 fix: don't block tgz (#770)
revert https://github.com/cnpm/cnpmcore/pull/763

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Refactor**
- Streamlined the package download process by removing outdated package
blocking checks, leading to a smoother and more direct download
experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-03-11 22:41:16 +08:00
semantic-release-bot
f51f6028ac Release 4.1.0
[skip ci]

## [4.1.0](https://github.com/cnpm/cnpmcore/compare/v4.0.3...v4.1.0) (2025-03-09)

### Features

* remove codeql-analysis.yml ([3f3751b](3f3751b2d5))
* use tegg v4 ([#769](https://github.com/cnpm/cnpmcore/issues/769)) ([7938919](7938919d81))
2025-03-09 09:30:39 +00:00
fengmk2
3f3751b2d5 feat: remove codeql-analysis.yml 2025-03-09 17:28:49 +08:00
fengmk2
7938919d81 feat: use tegg v4 (#769)
[skip ci]

---------

Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net>
2025-03-09 17:20:44 +08:00
fengmk2
52c2494355 test: fix unstable tests 2025-03-09 17:11:20 +08:00
fengmk2
0d4fec90dd test: remove oxlint quiet 2025-03-09 16:53:52 +08:00
fengmk2
b8d055a74e test: remove test/common/EnvUtil.test.ts 2025-03-09 16:50:12 +08:00
fengmk2
5c8b024a0a Merge branch 'next' into merge-next 2025-03-09 16:45:43 +08:00
semantic-release-bot
914aee2560 Release 3.75.1
[skip ci]

## [3.75.1](https://github.com/cnpm/cnpmcore/compare/v3.75.0...v3.75.1) (2025-03-09)

### Bug Fixes

* only get packageId from database ([#768](https://github.com/cnpm/cnpmcore/issues/768)) ([bc068d1](bc068d165c))
2025-03-09 02:10:36 +00:00
fengmk2
bc068d165c fix: only get packageId from database (#768)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Refactor**
- Enhanced the internal process for retrieving package version details,
resulting in more accurate and reliable version information.
- Streamlined the handling of missing package data to reduce unexpected
issues.
- Optimized overall data processing, contributing to improved stability
and a smoother user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-03-09 10:09:03 +08:00
semantic-release-bot
9ffb09eaa8 Release 3.75.0
[skip ci]

## [3.75.0](https://github.com/cnpm/cnpmcore/compare/v3.74.2...v3.75.0) (2025-03-09)

### Features

* mirror node-pty-prebuilt-multiarch ([#767](https://github.com/cnpm/cnpmcore/issues/767)) ([cbefb5c](cbefb5c6d0))
2025-03-09 01:56:37 +00:00
时瑾
cbefb5c6d0 feat: mirror node-pty-prebuilt-multiarch (#767)
closes https://github.com/cnpm/cnpmcore/issues/766

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Expanded our binary support to include prebuilt binaries for the
node-pty package, enhancing integration possibilities and deployment
options for users relying on these binaries.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-03-09 09:55:02 +08:00
fengmk2
0a64698ec0 refactor: use tegg v4 (#765) 2025-03-06 00:04:09 +08:00
elrrrrrrr
1922bf2f76 chore: disbale public registration by default (#764)
> The 'allowPublicRegistration' is enabled by default, which my cause
unexpected users registering arbitrarily
1. ⚙ Modify the default configuration 'allowPublicRegistration' to
'false'`

-------------
> 目前默认开启了 `allowPublicRegistration` 配置,公网部署可能会导致预期外的用户任意注册

1. ⚙ 修改默认配置 `allowPublicRegistration` 为 `false`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Public registration can now be enabled through a new configuration
option, allowing for more flexible user onboarding.
  
- **Bug Fixes**
- Updated the configuration to disallow public registration by default,
ensuring only administrators can log in unless changed.

- **Documentation**
- Added an informational note in the developer documentation regarding
public registration settings.

- **Tests**
- Introduced a setup method to enable public registration before each
test case runs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-02-28 09:34:11 +08:00
semantic-release-bot
47da2f40cf Release 3.74.2
[skip ci]

## [3.74.2](https://github.com/cnpm/cnpmcore/compare/v3.74.1...v3.74.2) (2025-02-27)

### Bug Fixes

* block tgz ([#763](https://github.com/cnpm/cnpmcore/issues/763)) ([3054577](305457777e))
2025-02-27 06:44:47 +00:00
elrrrrrrr
305457777e fix: block tgz (#763)
> The tgz download interface does not check if the package is blocked,
which may pose additional risks for parsing package-lock.json or other
lock files.
[exp](https://registry.npmmirror.com/joker-su/-/joker-su-1.0.0.tgz)
1. 🛡️ Add validation logic for
DownloadPackageVersionTarController#download to check if the package is
allowed to be downloaded.
2. 🧶 Add PackageVersionService#findBlockInfo to check if the
corresponding package is blocked.
3. ♻️ When a single version is blocked, skip check as per the current
manifest logic.

---------

> tgz 下载接口没有判断包是否被 block,对于 package-lock.json
或者其他依赖锁文件解析可能会有额外风险,[exp](https://registry.npmmirror.com/joker-su/-/joker-su-1.0.0.tgz)

1. 🛡️ `DownloadPackageVersionTarController#download` 接口新增校验逻辑,判断是否允许下载
2. 🧶 新增 PackageVersionService#findBlockInfo 判断对应包是否被全局拦截
3. ♻️ 单版本被 block 时,考虑到误封场景,按目前 manifest 逻辑,不在 tgz 下载时进行拦截操作

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Enhanced the package download process with an additional block check.
Now, if a package is flagged, the download will be halted and a clear
error response is returned to inform users of the block.
- Introduced a method to retrieve block information related to package
versions, improving the service's capabilities.

- **Tests**
- Added new test cases to verify the blocking functionality for package
downloads, ensuring the application correctly handles requests for
blocked packages.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-02-27 06:43:28 +00:00
Kevin Cui
ae88145317 docs(elk): add missing version field (#760) 2025-02-26 18:01:05 +08:00
semantic-release-bot
069afb98cc Release 4.0.2
[skip ci]

## [4.0.2](https://github.com/cnpm/cnpmcore/compare/v4.0.1...v4.0.2) (2025-02-10)

### Bug Fixes

* should return default value when env is empty string ([#759](https://github.com/cnpm/cnpmcore/issues/759)) ([b6c781e](b6c781ec25))
2025-02-10 11:47:32 +00:00
fengmk2
b6c781ec25 fix: should return default value when env is empty string (#759)
pick from https://github.com/cnpm/cnpmcore/pull/754
2025-02-10 19:46:06 +08:00
semantic-release-bot
05b3b798b6 Release 3.74.1
[skip ci]

## [3.74.1](https://github.com/cnpm/cnpmcore/compare/v3.74.0...v3.74.1) (2025-02-10)

### Bug Fixes

* should return default value when env is empty string ([#758](https://github.com/cnpm/cnpmcore/issues/758)) ([e72e396](e72e396e3c))
2025-02-10 01:28:21 +00:00
fengmk2
e72e396e3c fix: should return default value when env is empty string (#758)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- Refactor
- Enhanced environment variable handling to trim extra whitespace and
improve default value checks for more robust configuration processing.
  
- Tests
- Expanded test coverage to validate default behavior, type conversions,
and error handling for various environment variable scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-02-10 09:26:47 +08:00
fengmk2
d095d3f48c chore: fix docker demo url 2025-02-09 23:25:05 +08:00
fengmk2
b0f7bf0967 chore: enable elasticsearch on local dev env (#757) 2025-02-09 23:21:49 +08:00
fengmk2
02a1ee35d7 chore: support alpine docker image (#755) 2025-02-09 21:30:37 +08:00
semantic-release-bot
1e9d710b0f Release 4.0.1
[skip ci]

## [4.0.1](https://github.com/cnpm/cnpmcore/compare/v4.0.0...v4.0.1) (2025-02-09)

### Bug Fixes

* remove npm-cli-login for ssri security ([#754](https://github.com/cnpm/cnpmcore/issues/754)) ([d18981e](d18981e658))
2025-02-09 08:40:22 +00:00
fengmk2
d18981e658 fix: remove npm-cli-login for ssri security (#754)
https://github.com/advisories/GHSA-vx3p-948g-6vhq

![image](https://github.com/user-attachments/assets/4eaf45a2-f3da-45ba-9613-c3bcb62f31f2)

https://hub.docker.com/repository/docker/fengmk2/cnpmcore/tags/v4/sha256-38b310438907eec1f00da6f110f6f24c81c85e983833fddd08a31598510154f3
2025-02-09 16:38:55 +08:00
semantic-release-bot
5103409f40 Release 4.0.0
[skip ci]

## [4.0.0](https://github.com/cnpm/cnpmcore/compare/v3.74.0...v4.0.0) (2025-02-09)

### ⚠ BREAKING CHANGES

* only support egg >= 4.0.0

the first app on egg v4

https://github.com/eggjs/egg/issues/3644

### Features

* use egg v4 ([#747](https://github.com/cnpm/cnpmcore/issues/747)) ([4427a4f](4427a4fca5))
2025-02-09 07:48:34 +00:00
fengmk2
ea4823c017 chore: enable auto release on next branch 2025-02-09 15:46:49 +08:00
fengmk2
4427a4fca5 feat: use egg v4 (#747)
BREAKING CHANGE: only support egg >= 4.0.0

the first app on egg v4

https://github.com/eggjs/egg/issues/3644
2025-02-09 15:43:24 +08:00
fengmk2
455fc3a444 chore: start next branch 2025-02-09 15:37:07 +08:00
semantic-release-bot
386974272d Release 3.74.0
[skip ci]

## [3.74.0](https://github.com/cnpm/cnpmcore/compare/v3.73.1...v3.74.0) (2025-02-09)

### Features

* allow to set sourceRegistry by CNPMCORE_CONFIG_SOURCE_REGISTRY ([#753](https://github.com/cnpm/cnpmcore/issues/753)) ([9f4f1f1](9f4f1f1e28))
2025-02-09 03:16:57 +00:00
fengmk2
9f4f1f1e28 feat: allow to set sourceRegistry by CNPMCORE_CONFIG_SOURCE_REGISTRY (#753)
Improve the local development process based on docker-compose

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a centralized configuration utility that validates
environment variable types and provides fallback defaults.
- **Refactor**
- Standardized environment variable handling across configuration files,
improving maintainability and consistency in system setup.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-02-09 11:15:28 +08:00
semantic-release-bot
aba2b36291 Release 3.73.1
[skip ci]

## [3.73.1](https://github.com/cnpm/cnpmcore/compare/v3.73.0...v3.73.1) (2025-02-08)

### Bug Fixes

* webauth no need to validate hostname on cli request ([#752](https://github.com/cnpm/cnpmcore/issues/752)) ([f1fc249](f1fc2492b6))
2025-02-08 09:34:38 +00:00
fengmk2
f1fc2492b6 fix: webauth no need to validate hostname on cli request (#752) 2025-02-08 17:33:12 +08:00
semantic-release-bot
c23a6699f3 Release 3.73.0
[skip ci]

## [3.73.0](https://github.com/cnpm/cnpmcore/compare/v3.72.1...v3.73.0) (2025-02-07)

### Features

* update playwright DOWNLOAD_PATHS ([#751](https://github.com/cnpm/cnpmcore/issues/751)) ([1850c8b](1850c8b2d4))
2025-02-07 16:09:19 +00:00
fengmk2
1850c8b2d4 feat: update playwright DOWNLOAD_PATHS (#751)
closes https://github.com/cnpm/cnpmcore/issues/750

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Expanded support with additional platform configurations for Ubuntu
24.04 and macOS 15 (including ARM variants).
	- Introduced a new Windows configuration for improved compatibility.
- Updated browser configurations to deliver the latest versions across
Chromium, Firefox, Webkit, FFmpeg, and Android.
- **Tests**
- Refined validation checks and error reporting to ensure consistent and
reliable browser setups.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-02-08 00:08:03 +08:00
semantic-release-bot
c70fdccc04 Release 3.72.1
[skip ci]

## [3.72.1](https://github.com/cnpm/cnpmcore/compare/v3.72.0...v3.72.1) (2025-02-07)

### Bug Fixes

* chromium-headless-shell should under the chromium dir ([#749](https://github.com/cnpm/cnpmcore/issues/749)) ([a580b05](a580b05004))
2025-02-07 15:03:41 +00:00
fengmk2
a580b05004 fix: chromium-headless-shell should under the chromium dir (#749)
closes https://github.com/cnpm/cnpmcore/issues/742

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- Improved organization for browser downloads: Secondary browser
components are now integrated under the primary browser category,
resulting in a more intuitive and streamlined binary structure for
users.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-02-07 23:02:11 +08:00
semantic-release-bot
43636bd80b Release 3.72.0
[skip ci]

## [3.72.0](https://github.com/cnpm/cnpmcore/compare/v3.71.3...v3.72.0) (2025-02-07)

### Features

* **mirror:** add chromium-headless-shell ([#748](https://github.com/cnpm/cnpmcore/issues/748)) ([3a3aa81](3a3aa818a3)), closes [#742](https://github.com/cnpm/cnpmcore/issues/742)
2025-02-07 14:12:55 +00:00
Beace
3a3aa818a3 feat(mirror): add chromium-headless-shell (#748)
在内部也遇到了,感觉直接加就行,可以试试看看 @fengmk2 

close #742 

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced enhanced support for the chromium-headless-shell binary
with updated download options across multiple operating systems,
including popular Linux distributions, macOS (with arm64 support), and
Windows. This improvement ensures smoother integration and broader
compatibility for users running different platforms.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: fengmk2 <fengmk2@gmail.com>
2025-02-07 22:11:25 +08:00
semantic-release-bot
4012f584ba Release 3.71.3
[skip ci]

## [3.71.3](https://github.com/cnpm/cnpmcore/compare/v3.71.2...v3.71.3) (2024-12-23)

### Bug Fixes

* incorrect request headers in proxy mode and deleted unparsable cached data ([#719](https://github.com/cnpm/cnpmcore/issues/719)) ([2780c53](2780c532e1))
2024-12-23 06:43:23 +00:00
hezhengxu2018
2780c532e1 fix: incorrect request headers in proxy mode and deleted unparsable cached data (#719)
proxy时因为一个低级的拼写错误没有正确的携带请求头,导致代理模式时返回的数据不正确。但是现在用户发起的请求中的user-agent和x-forwarded等头部信息也没有正确的携带。虽然影响不大但还是想和跑批时更新的请求做一下区分。


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Enhanced error handling and logging for task execution, improving
traceability.
  
- **Improvements**
- Updated HTTP header access method for better alignment with context
structure.
- Clarified logic for manifest retrieval based on file type, ensuring
correct API usage.
- Streamlined cache handling and response generation logic in package
management.
- Improved method visibility and organization within the cache service
and controller.
- Simplified task creation logic and cache removal processes in the
controller.
- Updated expected outcomes for cache-related operations in the test
cases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-12-23 14:41:56 +08:00
semantic-release-bot
638a3da767 Release 3.71.2
[skip ci]

## [3.71.2](https://github.com/cnpm/cnpmcore/compare/v3.71.1...v3.71.2) (2024-12-18)

### Bug Fixes

* **search:** author is `???` in result ([#741](https://github.com/cnpm/cnpmcore/issues/741)) ([acffb14](acffb14ea0)), closes [/github.com/npm/cli/pull/7407/files#diff-4bc15933c685fc9a9ce8be0c13a2f067f5e2b3334bacd6664bdfa7ddc46aedb6L58](https://github.com/cnpm//github.com/npm/cli/pull/7407/files/issues/diff-4bc15933c685fc9a9ce8be0c13a2f067f5e2b3334bacd6664bdfa7ddc46aedb6L58) [/github.com/npm/cli/pull/7407/files#diff-4bc15933c685fc9a9ce8be0c13a2f067f5e2b3334bacd6664bdfa7ddc46aedb6R162](https://github.com/cnpm//github.com/npm/cli/pull/7407/files/issues/diff-4bc15933c685fc9a9ce8be0c13a2f067f5e2b3334bacd6664bdfa7ddc46aedb6R162)
2024-12-18 12:47:03 +00:00
Kevin Cui
acffb14ea0 fix(search): author is ??? in result (#741)
### 改动原因

在今年 4 月份,npm cli 对 search 做了[一些改动],在 4 月份以前(npm cli < `v10.6.0`),采用的是
`maintainers` 字段,而在 https://github.com/npm/cli/pull/7407 后,改为使用:
`publisher` 字段。

导致当 npm 版本大于等于 `v10.6.0` 后,search 结果中的 `author` 将变成 `???`,如图:

![CleanShot 2024-12-18 at 16 46
58@2x](https://github.com/user-attachments/assets/4b97bf63-78cb-4720-8c00-469eeff3e92f)

预期的结果应该为:

![CleanShot 2024-12-18 at 16 47
33@2x](https://github.com/user-attachments/assets/2d7fca89-4383-42bd-8b83-66257efe65e8)

### 技术细节说明

当前改动没有为 `es` 增加新的索引,原因是处于以下考虑:

1. es 的 `mapping` 一旦创建,就无法修改(虽然使用了 `dynamic: true` 但无法细粒度的进行控制)
2. 源数据中的 `_npmUser` 已经有相关信息了,没有必要为此浪费额外的磁盘空间
3. 如果想对以前的数据进行更新会比较麻烦,性价比较低

npm cli 老版本:
https://github.com/npm/cli/pull/7407/files#diff-4bc15933c685fc9a9ce8be0c13a2f067f5e2b3334bacd6664bdfa7ddc46aedb6L58
npm cli 新版本:
https://github.com/npm/cli/pull/7407/files#diff-4bc15933c685fc9a9ce8be0c13a2f067f5e2b3334bacd6664bdfa7ddc46aedb6R162

### 其他

相关 PR: https://github.com/cnpm/cnpmcore/pull/513

PTAL @Beace @fengmk2 @elrrrrrrr

[一些改动]: https://github.com/npm/cli/pull/7407

Signed-off-by: Kevin Cui <bh@bugs.cc>
2024-12-18 20:45:32 +08:00
semantic-release-bot
feba680795 Release 3.71.1
[skip ci]

## [3.71.1](https://github.com/cnpm/cnpmcore/compare/v3.71.0...v3.71.1) (2024-12-09)

### Bug Fixes

* ignore npm registry 404 status response on sync process ([#740](https://github.com/cnpm/cnpmcore/issues/740)) ([57226c5](57226c57a6))
2024-12-09 11:29:42 +00:00
fengmk2
57226c57a6 fix: ignore npm registry 404 status response on sync process (#740)
closes https://github.com/cnpm/cnpmcore/issues/739

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Enhanced error handling for package synchronization, including
specific logging for package not found scenarios.
	- Simplified criteria for identifying removed packages.

- **Bug Fixes**
	- Corrected documentation for the `syncMode` property.

- **Chores**
	- Updated dependency versions in `package.json`.

- **Tests**
- Added new test cases and refined existing assertions to improve
logging and error handling verification.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-12-09 19:28:08 +08:00
semantic-release-bot
167e37c241 Release 3.71.0
[skip ci]

## [3.71.0](https://github.com/cnpm/cnpmcore/compare/v3.70.0...v3.71.0) (2024-11-30)

### Features

* sync chrome-for-testing json files ([#737](https://github.com/cnpm/cnpmcore/issues/737)) ([9bb12fd](9bb12fde12))
2024-11-30 14:31:33 +00:00
fengmk2
9bb12fde12 feat: sync chrome-for-testing json files (#737)
closes https://github.com/cnpm/cnpmcore/issues/730

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Enhanced data fetching for Chrome for Testing with new JSON entries
for version management.
  
- **Bug Fixes**
- Improved data validation in tests to ensure correct structure and
content of fetched data.

- **Tests**
- Added assertions to validate properties of fetched items in the
ChromeForTestingBinary tests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-11-30 22:29:55 +08:00
semantic-release-bot
1d128e280c Release 3.70.0
[skip ci]

## [3.70.0](https://github.com/cnpm/cnpmcore/compare/v3.69.0...v3.70.0) (2024-11-30)

### Features

* support PostgreSQL ([#733](https://github.com/cnpm/cnpmcore/issues/733)) ([f240799](f240799fa2))
2024-11-30 13:58:14 +00:00
fengmk2
f240799fa2 feat: support PostgreSQL (#733)
closes https://github.com/cnpm/cnpmcore/issues/731

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

- **New Features**
- Added support for PostgreSQL alongside MySQL, including a new database
setup script and comprehensive documentation for local development.
- Introduced a new CI job for PostgreSQL testing in the GitHub Actions
workflow.
- Enhanced the `README.md` and `DEVELOPER.md` files to provide clearer
instructions for using both database systems.
- Added new environment variable configurations for PostgreSQL in the
Docker deployment documentation.

- **Bug Fixes**
- Improved error handling in tests for duplicate entries to accommodate
both MySQL and PostgreSQL error messages.

- **Documentation**
- Updated setup instructions for PostgreSQL and clarified MySQL setup in
the documentation.
	- Enhanced contributor information in the README.
- Expanded instructions for setting up Elasticsearch and Kibana,
including environment variable configurations.

- **Chores**
- Updated package dependencies to include PostgreSQL client libraries
and modified scripts to support both databases.
	- Changed the base image in the Dockerfile to a newer Node.js version.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-11-30 21:56:45 +08:00
semantic-release-bot
dd15b08fa2 Release 3.69.0
[skip ci]

## [3.69.0](https://github.com/cnpm/cnpmcore/compare/v3.68.0...v3.69.0) (2024-11-30)

### Features

* mirror deno ([#736](https://github.com/cnpm/cnpmcore/issues/736)) ([6de0876](6de0876d35))
2024-11-30 08:36:07 +00:00
fengmk2
6de0876d35 feat: mirror deno (#736)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced support for the Deno runtime, providing users with a modern
runtime for JavaScript and TypeScript.
- **Documentation**
- Updated configuration to include Deno, ensuring users can easily
access and utilize this new option.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-11-30 16:34:36 +08:00
semantic-release-bot
63a8473af7 Release 3.68.0
[skip ci]

## [3.68.0](https://github.com/cnpm/cnpmcore/compare/v3.67.1...v3.68.0) (2024-11-30)

### Features

* enable allowH2 by default and require Node.js >= 18.20.0 ([#734](https://github.com/cnpm/cnpmcore/issues/734)) ([9b01383](9b01383210))
2024-11-30 07:54:28 +00:00
fengmk2
9b01383210 feat: enable allowH2 by default and require Node.js >= 18.20.0 (#734)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

- **New Features**
- Enhanced error handling across various services by introducing a
centralized timeout error checking function.
	- HTTP/2 support enabled in the HTTP client configuration.

- **Bug Fixes**
	- Corrected a typographical error in comments for better clarity.

- **Documentation**
	- Updated Node.js version requirements in the project configuration.

- **Tests**
- Improved test cases for `NpmChangesStream` and `TaskRepository` to
ensure accurate behavior and performance.

- **Chores**
	- Updated Node.js version in CI workflow for more precise testing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-11-30 15:53:00 +08:00
fengmk2
b808ebcd60 chore: security support >= 3.0.0 (#732)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Documentation**
- Updated the "Supported Versions" section in the SECURITY.md file to
reflect that security updates are now provided for versions 3.0.0 and
above.
- Minor formatting adjustments made in the "Disclosure Policy" section
for consistency.
- **Chores**
- Simplified debugger configurations by removing explicit protocol and
port settings from the `.vscode/launch.json` file.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: elrrrrrrr <elrrrrrrr@gmail.com>
2024-11-24 12:59:15 +08:00
semantic-release-bot
71cc3381d7 Release 3.67.1
[skip ci]

## [3.67.1](https://github.com/cnpm/cnpmcore/compare/v3.67.0...v3.67.1) (2024-11-14)

### Bug Fixes

* adapter new html format on nodejs.org/dist ([#728](https://github.com/cnpm/cnpmcore/issues/728)) ([914b59c](914b59c7ef))
2024-11-14 15:14:40 +00:00
fengmk2
914b59c7ef fix: adapter new html format on nodejs.org/dist (#728)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Enhanced file fetching capabilities with updated logic to handle a
broader range of file formats and attributes.
- Introduced a new index HTML page for Node.js distribution version
18.15.0, listing downloadable files with metadata.

- **Bug Fixes**
- Improved regex for matching HTML anchor tags to accurately capture
additional file types and structures.

- **Tests**
- Added new test cases for the `fetch()` method to verify functionality
against the Node.js distribution version 18.15.0.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-11-14 23:13:03 +08:00
semantic-release-bot
ac4709a7be Release 3.67.0
[skip ci]

## [3.67.0](https://github.com/cnpm/cnpmcore/compare/v3.66.0...v3.67.0) (2024-11-09)

### Features

* say goodbye to alinode ([#726](https://github.com/cnpm/cnpmcore/issues/726)) ([99a5ef1](99a5ef1715))
2024-11-09 07:09:12 +00:00
fengmk2
99a5ef1715 feat: say goodbye to alinode (#726)
> getaddrinfo ENOTFOUND alinode.aliyun.com

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a new property to disable the `alinode` binary, affecting
its availability in the application.

- **Improvements**
- Updated the `ignoreDownloadStatuses` for the `python` binary to
enhance type safety, ensuring proper adherence to expected data types.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-11-09 15:07:44 +08:00
semantic-release-bot
2edbec6008 Release 3.66.0
[skip ci]

## [3.66.0](https://github.com/cnpm/cnpmcore/compare/v3.65.0...v3.66.0) (2024-11-03)

### Features

* compatible verdaccio path style ([#723](https://github.com/cnpm/cnpmcore/issues/723)) ([7158e66](7158e66c9f))
2024-11-03 12:37:16 +00:00
ZhengJin
7158e66c9f feat: compatible verdaccio path style (#723)
兼容Verdaccio下载地址风格,镜像库从Verdaccio切换至cnpmcore后无需大面积调整lock文件

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a comprehensive test suite for the download functionality,
ensuring robust behavior for various package retrieval scenarios.
- Enhanced validation for CORS requests and CDN redirection during
package downloads.
- Added new test cases for downloading version tarballs with scoped
package handling.

- **Bug Fixes**
- Improved error handling for non-existent packages and versions,
providing clearer error messages.
- Ensured proper handling of deprecated download paths and
scoped/non-scoped package names.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-11-03 20:35:45 +08:00
semantic-release-bot
4facf90ae0 Release 3.65.0
[skip ci]

## [3.65.0](https://github.com/cnpm/cnpmcore/compare/v3.64.0...v3.65.0) (2024-10-26)

### Features

* strict validate deps ([#720](https://github.com/cnpm/cnpmcore/issues/720)) ([12650ac](12650acf72))
2024-10-26 11:59:49 +00:00
elrrrrrrr
12650acf72 feat: strict validate deps (#720)
> During the sync process, dependency installation errors may occur due
to incorrect timing or incorrect declaration.
1. ⚙️ Added `strictValidatePackageDeps` configuration, disabled by
default.
2. 🔄 In publish process, if the dependencies for the current version do
not exist, it will be interrupted.
3. 📦 In synch process, will automatically enter the queue to wait for
the next synchronization attempt (up to 3 retries).
4. ♻️ Packages that have already been published or synchronized will not
be affected.

-----------

> 在版本同步时,由于同步时机或自身依赖声明错误,导致依赖安装报错。
1. ⚙️ 新增 `strictValidatePackageDeps` 配置,默认关闭
2. 🔄 在包发布时,如果当前版本的 `dependencies` 不存在,则中断发布
3. 📦 在包同步时,如果校验未通过,则中断发布流程,自动进入队列等待下次同步(最多重试3次)
4. ♻️ 已发布、同步的包不受影响

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

- **New Features**
- Introduced strict validation for package dependencies during
publishing and synchronization.
- Added a new configuration option for enforcing dependency validation.

- **Bug Fixes**
- Enhanced error handling for dependency validation failures, allowing
for task retries.

- **Tests**
- Added new test cases to ensure proper validation of package
dependencies under strict settings.
- Created a new test suite for handling invalid dependencies in package
synchronization.

- **Chores**
- Updated logging for package synchronization processes to improve
clarity and error tracking.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-26 19:58:07 +08:00
semantic-release-bot
2b812a161e Release 3.64.0
[skip ci]

## [3.64.0](https://github.com/cnpm/cnpmcore/compare/v3.63.1...v3.64.0) (2024-10-18)

### Features

* mirror protobuf binary ([#717](https://github.com/cnpm/cnpmcore/issues/717)) ([d6f0e1d](d6f0e1d866))
2024-10-18 15:35:28 +00:00
akitaSummer
d6f0e1d866 feat: mirror protobuf binary (#717)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added support for Protocol Buffers with a new binary entry in the
configuration.

- **Tests**
- Introduced a new test case to verify the fetching of release data for
the Protocol Buffers repository from GitHub.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: shixia.ly <shixia.ly@antgroup.com>
2024-10-18 23:33:39 +08:00
semantic-release-bot
efac8a97e2 Release 3.63.1
[skip ci]

## [3.63.1](https://github.com/cnpm/cnpmcore/compare/v3.63.0...v3.63.1) (2024-10-13)

### Bug Fixes

* change skia-canvas to github release ([#715](https://github.com/cnpm/cnpmcore/issues/715)) ([99a8660](99a86600db))
2024-10-13 03:22:57 +00:00
fengmk2
99a86600db fix: change skia-canvas to github release (#715)
closes https://github.com/cnpm/cnpmcore/issues/710

pick from https://github.com/cnpm/cnpmcore/pull/712

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Updated the `binaries` configuration to include new entries and
modified existing ones, enhancing the variety and sources of available
binaries.
  
- **Bug Fixes**
- Improved the test coverage for the `GithubBinary` class with a new
test case to ensure proper fetching of `skia-canvas` release data from
GitHub.

- **Refactor**
- Removed outdated test cases related to the `skia-canvas` package from
the `NodePreGypBinary` test suite, streamlining the testing process.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: ltxhhz <ltxhhz@qq.com>
2024-10-13 11:21:31 +08:00
fengmk2
b0cd0ba387 chore: change 3.63.0.sql (#714) 2024-10-13 10:41:07 +08:00
semantic-release-bot
d987bf4a55 Release 3.63.0
[skip ci]

## [3.63.0](https://github.com/cnpm/cnpmcore/compare/v3.62.2...v3.63.0) (2024-10-13)

### Features

* proxy mode [sql changed] ([#571](https://github.com/cnpm/cnpmcore/issues/571)) ([91aea0f](91aea0f106))
2024-10-13 02:23:31 +00:00
hezhengxu2018
91aea0f106 feat: proxy mode [sql changed] (#571)
closes https://github.com/cnpm/cnpmcore/issues/366

开启代理模式时如果找不到依赖会直接返回上游仓库的manifest信息并缓存于nfs,当请求的tgz文件不存在时从上游仓库获取并返回,同时创建对应版本的同步任务。每小时检查更新已缓存的manifest文件保证上游仓库发布新版本时不会因为缓存落后而404。

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced proxy cache management for package manifests and versions.
  - Added new HTTP methods for managing proxy caches.
- Implemented scheduled workers for updating and synchronizing proxy
cache.

- **Updates**
  - Expanded `SyncMode` enum to include a new value `proxy`.
- Updated constants with `PROXY_CACHE_DIR_NAME` and
`ABBREVIATED_META_TYPE`.

- **Tests**
- Added comprehensive test cases for `ProxyCacheService`,
`ProxyCacheRepository`, and related controllers.
- Verified functionality of scheduled workers for proxy cache updates
and synchronization.
- Enhanced testing coverage for handling package downloads in proxy
mode.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: fengmk2 <suqian.yf@antgroup.com>
2024-10-13 10:21:57 +08:00
fengmk2
75d3a66b5c chore: brew install mysql v9 (#713)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Documentation**
- Updated setup instructions to reflect the requirement for MySQL 9,
removing references to MySQL 5.7 and 8.
- Removed outdated troubleshooting information related to MySQL 8
authentication issues.

- **Chores**
- Updated `.gitignore` to include `.egg/` and improved formatting for
`.DS_Store`.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-12 09:48:46 +08:00
semantic-release-bot
44ca113931 Release 3.62.2
[skip ci]

## [3.62.2](https://github.com/cnpm/cnpmcore/compare/v3.62.1...v3.62.2) (2024-10-10)

### Bug Fixes

* dup clz name ([#711](https://github.com/cnpm/cnpmcore/issues/711)) ([f7c49e5](f7c49e55fa))
2024-10-10 11:18:41 +00:00
elrrrrrrr
f7c49e55fa fix: dup clz name (#711)
> https://github.com/eggjs/tegg/pull/242/files 
* 🤖 Updated the duplicate class name, fix the error when dev.
------
> https://github.com/eggjs/tegg/pull/242/files 
* 🤖 修改同名 class 名称定义,修复本地启动报错 

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Enhanced clarity in event handling by renaming various package-related
event classes for better understanding.

- **Bug Fixes**
- Updated test cases to reflect the renamed event classes, ensuring
accurate functionality in the testing framework.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-10-10 17:04:59 +08:00
semantic-release-bot
e6ed2215a4 Release 3.62.1
[skip ci]

## [3.62.1](https://github.com/cnpm/cnpmcore/compare/v3.62.0...v3.62.1) (2024-09-25)

### Reverts

* Revert "feat: set allowH2 to true and require Node.js >= 18 (#705)" (#707) ([526b66a](526b66a93c)), closes [#705](https://github.com/cnpm/cnpmcore/issues/705) [#707](https://github.com/cnpm/cnpmcore/issues/707)
2024-09-25 10:37:30 +00:00
fengmk2
526b66a93c Revert "feat: set allowH2 to true and require Node.js >= 18 (#705)" (#707)
This reverts commit 9a7994090b.


![image](https://github.com/user-attachments/assets/eeb9ea95-60ec-4bcf-a695-60be303e2f5f)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
	- Updated HTTP client configuration to enhance compatibility.
	- Adjusted minimum Node.js version requirement for broader support.

- **Bug Fixes**
	- Removed HTTP/2 support from the HTTP client configuration.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-09-25 18:36:04 +08:00
semantic-release-bot
0a0c4e72ae Release 3.62.0
[skip ci]

## [3.62.0](https://github.com/cnpm/cnpmcore/compare/v3.61.6...v3.62.0) (2024-09-22)

### Features

* set allowH2 to true and require Node.js >= 18 ([#705](https://github.com/cnpm/cnpmcore/issues/705)) ([9a79940](9a7994090b))
2024-09-22 15:49:18 +00:00
fengmk2
9a7994090b feat: set allowH2 to true and require Node.js >= 18 (#705)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a new configuration option allowing the use of HTTP/2 in
the HTTP client.
  
- **Updates**
- Updated the minimum required Node.js version to 18.20.0 for improved
performance and compatibility.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-09-22 23:47:37 +08:00
semantic-release-bot
c1de249445 Release 3.61.6
[skip ci]

## [3.61.6](https://github.com/cnpm/cnpmcore/compare/v3.61.5...v3.61.6) (2024-07-11)

### Bug Fixes

* ignore hook when disable ([#702](https://github.com/cnpm/cnpmcore/issues/702)) ([bd49917](bd49917b86))
2024-07-11 13:28:17 +00:00
elrrrrrrr
bd49917b86 fix: ignore hook when disable (#702)
> Currently, `triggerHookWorkerMaxConcurrentTasks` is 10 by defualt,
which can lead to some redis queries even hookEnable is not activated.
* ♻️ Follow `CreateTriggerHookWorker`, when hookEnable is not activated,
do not query task queue.
-------

> 目前 triggerHookWorkerMaxConcurrentTasks 默认为 10,在未开启 hookEnable
时会带来一些冗余的 redis 查询
* ♻️ 参照 `CreateTriggerHookWorker` 逻辑,hookEnable 关闭时,不进行存量任务轮训

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Added a check to ensure hooks are enabled before proceeding, improving
reliability and preventing errors when hooks are disabled.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-07-11 21:26:52 +08:00
semantic-release-bot
381a10cd6e Release 3.61.5
[skip ci]

## [3.61.5](https://github.com/cnpm/cnpmcore/compare/v3.61.4...v3.61.5) (2024-06-25)

### Bug Fixes

* auto remove blocked package on sync process ([#700](https://github.com/cnpm/cnpmcore/issues/700)) ([ca6ce4e](ca6ce4e860))
2024-06-25 12:30:37 +00:00
elrrrrrrr
ca6ce4e860 fix: auto remove blocked package on sync process (#700)
> remove es index when the target pkg has been blocked or deleted.
1.  update `SyncESPackage` logic for event register
2. ♻️ do the block check when `syncPackage` 
---------
> 包删除或被 block 时,es 索引同步删除
1.  修改 `SyncESPackage` 逻辑,更新事件注册
2. ♻️ `syncPackage` 时,重新判断包是否被 block,用于清理

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced event types `PACKAGE_BLOCKED` and `PACKAGE_UNBLOCKED` for
better package management.

- **Bug Fixes**
- Improved logic to handle package blocks before syncing, ensuring
smoother operations.

- **Tests**
- Added test scenarios to cover new package blocking and unblocking
features, enhancing reliability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-06-25 20:28:55 +08:00
semantic-release-bot
571d265065 Release 3.61.4
[skip ci]

## [3.61.4](https://github.com/cnpm/cnpmcore/compare/v3.61.3...v3.61.4) (2024-06-14)

### Bug Fixes

* perf diff ([#699](https://github.com/cnpm/cnpmcore/issues/699)) ([753e519](753e519f17)), closes [#698](https://github.com/cnpm/cnpmcore/issues/698)
2024-06-14 01:43:06 +00:00
elrrrrrrr
753e519f17 fix: perf diff (#699)
> optimize binary sync perf , closes #698 
* ♻️ calculate the latestItem only once for the same fetchItems.
--------
> binary 同步性能优化,close #698

* ♻️ binary 最新版本比对时,相同 fetchItems 仅计算一次。

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Improved logic for identifying the latest item in synchronization
processes, ensuring more accurate date comparisons.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-06-14 09:41:43 +08:00
elrrrrrrr
038736dd60 chore: trends api (#697)
* 📚 add doc for the trends API.
---------
* 📚 添加 trends api 相关文档


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Documentation**
- Added sections for `[Token]` and `[Trend]` in the Registry API
documentation.
- Introduced a new endpoint `GET /downloads/range/:start::end/:pkgName`
for fetching package download trends.
	- Included sample response data for the `Trend` endpoint.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-06-03 22:51:46 +08:00
semantic-release-bot
317e24da55 Release 3.61.3
[skip ci]

## [3.61.3](https://github.com/cnpm/cnpmcore/compare/v3.61.2...v3.61.3) (2024-05-30)

### Bug Fixes

* force detect unpkg-white-list ([#695](https://github.com/cnpm/cnpmcore/issues/695)) ([9664504](9664504151))
2024-05-30 06:22:04 +00:00
fengmk2
9664504151 fix: force detect unpkg-white-list (#695) 2024-05-30 14:20:48 +08:00
semantic-release-bot
dcc5509dac Release 3.61.2
[skip ci]

## [3.61.2](https://github.com/cnpm/cnpmcore/compare/v3.61.1...v3.61.2) (2024-05-30)

### Bug Fixes

* support + wildcast ([#694](https://github.com/cnpm/cnpmcore/issues/694)) ([c8f5ee8](c8f5ee82f1)), closes [#692](https://github.com/cnpm/cnpmcore/issues/692)
2024-05-30 03:53:37 +00:00
elrrrrrrr
c8f5ee82f1 fix: support + wildcast (#694)
> Update #692, Declare compatibility using + notation

1. 🤖 Ensure version matching by `semver`
---------

> 更新 #692 , 兼容版本声明为 `+` 的场景
1. 🤖 统一通过 `semver` 进行版本匹配判断

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added support for prerelease versions when checking package version
compatibility.

- **Tests**
- Updated test cases to include operations related to a new object `baz`
with version `*`.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-30 11:51:56 +08:00
semantic-release-bot
cacf5e9da3 Release 3.61.1
[skip ci]

## [3.61.1](https://github.com/cnpm/cnpmcore/compare/v3.61.0...v3.61.1) (2024-05-30)

### Bug Fixes

* rc version should match `*` version ([#692](https://github.com/cnpm/cnpmcore/issues/692)) ([0b62238](0b6223882e))
2024-05-30 01:35:17 +00:00
fengmk2
0b6223882e fix: rc version should match * version (#692)
closes https://github.com/cnpm/unpkg-white-list/issues/63

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved package version checking to support wildcard (`*`) versions,
ensuring better compatibility and flexibility.
- Fixed issues in handling release candidate (rc) versions in package
version checks.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-30 09:33:51 +08:00
semantic-release-bot
9beaf4164c Release 3.61.0
[skip ci]

## [3.61.0](https://github.com/cnpm/cnpmcore/compare/v3.60.0...v3.61.0) (2024-05-25)

### Features

* sync missing acceptDependencies on abbreviated format ([#691](https://github.com/cnpm/cnpmcore/issues/691)) ([96648fd](96648fddaf))
2024-05-25 06:59:41 +00:00
fengmk2
96648fddaf feat: sync missing acceptDependencies on abbreviated format (#691)
closes https://github.com/cnpm/cnpmcore/issues/689

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added support for `acceptDependencies`, `directories`, and `funding`
metadata in package management.

- **Bug Fixes**
  - Improved package metadata synchronization to include new keys.

- **Tests**
- Added new test cases to verify `acceptDependencies` metadata syncing.

- **Chores**
- Updated import statements to use the `node` namespace for better
compatibility.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-25 14:57:59 +08:00
semantic-release-bot
6f9f8abc16 Release 3.60.0
[skip ci]

## [3.60.0](https://github.com/cnpm/cnpmcore/compare/v3.59.1...v3.60.0) (2024-05-24)

### Features

* mirror homebrew-macos-cross-toolchains ([#690](https://github.com/cnpm/cnpmcore/issues/690)) ([a247065](a2470650d5))
2024-05-24 02:57:17 +00:00
fengmk2
a2470650d5 feat: mirror homebrew-macos-cross-toolchains (#690)
https://github.com/messense/homebrew-macos-cross-toolchains/releases

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added support for `homebrew-macos-cross-toolchains` in the binaries
list, enhancing cross-toolchain capabilities for macOS users.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-24 10:55:27 +08:00
semantic-release-bot
8b1f526966 Release 3.59.1
[skip ci]

## [3.59.1](https://github.com/cnpm/cnpmcore/compare/v3.59.0...v3.59.1) (2024-05-19)

### Bug Fixes

* files meta only return currrent files and sub directory ([#687](https://github.com/cnpm/cnpmcore/issues/687)) ([e442580](e442580b81))
2024-05-19 02:00:04 +00:00
fengmk2
e442580b81 fix: files meta only return currrent files and sub directory (#687)
closes https://github.com/cnpm/cnpmcore/issues/680

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved file and directory handling to ensure accurate listing and
filtering of package version files.

- **Tests**
- Updated test cases to reflect the new logic for file and directory
handling, ensuring more reliable and accurate test results.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-19 09:58:14 +08:00
semantic-release-bot
8a927fcc2d Release 3.59.0
[skip ci]

## [3.59.0](https://github.com/cnpm/cnpmcore/compare/v3.58.1...v3.59.0) (2024-05-18)

### Features

* add unpkg-white-list to detect sync unpkg files or not ([#686](https://github.com/cnpm/cnpmcore/issues/686)) ([0530116](05301166a2))
2024-05-18 05:13:07 +00:00
fengmk2
05301166a2 feat: add unpkg-white-list to detect sync unpkg files or not (#686)
see https://github.com/cnpm/unpkg-white-list

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit


- **New Features**
- Introduced a new configuration option `enableSyncUnpkgFilesWhiteList`
to enhance package version file synchronization.

- **Improvements**
- Enhanced logging in package version file operations for better
traceability.
- Simplified file redirection logic for improved performance and
readability.

- **Tests**
- Added test cases for the new `enableSyncUnpkgFilesWhiteList`
configuration to ensure reliability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-18 13:11:22 +08:00
fengmk2
c5c6145fda chore: update contributors
[skip ci]
2024-05-17 22:32:11 +08:00
semantic-release-bot
3383d7f403 Release 3.58.1
[skip ci]

## [3.58.1](https://github.com/cnpm/cnpmcore/compare/v3.58.0...v3.58.1) (2024-05-17)

### Bug Fixes

* remove CVE-2023-46809 revert config ([#683](https://github.com/cnpm/cnpmcore/issues/683)) ([ff00e42](ff00e42668))
2024-05-17 14:29:38 +00:00
fengmk2
ff00e42668 fix: remove CVE-2023-46809 revert config (#683)
revert https://github.com/cnpm/cnpmcore/pull/650

run test on Node.js 22

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Chores**
  - Updated Node.js version support to 18, 20, and 22.
  - Removed unnecessary configuration from the release workflow.
- Cleaned up outdated security-related entries in the package
configuration.
  - Updated encryption functions in CryptoUtil.ts for improved security.
- Refactored import statements for ES module syntax in
webauthController.test.ts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-05-17 22:27:55 +08:00
semantic-release-bot
ade9305342 Release 3.58.0
[skip ci]

## [3.58.0](https://github.com/cnpm/cnpmcore/compare/v3.57.0...v3.58.0) (2024-05-16)

### Features

* support unpkg alias path access entry file [#674](https://github.com/cnpm/cnpmcore/issues/674) ([#675](https://github.com/cnpm/cnpmcore/issues/675)) ([a51891d](a51891d3b9))
2024-05-16 15:48:28 +00:00
chilingling
a51891d3b9 feat: support unpkg alias path access entry file #674 (#675)
closes https://github.com/cnpm/cnpmcore/issues/674

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Implemented a feature to handle compatibility with unpkg by searching
for and redirecting to possible file entries if the requested file is
not found.

- **Tests**
- Added a new test case to ensure the redirection to possible file
entries functions correctly.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-16 23:46:51 +08:00
elrrrrrrr
65d6f4489f chore: add sync-setup (#666)
> close #664 Missing a startup document for sync pkgs.
* 📚 Add doc for sync-setup

-------

> close #664 目前缺少一个启动文档,开启包同步服务
* 📚 添加 sync-setup 使用文档

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Documentation**
- Added a new guide for setting up package synchronization services
using `cnpmcore`.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-16 13:26:33 +08:00
semantic-release-bot
8366ee70a4 Release 3.57.0
[skip ci]

## [3.57.0](https://github.com/cnpm/cnpmcore/compare/v3.56.2...v3.57.0) (2024-05-14)

### Features

* update playwright config ([#681](https://github.com/cnpm/cnpmcore/issues/681)) ([6bfbe35](6bfbe35c65))
2024-05-14 16:42:10 +00:00
elrrrrrrr
6bfbe35c65 feat: update playwright config (#681)
> follow
[ref](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/registry/index.ts)
update
* 🔧 Modify Playwright `DOWNLOAD_PATHS` configuration 
* 🔧 Add `android` related configuration 
* ♻️ Remove `chromium-with-symbols` configuration and `ubuntu18.04`
related versions

---------

> 参照
[ref](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/registry/index.ts)
变更
* 🔧 修改 playwright `DOWNLOAD_PATHS` 相关配置
* 🔧 添加 `android` 相关配置
* ♻️ 移除 `chromium-with-symbols` 配置,`ubuntu18.04` 相关版本




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added support for `mac14` and `mac14-arm64` platforms for Chromium and
Chromium tip-of-tree downloads.

- **Bug Fixes**
- Updated download paths for various platforms to ensure compatibility
and correct file retrieval.

- **Tests**
- Adjusted test assertions to reflect changes in download paths and
platform support, ensuring accurate validation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-15 00:40:37 +08:00
semantic-release-bot
cedb959f65 Release 3.56.2
[skip ci]

## [3.56.2](https://github.com/cnpm/cnpmcore/compare/v3.56.1...v3.56.2) (2024-05-14)

### Bug Fixes

* allow to disable sync unpkg files ([#679](https://github.com/cnpm/cnpmcore/issues/679)) ([101c9b3](101c9b30b5))
2024-05-14 06:47:57 +00:00
fengmk2
101c9b30b5 fix: allow to disable sync unpkg files (#679)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added a new configuration option `enableSyncUnpkgFiles` for enhanced
synchronization control.
  
- **Improvements**
- Improved synchronization logic to check both `enableUnpkg` and
`enableSyncUnpkgFiles` settings before proceeding.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-14 14:46:26 +08:00
semantic-release-bot
cdca770a0b Release 3.56.1
[skip ci]

## [3.56.1](https://github.com/cnpm/cnpmcore/compare/v3.56.0...v3.56.1) (2024-05-14)

### Bug Fixes

* only set err.status on statusCode >= 200 ([#677](https://github.com/cnpm/cnpmcore/issues/677)) ([668eed2](668eed2d50))
2024-05-14 02:30:02 +00:00
fengmk2
668eed2d50 fix: only set err.status on statusCode >= 200 (#677)
> nodejs.AssertionError: invalid status code: -1

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved error handling to ensure the HTTP status code is valid and
within the correct range.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-14 10:28:31 +08:00
semantic-release-bot
dbf5b5248a Release 3.56.0
[skip ci]

## [3.56.0](https://github.com/cnpm/cnpmcore/compare/v3.55.1...v3.56.0) (2024-05-11)

### Features

* mirror edgewebdriver ([#676](https://github.com/cnpm/cnpmcore/issues/676)) ([21cbc18](21cbc1849f))
2024-05-11 10:11:05 +00:00
fengmk2
21cbc1849f feat: mirror edgewebdriver (#676)
closes https://github.com/cnpm/cnpmcore/issues/594

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced the EdgedriverBinary class to enhance the fetching and
management of Edgedriver binaries.
- Added support for the Edgedriver type in our binary management system.
- Updated the binaries configuration to include the new Edgedriver
category, complete with repository and distribution details.

- **Documentation**
- Updated documentation to reflect the addition of the Edgedriver
category and its functionalities.

- **Tests**
- Implemented new tests for the EdgedriverBinary class to ensure
functionality works as expected.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-05-11 18:09:27 +08:00
semantic-release-bot
67f1a2476d Release 3.55.1
[skip ci]

## [3.55.1](https://github.com/cnpm/cnpmcore/compare/v3.55.0...v3.55.1) (2024-04-17)

### Bug Fixes

* add logs fro aggregate errors ([#672](https://github.com/cnpm/cnpmcore/issues/672)) ([c0f96d7](c0f96d72e5))
2024-04-17 13:18:19 +00:00
elrrrrrrr
c0f96d72e5 fix: add logs fro aggregate errors (#672)
> add logs for sync tasks with the upstream registry errors
[ref](https://cdn.npmmirror.com/packages/%40eggjs/tegg-schedule-plugin/syncs/2024/04/171901-661fac1a613c4b7bd1e015e3.log)
* 📒 Log the corresponding errors for AggregateError

--------------

> 对 upstream registry 创建同步任务失败时,添加日志信息
[ref](https://cdn.npmmirror.com/packages/%40eggjs/tegg-schedule-plugin/syncs/2024/04/171901-661fac1a613c4b7bd1e015e3.log)
* 📒 针对 AggregateError 打印对应 errors



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit


- **Documentation**
- Updated comments in the package synchronization service to include
information about handling `AggregateError` exceptions.
- **New Features**
- Added a retry mechanism with a limit of 3 attempts in the HTTP
requests for the NPMRegistry class.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-04-17 21:16:26 +08:00
semantic-release-bot
abad15b8e0 Release 3.55.0
[skip ci]

## [3.55.0](https://github.com/cnpm/cnpmcore/compare/v3.54.0...v3.55.0) (2024-04-15)

### Features

* use mysql2 ([#671](https://github.com/cnpm/cnpmcore/issues/671)) ([58d19b1](58d19b17f0))
2024-04-15 06:24:32 +00:00
fengmk2
58d19b17f0 feat: use mysql2 (#671)
https://github.com/cyjake/leoric/pull/419

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Refactor**
- Updated the database management system to enhance performance and
compatibility.
- **Chores**
	- Upgraded internal libraries to improve stability and security.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-04-15 14:22:53 +08:00
semantic-release-bot
b94c8efd6c Release 3.54.0
[skip ci]

## [3.54.0](https://github.com/cnpm/cnpmcore/compare/v3.53.4...v3.54.0) (2024-04-13)

### Features

* use mysql2@^3.9.4 ([#669](https://github.com/cnpm/cnpmcore/issues/669)) ([468f9e4](468f9e4e36))

### Bug Fixes

* remove unuse mysql2 deps ([#670](https://github.com/cnpm/cnpmcore/issues/670)) ([c71d185](c71d185ee1))
2024-04-13 14:24:37 +00:00
fengmk2
c71d185ee1 fix: remove unuse mysql2 deps (#670)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Chores**
	- Removed the `mysql2` dependency from the project.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-04-13 22:23:12 +08:00
fengmk2
468f9e4e36 feat: use mysql2@^3.9.4 (#669)
closes https://github.com/cnpm/cnpmcore/issues/668

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit


- **Chores**
- Updated the database management package to enhance performance and
security.
	- Adjusted supported Node.js versions in the workflow configuration.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-04-13 21:51:16 +08:00
semantic-release-bot
ebc212c1c4 Release 3.53.4
[skip ci]

## [3.53.4](https://github.com/cnpm/cnpmcore/compare/v3.53.3...v3.53.4) (2024-03-28)

### Bug Fixes

* sync cypress linux-arm64 binary ([#662](https://github.com/cnpm/cnpmcore/issues/662)) ([049b186](049b186a0e))
2024-03-28 05:21:01 +00:00
fengmk2
049b186a0e fix: sync cypress linux-arm64 binary (#662)
closes https://github.com/cnpm/cnpmjs.org/issues/1560

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Added support for the `linux-arm64` platform in Cypress binary
handling.
- **Tests**
- Updated tests to include assertions for the newly supported
`linux-arm64` platform.
- **Chores**
- Updated Node.js version to 21 in workflow configuration for improved
job execution.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-03-28 13:19:44 +08:00
semantic-release-bot
6664189a91 Release 3.53.3
[skip ci]

## [3.53.3](https://github.com/cnpm/cnpmcore/compare/v3.53.2...v3.53.3) (2024-02-23)

### Bug Fixes

* dont set empty string authorization header ([#654](https://github.com/cnpm/cnpmcore/issues/654)) ([64bb78c](64bb78cf8a))
2024-02-23 17:48:31 +00:00
fengmk2
64bb78cf8a fix: dont set empty string authorization header (#654)
https://github.com/cnpm/cnpmcore/issues/652
2024-02-24 01:47:20 +08:00
semantic-release-bot
f7d9d49b4c Release 3.53.2
[skip ci]

## [3.53.2](https://github.com/cnpm/cnpmcore/compare/v3.53.1...v3.53.2) (2024-02-23)

### Bug Fixes

* only set chrome-for-testing data timestamp after sync task finish ([#653](https://github.com/cnpm/cnpmcore/issues/653)) ([4bc0c9c](4bc0c9ca59))
2024-02-23 17:18:58 +00:00
fengmk2
4bc0c9ca59 fix: only set chrome-for-testing data timestamp after sync task finish (#653)
closes https://github.com/cnpm/cnpmcore/issues/652
2024-02-24 01:17:48 +08:00
hljwkwm
ae83136e62 docs: add custom time zone example (#651)
使用 Docker 打出的镜像,默认会使用0区时区,会影响到日志、数据库等时间的显示,考虑到全球化,可在镜像运行时,
设置环境变量 `TZ` 即可解决该问题,TZ 列表可参考:[https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List)。
2024-02-21 21:48:28 +08:00
semantic-release-bot
6b4f9af947 Release 3.53.1
[skip ci]

## [3.53.1](https://github.com/cnpm/cnpmcore/compare/v3.53.0...v3.53.1) (2024-02-20)

### Bug Fixes

* fix breaking change about RSA_PKCS1_PADDING ([#650](https://github.com/cnpm/cnpmcore/issues/650)) ([a6737e6](a6737e6150))
2024-02-20 14:10:53 +00:00
hljwkwm
a6737e6150 fix: fix breaking change about RSA_PKCS1_PADDING (#650)
Node.JS 安全性修复导致 RSA_PKCS1_PADDING 不可用,会报出以下错误:

> RSA_PKCS1_PADDING is no longer supported for private decryption, this
can be reverted with --security-revert=CVE-2023-46809。

参考链接:https://www.eggjs.org/zh-CN/core/security#revert-cve
2024-02-20 22:09:44 +08:00
semantic-release-bot
2ec6bd94b2 Release 3.53.0
[skip ci]

## [3.53.0](https://github.com/cnpm/cnpmcore/compare/v3.52.0...v3.53.0) (2024-01-27)

### Features

* mirror bun ([#645](https://github.com/cnpm/cnpmcore/issues/645)) ([26d2ef2](26d2ef2124))
2024-01-27 13:16:31 +00:00
fengmk2
26d2ef2124 feat: mirror bun (#645)
https://github.com/oven-sh/bun
2024-01-27 21:15:05 +08:00
semantic-release-bot
9004ce7a1c Release 3.52.0
[skip ci]

## [3.52.0](https://github.com/cnpm/cnpmcore/compare/v3.51.1...v3.52.0) (2024-01-25)

### Features

* mirror pkg-fetch from yao-pkg/pkg-fetch ([#644](https://github.com/cnpm/cnpmcore/issues/644)) ([6e32679](6e326790c4))
2024-01-25 10:10:11 +00:00
unbyte
6e326790c4 feat: mirror pkg-fetch from yao-pkg/pkg-fetch (#644)
**what:** pkg-fetch provides prebuilt node binaries used by pkg.

**why from yao-pkg:**
[vercel/pkg-fetch](https://github.com/vercel/pkg-fetch) was archived,
[yao-pkg/pkg-fetch](https://github.com/yao-pkg/pkg-fetch) is currently
the most actively maintained fork.

close: https://github.com/cnpm/cnpmcore/issues/543
2024-01-25 18:08:56 +08:00
semantic-release-bot
4644c1e788 Release 3.51.1
[skip ci]

## [3.51.1](https://github.com/cnpm/cnpmcore/compare/v3.51.0...v3.51.1) (2024-01-23)

### Bug Fixes

* hooks triggers ([#641](https://github.com/cnpm/cnpmcore/issues/641)) ([838eecf](838eecff2d))
2024-01-23 07:35:16 +00:00
elrrrrrrr
838eecff2d fix: hooks triggers (#641)
> Since the eventBus#cork , version & tag events are triggered at same
time, cause the abnormal triggers of different types of hooks.
* ~~🐞 Fix triggerHook type targetName to be `tagetName:changeId`~~
* 🤖 Only merge sync tasks (binary, package) which in waiting states
--------
> 由于 `eventBus#cork` 机制,版本事件同时触发,导致不同类型 hook 触发异常
* ~~🐞 修改 triggerHook 类型 targetName 为 `包名:changeId`~~
* 🤖 仅合并 waiting 状态下包同步任务
2024-01-23 15:34:09 +08:00
semantic-release-bot
08678c70db Release 3.51.0
[skip ci]

## [3.51.0](https://github.com/cnpm/cnpmcore/compare/v3.50.0...v3.51.0) (2024-01-13)

### Features

* support GITHUB_TOKEN to increae github api request rate ([#639](https://github.com/cnpm/cnpmcore/issues/639)) ([0f7aa4a](0f7aa4a50f))
2024-01-13 05:49:31 +00:00
fengmk2
0f7aa4a50f feat: support GITHUB_TOKEN to increae github api request rate (#639) 2024-01-13 13:48:10 +08:00
semantic-release-bot
05445b49c3 Release 3.50.0
[skip ci]

## [3.50.0](https://github.com/cnpm/cnpmcore/compare/v3.49.5...v3.50.0) (2024-01-13)

### Features

* mirror ant-design-charts releases ([#638](https://github.com/cnpm/cnpmcore/issues/638)) ([ad86be3](ad86be312e))
2024-01-13 05:11:12 +00:00
fengmk2
ad86be312e feat: mirror ant-design-charts releases (#638)
closes https://github.com/cnpm/cnpmcore/issues/635
2024-01-13 13:09:42 +08:00
windhc
039a56f471 chore: add registry api doc (#636)
closes https://github.com/cnpm/cnpmcore/issues/630
2024-01-13 12:52:10 +08:00
semantic-release-bot
3310b0e435 Release 3.49.5
[skip ci]

## [3.49.5](https://github.com/cnpm/cnpmcore/compare/v3.49.4...v3.49.5) (2024-01-06)

### Bug Fixes

* read @prisma/engines-version from dependencies ([#633](https://github.com/cnpm/cnpmcore/issues/633)) ([a009668](a0096685fc))
2024-01-06 13:28:01 +00:00
fengmk2
a0096685fc fix: read @prisma/engines-version from dependencies (#633)
closes https://github.com/cnpm/cnpm/issues/436
2024-01-06 21:26:59 +08:00
semantic-release-bot
b0e0a2d464 Release 3.49.4
[skip ci]

## [3.49.4](https://github.com/cnpm/cnpmcore/compare/v3.49.3...v3.49.4) (2024-01-06)

### Bug Fixes

* github file size limit increased to 250mb ([#632](https://github.com/cnpm/cnpmcore/issues/632)) ([d5bf9ce](d5bf9ceb1b))
2024-01-06 13:10:36 +00:00
fengmk2
d5bf9ceb1b fix: github file size limit increased to 250mb (#632)
closes https://github.com/cnpm/cnpm/issues/435
2024-01-06 21:08:41 +08:00
semantic-release-bot
10b97c8697 Release 3.49.3
[skip ci]

## [3.49.3](https://github.com/cnpm/cnpmcore/compare/v3.49.2...v3.49.3) (2023-12-25)

### Bug Fixes

* unpkg lock ([#629](https://github.com/cnpm/cnpmcore/issues/629)) ([5a8a4eb](5a8a4eb10c))
2023-12-25 03:07:09 +00:00
elrrrrrrr
5a8a4eb10c fix: unpkg lock (#629)
> When accessing the unpkg service, when the packages have not yet been
synchronized, will lead to multiple synchronization attempts
concurrently causing db insert errors.
* 🔒 Added a Redis lock for the `ensurePackageVersionFilesSync` function,
with a default timeout of 60 seconds.
* 🥸 Admin PUT requests and the package version auto sync process are not
restricted by this.

> 当访问 unpkg 服务时,如果访问存量未同步的包,可能导致多次同步并发报错
* 🔒 为 ensurePackageVersionFilesSync 添加 redis 锁,默认超时 60s
* 🥸 管理员手动 PUT 请求和包同步流程不受限制
2023-12-25 11:05:43 +08:00
semantic-release-bot
7e176f2f42 Release 3.49.2
[skip ci]

## [3.49.2](https://github.com/cnpm/cnpmcore/compare/v3.49.1...v3.49.2) (2023-12-19)

### Bug Fixes

* ignore db query error default logger ([#626](https://github.com/cnpm/cnpmcore/issues/626)) ([6cc2f2d](6cc2f2d830))
* should search default packages when text is empty ([#623](https://github.com/cnpm/cnpmcore/issues/623)) ([0c4a52d](0c4a52d220))
2023-12-19 09:17:04 +00:00
fengmk2
6cc2f2d830 fix: ignore db query error default logger (#626) 2023-12-19 17:15:55 +08:00
semantic-release-bot
dddb10e510 Release 3.49.2
[skip ci]

## [3.49.2](https://github.com/cnpm/cnpmcore/compare/v3.49.1...v3.49.2) (2023-12-18)

### Bug Fixes

* should search default packages when text is empty ([#623](https://github.com/cnpm/cnpmcore/issues/623)) ([0c4a52d](0c4a52d220))
2023-12-18 12:01:55 +00:00
Beace
0c4a52d220 fix: should search default packages when text is empty (#623)
当搜索关键词为空时,不进行 ES 关键词搜索拼接,返回一个默认的全量数据排名。 

close https://github.com/cnpm/cnpmcore/issues/622
2023-12-18 20:00:52 +08:00
semantic-release-bot
c3e481c5c4 Release 3.49.1
[skip ci]

## [3.49.1](https://github.com/cnpm/cnpmcore/compare/v3.49.0...v3.49.1) (2023-12-18)

### Bug Fixes

* use tar fork version to fix memory leak ([#625](https://github.com/cnpm/cnpmcore/issues/625)) ([6c519f7](6c519f73ce))
2023-12-18 03:04:36 +00:00
fengmk2
6c519f73ce fix: use tar fork version to fix memory leak (#625)
closes https://github.com/cnpm/cnpmcore/issues/624
2023-12-18 11:03:32 +08:00
semantic-release-bot
87ca86f1db Release 3.49.0
[skip ci]

## [3.49.0](https://github.com/cnpm/cnpmcore/compare/v3.48.4...v3.49.0) (2023-12-12)

### Features

* use egg-cors@3 ([#620](https://github.com/cnpm/cnpmcore/issues/620)) ([fcca3c3](fcca3c30ce))
2023-12-12 01:31:42 +00:00
fengmk2
fcca3c30ce feat: use egg-cors@3 (#620)
https://github.com/eggjs/egg-cors/pull/27
2023-12-12 09:08:33 +08:00
semantic-release-bot
37b50842fd Release 3.48.4
[skip ci]

## [3.48.4](https://github.com/cnpm/cnpmcore/compare/v3.48.3...v3.48.4) (2023-12-07)

### Bug Fixes

* incorrect latest tag in strict specific version ([#610](https://github.com/cnpm/cnpmcore/issues/610)) ([acfd667](acfd66748f))
2023-12-07 12:48:52 +00:00
elrrrrrrr
e62fa26788 chore: ts (#619)
* 锁定 typescript 依赖至 5.2.2 解决构建报错
2023-12-07 20:47:31 +08:00
semantic-release-bot
64dfcb35a4 Release 3.48.4
[skip ci]

## [3.48.4](https://github.com/cnpm/cnpmcore/compare/v3.48.3...v3.48.4) (2023-11-28)

### Bug Fixes

* incorrect latest tag in strict specific version ([#610](https://github.com/cnpm/cnpmcore/issues/610)) ([acfd667](acfd66748f))
2023-11-28 14:48:48 +00:00
hezhengxu2018
acfd66748f fix: incorrect latest tag in strict specific version (#610)
1. 添加新建任务时的重复版本校验,去除执行任务时的版本去重
2.
在 strictSyncSpecificVersion 开启时,修改为从可用版本中选择 latestTag。之前从任务的指定版本中生成 latestTag 逻辑不正确
2023-11-28 22:47:34 +08:00
semantic-release-bot
072e146e5b Release 3.48.3
[skip ci]

## [3.48.3](https://github.com/cnpm/cnpmcore/compare/v3.48.2...v3.48.3) (2023-11-06)

### Bug Fixes

* es query script score syntax fix and add error handler for 404 error ([#607](https://github.com/cnpm/cnpmcore/issues/607)) ([8e1f4ca](8e1f4ca880))
2023-11-06 06:29:09 +00:00
Beace
8e1f4ca880 fix: es query script score syntax fix and add error handler for 404 error (#607)
closes https://github.com/cnpm/cnpmcore/issues/598
2023-11-06 14:27:32 +08:00
semantic-release-bot
603bb82b1f Release 3.48.2
[skip ci]

## [3.48.2](https://github.com/cnpm/cnpmcore/compare/v3.48.1...v3.48.2) (2023-11-03)

### Bug Fixes

* should set OPTIONS on access-control-allow-methods ([#608](https://github.com/cnpm/cnpmcore/issues/608)) ([0179ef3](0179ef364a))
2023-11-03 11:13:45 +00:00
fengmk2
0179ef364a fix: should set OPTIONS on access-control-allow-methods (#608)
Access to fetch at
'https://registry.npmmirror.com/isstream/-/isstream-0.1.0.tgz' from
origin 'https://foo.com' has been blocked by CORS policy: Method OPTIONS
is not allowed by Access-Control-Allow-Methods in preflight response.
2023-11-03 19:12:25 +08:00
semantic-release-bot
f03d48e511 Release 3.48.1
[skip ci]

## [3.48.1](https://github.com/cnpm/cnpmcore/compare/v3.48.0...v3.48.1) (2023-11-03)

### Bug Fixes

* should set access-control-allow-origin and headers ([#606](https://github.com/cnpm/cnpmcore/issues/606)) ([18ef7f4](18ef7f49af))
2023-11-03 10:53:27 +00:00
fengmk2
18ef7f49af fix: should set access-control-allow-origin and headers (#606) 2023-11-03 18:52:08 +08:00
semantic-release-bot
9ea70088fb Release 3.48.0
[skip ci]

## [3.48.0](https://github.com/cnpm/cnpmcore/compare/v3.47.2...v3.48.0) (2023-11-03)

### Features

* allow OPTIONS request on tgz downlaod url ([#605](https://github.com/cnpm/cnpmcore/issues/605)) ([5bedb25](5bedb25f9d))
2023-11-03 10:24:08 +00:00
fengmk2
5bedb25f9d feat: allow OPTIONS request on tgz downlaod url (#605)
make webcontainer can run npm install on cnpmcore registry
2023-11-03 18:22:57 +08:00
semantic-release-bot
31946ba10e Release 3.47.2
[skip ci]

## [3.47.2](https://github.com/cnpm/cnpmcore/compare/v3.47.1...v3.47.2) (2023-10-28)

### Bug Fixes

* ignore BodyTimeoutError ([#603](https://github.com/cnpm/cnpmcore/issues/603)) ([cde4f03](cde4f03c30))
2023-10-28 14:29:05 +00:00
fengmk2
cde4f03c30 fix: ignore BodyTimeoutError (#603) 2023-10-28 22:27:42 +08:00
semantic-release-bot
c3c7b391c0 Release 3.47.1
[skip ci]

## [3.47.1](https://github.com/cnpm/cnpmcore/compare/v3.47.0...v3.47.1) (2023-10-26)

### Bug Fixes

* ignore HttpClientRequestTimeoutError on change stream worker ([#601](https://github.com/cnpm/cnpmcore/issues/601)) ([0791769](079176926d))
2023-10-26 09:43:17 +00:00
fengmk2
079176926d fix: ignore HttpClientRequestTimeoutError on change stream worker (#601) 2023-10-26 17:41:59 +08:00
semantic-release-bot
e01d39ef4e Release 3.47.0
[skip ci]

## [3.47.0](https://github.com/cnpm/cnpmcore/compare/v3.46.0...v3.47.0) (2023-10-26)

### Features

* ignore network error to error log ([#600](https://github.com/cnpm/cnpmcore/issues/600)) ([22d401e](22d401ee1f))
2023-10-26 07:39:36 +00:00
fengmk2
22d401ee1f feat: ignore network error to error log (#600) 2023-10-26 15:38:10 +08:00
killa
3cdb7cc9df mirrors: add fuse-t (#599) 2023-10-25 12:59:09 +08:00
semantic-release-bot
5ad775e411 Release 3.46.0
[skip ci]

## [3.46.0](https://github.com/cnpm/cnpmcore/compare/v3.45.1...v3.46.0) (2023-10-11)

### Features

* read remote auth token from database ([#595](https://github.com/cnpm/cnpmcore/issues/595)) ([707a1d3](707a1d3809))
2023-10-11 14:26:40 +00:00
hezhengxu2018
707a1d3809 feat: read remote auth token from database (#595)
closes https://github.com/cnpm/cnpmcore/issues/586
2023-10-11 09:25:17 -05:00
semantic-release-bot
9fcbb00406 Release 3.45.1
[skip ci]

## [3.45.1](https://github.com/cnpm/cnpmcore/compare/v3.45.0...v3.45.1) (2023-10-07)

### Bug Fixes

* use oss-cnpm@5.0.1 ([#597](https://github.com/cnpm/cnpmcore/issues/597)) ([413ec56](413ec5685e))
2023-10-07 10:51:42 +00:00
fengmk2
413ec5685e fix: use oss-cnpm@5.0.1 (#597)
https://github.com/node-modules/oss-client/pull/15
2023-10-07 18:50:13 +08:00
fengmk2
f66057794e Release 3.45.0
[skip ci]
2023-10-07 13:34:36 +08:00
semantic-release-bot
9a5e8c387a Release 3.44.0
[skip ci]

## [3.44.0](https://github.com/cnpm/cnpmcore/compare/v3.43.5...v3.44.0) (2023-10-07)

### Features

* sync all crhome for test binaries ([#592](https://github.com/cnpm/cnpmcore/issues/592)) ([4596b21](4596b21271))
* use oss-client v2 ([#596](https://github.com/cnpm/cnpmcore/issues/596)) ([d24e3bd](d24e3bd235))
2023-10-07 04:19:41 +00:00
fengmk2
d24e3bd235 feat: use oss-client v2 (#596)
https://github.com/cnpm/oss-cnpm/pull/29
https://github.com/node-modules/oss-client/pull/12
2023-10-07 12:18:00 +08:00
semantic-release-bot
d6d72650dd Release 3.44.0
[skip ci]

## [3.44.0](https://github.com/cnpm/cnpmcore/compare/v3.43.5...v3.44.0) (2023-09-19)

### Features

* sync all crhome for test binaries ([#592](https://github.com/cnpm/cnpmcore/issues/592)) ([4596b21](4596b21271))
2023-09-19 15:34:28 +00:00
fengmk2
4596b21271 feat: sync all crhome for test binaries (#592)
closes https://github.com/cnpm/cnpmcore/issues/591
2023-09-19 23:33:03 +08:00
semantic-release-bot
c33f10e0ab Release 3.43.5
[skip ci]

## [3.43.5](https://github.com/cnpm/cnpmcore/compare/v3.43.4...v3.43.5) (2023-09-05)

### Bug Fixes

* the license may be an object ([#587](https://github.com/cnpm/cnpmcore/issues/587)) ([88b6afb](88b6afb66e)), closes [/github.com/cnpm/cnpmcore/issues/585#issuecomment-1706009496](https://github.com/cnpm//github.com/cnpm/cnpmcore/issues/585/issues/issuecomment-1706009496)
2023-09-05 10:47:03 +00:00
Beace
88b6afb66e fix: the license may be an object (#587)
有些 package 的 license 是个对象,会导致 es 写入失败
https://github.com/cnpm/cnpmcore/issues/585#issuecomment-1706009496


![image](https://github.com/cnpm/cnpmcore/assets/13284978/4343a1e8-1fa5-4aed-950d-d5038534dad8)
2023-09-05 18:45:42 +08:00
semantic-release-bot
6d156a5c96 Release 3.43.4
[skip ci]

## [3.43.4](https://github.com/cnpm/cnpmcore/compare/v3.43.3...v3.43.4) (2023-09-02)

### Bug Fixes

* add CDN cache header on search api ([#583](https://github.com/cnpm/cnpmcore/issues/583)) ([89f6b98](89f6b989c1))
2023-09-02 02:42:58 +00:00
fengmk2
89f6b989c1 fix: add CDN cache header on search api (#583) 2023-09-02 10:41:39 +08:00
semantic-release-bot
5e4d988c2f Release 3.43.3
[skip ci]

## [3.43.3](https://github.com/cnpm/cnpmcore/compare/v3.43.2...v3.43.3) (2023-09-01)

### Bug Fixes

* author display in cli ([#582](https://github.com/cnpm/cnpmcore/issues/582)) ([9b2dc41](9b2dc41134))
2023-09-01 06:52:08 +00:00
elrrrrrrr
9b2dc41134 fix: author display in cli (#582)
> Revert ineffective changes from
https://github.com/cnpm/cnpmcore/pull/581, add username index
* 🧶 Add username field to maintainers in search results, handle
uniformly when writing
* 📚 Update index documentation
-----
> 回滚 https://github.com/cnpm/cnpmcore/pull/581 无效改动,添加 username 索引
* 🧶 搜索结果中的 maintainers 添加 username 字段,写入时统一处理
* 📚 更新索引文档说明
2023-09-01 14:50:54 +08:00
semantic-release-bot
3f95d0fadd Release 3.43.2
[skip ci]

## [3.43.2](https://github.com/cnpm/cnpmcore/compare/v3.43.1...v3.43.2) (2023-09-01)

### Bug Fixes

* author info ([#581](https://github.com/cnpm/cnpmcore/issues/581)) ([6dd241d](6dd241d690))
2023-09-01 03:25:14 +00:00
elrrrrrrr
6dd241d690 fix: author info (#581)
> in npm@8 cli, empty author information after executing npm search

* 🐞 Fix: npm-cli author field is empty, need to add publisher-related
fields
* 📚 Update API documentation
-----

> npm@8 命令行,执行 npm search 后,author 信息为空
* 🐞 修复 npm-cli author 字段为空,需添加 publisher 相关字段
* 📚 更新文档接口信息


![image](https://github.com/cnpm/cnpmcore/assets/5574625/f3a91d29-0bf4-498a-ae2f-c3dd4b47b22f)
2023-09-01 11:23:34 +08:00
semantic-release-bot
868c8d305e Release 3.43.1
[skip ci]

## [3.43.1](https://github.com/cnpm/cnpmcore/compare/v3.43.0...v3.43.1) (2023-09-01)

### Bug Fixes

* use env.CNPMCORE_CONFIG_ENABLE_ES to enable ([#580](https://github.com/cnpm/cnpmcore/issues/580)) ([bcf67c4](bcf67c4cea))
2023-09-01 02:07:12 +00:00
fengmk2
bcf67c4cea fix: use env.CNPMCORE_CONFIG_ENABLE_ES to enable (#580) 2023-09-01 10:05:58 +08:00
semantic-release-bot
941b277244 Release 3.43.0
[skip ci]

## [3.43.0](https://github.com/cnpm/cnpmcore/compare/v3.42.2...v3.43.0) (2023-09-01)

### Features

* support npm search command like npmio ([#513](https://github.com/cnpm/cnpmcore/issues/513)) ([7f85848](7f858482f7))
2023-09-01 01:22:37 +00:00
Beace
7f858482f7 feat: support npm search command like npmio (#513)
- [x] 找个合适的 eggjs es 插件,或者手撸个,看社区的几个版本都比较低
- [x] HTTP Server 新增 API
`/-/v1/search?text=react&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5`,第一版不一定能
qpm 都支持,先支持现有的下载量数据,即 popularity 的参考数据
- [x] 监听相关的 metadata 变更的 event,同步写入、删除 增量的 ES 数据,ES 有较强抗压能力,这块直接做成同步就好
- [x] 考虑可能同步也会丢部分数据(stream 不稳定时),HTTP Server 再追加一个手动同步 ES 的接口,传包名同步触发写
ES 即可
- [x] 提供全量一次性的初始化同步 ES 脚本
- [x] setting/mapping 参考
https://github.com/npms-io/npms-analyzer/blob/master/config/elasticsearch/npms.json5

---------

Co-authored-by: fengmk2 <fengmk2@gmail.com>
Co-authored-by: elrrrrrrr <elrrrrrrr@gmail.com>
2023-09-01 09:21:03 +08:00
semantic-release-bot
6e45ac5a63 Release 3.42.2
[skip ci]

## [3.42.2](https://github.com/cnpm/cnpmcore/compare/v3.42.1...v3.42.2) (2023-08-31)

### Bug Fixes

* should use NodeNext on module and moduleResolution tsconfig ([#578](https://github.com/cnpm/cnpmcore/issues/578)) ([10d7a84](10d7a8499e))
2023-08-31 01:16:34 +00:00
fengmk2
10d7a8499e fix: should use NodeNext on module and moduleResolution tsconfig (#578)
https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/#module-and-moduleresolution-must-match-under-recent-node-js-settings

> Option 'moduleResolution' must be set to 'NodeNext' (or left
unspecified) when option 'module' is set to 'NodeNext'.
2023-08-31 09:15:04 +08:00
semantic-release-bot
2b2e13c01d Release 3.42.1
[skip ci]

## [3.42.1](https://github.com/cnpm/cnpmcore/compare/v3.42.0...v3.42.1) (2023-08-23)

### Bug Fixes

* default latest tag ([#575](https://github.com/cnpm/cnpmcore/issues/575)) ([ffe8fa7](ffe8fa7d19)), closes [#574](https://github.com/cnpm/cnpmcore/issues/574)
2023-08-23 06:12:39 +00:00
elrrrrrrr
ffe8fa7d19 fix: default latest tag (#575)
> closes #574 Fixed the issue where custom tags in publishConfig
prevented the default latest tag.
* 🧶 Modified the `savePackageVersion` API, automatically add latest tag
if no latest tag.
* 🧶 The publish tag parameter has been changed to tags, triggering
corresponding events in batches.
* ♻️ No changes to the package synchronization process.
------
> closes #574 修复 publishConfig 中自定义 tag,导致 latest tag 未设置的问题
* 🧶 修改 savePackageVersion 接口,如果当前包未配置 latest tag,则自动补全
* 🧶 publish tag 参数改为 tags,对应事件分批触发
* ♻️ 包同步流程不做修改
2023-08-23 14:11:23 +08:00
semantic-release-bot
39de1c7df2 Release 3.42.0
[skip ci]

## [3.42.0](https://github.com/cnpm/cnpmcore/compare/v3.41.0...v3.42.0) (2023-08-22)

### Features

* revalidate latest version ([#573](https://github.com/cnpm/cnpmcore/issues/573)) ([73b4383](73b4383f5c)), closes [#376](https://github.com/cnpm/cnpmcore/issues/376)
2023-08-22 07:42:19 +00:00
elrrrrrrr
73b4383f5c feat: revalidate latest version (#573)
> closes #376: fix issue with incomplete binary file after upstream
release or sync failure
* 🧶 Modify the binary `diff` method, adding latest version check method.
* ♻️ Perform additional comparison on the latest version, without
modifying existing data.

---------
> 修复 #376 ,兼容上游发布或同步失败后,产物同步不全的问题
* 🧶 调整 binary `diff` 方法,添加最新版本校验逻辑
* ♻️ 对最新版本的子目录进行额外比对,存量数据不做修改
2023-08-22 15:41:04 +08:00
semantic-release-bot
9916bd9ecf Release 3.41.0
[skip ci]

## [3.41.0](https://github.com/cnpm/cnpmcore/compare/v3.40.0...v3.41.0) (2023-08-17)

### Features

* improved readability of emoji in sync logs ([#572](https://github.com/cnpm/cnpmcore/issues/572)) ([0ac275a](0ac275a348))
2023-08-17 12:51:41 +00:00
一丝
0ac275a348 feat: improved readability of emoji in sync logs (#572) 2023-08-17 20:50:08 +08:00
semantic-release-bot
3f9c91c430 Release 3.40.0
[skip ci]

## [3.40.0](https://github.com/cnpm/cnpmcore/compare/v3.39.5...v3.40.0) (2023-08-08)

### Features

* signup on auth ([#567](https://github.com/cnpm/cnpmcore/issues/567)) ([c710600](c7106008d9))
2023-08-08 12:47:01 +00:00
elrrrrrrr
c7106008d9 feat: signup on auth (#567)
> Auto init the account when auth

* 🧶 Added `findOrCreateUser` method. Initialize account on both login
and authorization, as per the submitted GitHub."
------

> 授权时,默认进行账户初始化
* 🧶 新增 `findOrCreateUser` 方法,登录和授权时均初始化账户
2023-08-08 20:45:46 +08:00
semantic-release-bot
b102711adf Release 3.39.5
[skip ci]

## [3.39.5](https://github.com/cnpm/cnpmcore/compare/v3.39.4...v3.39.5) (2023-08-08)

### Bug Fixes

* noImplicitAny ts ([#568](https://github.com/cnpm/cnpmcore/issues/568)) ([1932bb9](1932bb9713))
2023-08-08 12:44:23 +00:00
elrrrrrrr
1932bb9713 fix: noImplicitAny ts (#568)
> Attempted to apply the `noImplicitAny`, parameter types should be
specified unless any is manually declared.
* 🐞 Fixed an issue in bugVersionAdvice where AbbreviatedManifest was
being set abnormally.
* 🤖 Added index.d.ts to store declarations for dependencies without
types.
* 🤔 skipLibCheck has no effect on leoric for now, so it cannot be
enabled temporarily.
--------
> 尝试应用 `noImplicitAny` 配置,除非手动声明 any,否则需要指定参数类型
* 🐞 修复 bugVersionAdvice 中,AbbreviatedManifest 设置异常
* 🤖 添加 index.d.ts 存放无类型依赖声明
* 🤔 skipLibCheck 对 leoric 失效,暂时无法开启 



![image](https://github.com/cnpm/cnpmcore/assets/5574625/7ed9d22e-cac8-4202-ba3c-d4c26eb7dc00)
2023-08-08 20:42:57 +08:00
semantic-release-bot
3297121b9f Release 3.39.4
[skip ci]

## [3.39.4](https://github.com/cnpm/cnpmcore/compare/v3.39.3...v3.39.4) (2023-08-04)

### Bug Fixes

* publisher info ([#565](https://github.com/cnpm/cnpmcore/issues/565)) ([94bcc1a](94bcc1a37e))
2023-08-04 05:47:12 +00:00
elrrrrrrr
94bcc1a37e fix: publisher info (#565)
> Fixed an issue with the `_npmUser` field setting during the initial
sync.
1. 🧶 Fixed _npmUser field setting issue during initial sync, should use
displayName.
2. 🧶 skip diff abbreviated meta for _npmUser
3. 🤖 Refined some TypeScript definitions, use isEqual to diff metas

-------

> 修复首次同步时,`_npmUser` 字段设置异常
1. 🧶 修复 publisher 匹配,获取逻辑,应当用 displayName 进行匹配
2. 🧶 精简 meta 信息,跳过比较不存在的 _npmUser 字段
2. 🤖 调整部分 ts 定义,使用 isEqual 来进行 diff 比较
2023-08-04 13:45:35 +08:00
semantic-release-bot
276b9511b8 Release 3.39.3
[skip ci]

## [3.39.3](https://github.com/cnpm/cnpmcore/compare/v3.39.2...v3.39.3) (2023-08-01)

### Bug Fixes

* don't log NotImplementedError to error log ([#563](https://github.com/cnpm/cnpmcore/issues/563)) ([bcf3547](bcf3547ff2))
2023-08-01 10:26:15 +00:00
fengmk2
bcf3547ff2 fix: don't log NotImplementedError to error log (#563)
![image](https://github.com/cnpm/cnpmcore/assets/156269/0a1de25e-47de-4c74-a934-566bd7a16f2f)
2023-08-01 18:24:46 +08:00
semantic-release-bot
9f6b44dfe9 Release 3.39.2
[skip ci]

## [3.39.2](https://github.com/cnpm/cnpmcore/compare/v3.39.1...v3.39.2) (2023-07-29)

### Bug Fixes

* rename libpg-query to libpg-query-node ([#561](https://github.com/cnpm/cnpmcore/issues/561)) ([9483a46](9483a460a3))
2023-07-29 08:22:32 +00:00
fengmk2
9483a460a3 fix: rename libpg-query to libpg-query-node (#561) 2023-07-29 16:21:03 +08:00
semantic-release-bot
3498ba221c Release 3.39.1
[skip ci]

## [3.39.1](https://github.com/cnpm/cnpmcore/compare/v3.39.0...v3.39.1) (2023-07-27)

### Bug Fixes

* body parser ignore ([#558](https://github.com/cnpm/cnpmcore/issues/558)) ([81d6455](81d6455ff8))
2023-07-27 08:13:00 +00:00
elrrrrrrr
81d6455ff8 fix: body parser ignore (#558)
> Closes https://github.com/cnpm/cnpmcore/issues/551 Some versions
encounter issues with audit requests using the GET method & gzip
* 🧶 Modified the NOT_IMPLEMENTED configuration to skip bodyParser
parsing by default.
* 🧶 Added homeService and implemented miscellaneous methods to handle
additional routes.
------
> Closes https://github.com/cnpm/cnpmcore/issues/551 部分版本 audit 请求为 get
且 参数解析异常
* 🧶 修改 NOT_IMPLEMENTED 配置,默认跳过 bodyParser 解析
* 🧶 添加 homeService,提供 misc 方法处理额外路由

---------

Co-authored-by: fengmk2 <fengmk2@gmail.com>
2023-07-27 16:11:47 +08:00
semantic-release-bot
7ba8dbb4a7 Release 3.39.0
[skip ci]

## [3.39.0](https://github.com/cnpm/cnpmcore/compare/v3.38.2...v3.39.0) (2023-07-27)

### Features

* Sync libpg-query binary ([#557](https://github.com/cnpm/cnpmcore/issues/557)) ([8556b5f](8556b5f92f))
2023-07-27 06:30:37 +00:00
wandergis
8556b5f92f feat: Sync libpg-query binary (#557) 2023-07-27 14:29:22 +08:00
semantic-release-bot
e4d44c68e5 Release 3.38.2
[skip ci]

## [3.38.2](https://github.com/cnpm/cnpmcore/compare/v3.38.1...v3.38.2) (2023-07-21)

### Bug Fixes

* _npmUser info in fullManifest ([#554](https://github.com/cnpm/cnpmcore/issues/554)) ([4455295](44552959eb)), closes [#553](https://github.com/cnpm/cnpmcore/issues/553)
2023-07-21 11:52:25 +00:00
elrrrrrrr
44552959eb fix: _npmUser info in fullManifest (#554)
> closes #553, fixing the issue introduced by
https://github.com/cnpm/cnpmcore/pull/491, which caused an abnormality
in the _npmUser field in fullManifest.
1. 🧶 Update the `publish` method to pass in the actual operating
publisher information.
2. 🧶 Update the diffMeta function to compare the _npmUser as well.
3. ♻️ Existing data needs to be resynchronized, from 6.2 to 7.20.
-----
> closes #553 , 修复由 https://github.com/cnpm/cnpmcore/pull/491 引入问题,导致
fullManifest 中 _npmUser 字段异常
1. 🧶 更新 .publish 方法,传入实际操作的 publisher 信息
2. 🧶 更新 diffMeta 算法,将 _npmUser 也进行比对
3. ♻️ 存量数据需要重新进行同步,6.2 -> 7.20
2023-07-21 19:50:55 +08:00
semantic-release-bot
9ac676772d Release 3.38.1
[skip ci]

## [3.38.1](https://github.com/cnpm/cnpmcore/compare/v3.38.0...v3.38.1) (2023-07-21)

### Bug Fixes

* publish lock ([#555](https://github.com/cnpm/cnpmcore/issues/555)) ([ec90ab8](ec90ab85fa))
2023-07-21 09:34:03 +00:00
elrrrrrrr
ec90ab85fa fix: publish lock (#555)
> When concurrently executing packet sending, there is a possibility of
version overwrite

* 🧶 Add `usingLock` logic to the publish interface to handle concurrent
execution, which will prevent version overwrite
* 🔨 Modify usingLock to include a return value indicating the success of
lock creation
------
> 并发执行发包时,可能出现版本覆盖问题
1. 🧶 在发布接口中,添加 usingLock 逻辑,包同步场景不涉及
2. 🔨 `usingLock` 添加返回值,标记是否创建锁成功
2023-07-21 17:32:39 +08:00
semantic-release-bot
9ca483cfa5 Release 3.38.0
[skip ci]

## [3.38.0](https://github.com/cnpm/cnpmcore/compare/v3.37.1...v3.38.0) (2023-07-17)

### Features

* misc router ([#552](https://github.com/cnpm/cnpmcore/issues/552)) ([e9e3a7b](e9e3a7b70f)), closes [#551](https://github.com/cnpm/cnpmcore/issues/551)
2023-07-17 06:53:39 +00:00
elrrrrrrr
e9e3a7b70f feat: misc router (#552)
![image](https://github.com/cnpm/cnpmcore/assets/5574625/d158e354-d390-4835-91de-7aa9f5104e49)


> close #551 , 针对未实现的 post 请求添加错误码

1. 🧶 拦截 audit 相关接口
[ref](https://docs.npmjs.com/cli/v9/commands/npm-audit)
2. ♻️ 默认 404 GET 接口不做额外处理

-----

> close #551, add error code for unimplemented post requests

1. 🧶 Intercept audit-related interfaces
[ref](https://docs.npmjs.com/cli/v9/commands/npm-audit)
2. ♻️ No additional processing for default 404 GET interfaces.
2023-07-17 14:52:21 +08:00
乐潇游
8a9412df4f chore: update tegg deps (#549) (#550) 2023-07-15 00:00:36 +08:00
semantic-release-bot
28deae4b70 Release 3.37.1
[skip ci]

## [3.37.1](https://github.com/cnpm/cnpmcore/compare/v3.37.0...v3.37.1) (2023-07-13)

### Bug Fixes

* playwright bianry sync config ([#548](https://github.com/cnpm/cnpmcore/issues/548)) ([166e334](166e3341f4))
2023-07-13 07:06:22 +00:00
elrrrrrrr
166e3341f4 fix: playwright bianry sync config (#548)
> Follow the Playwright-core configuration,
[ref](https://registry.npmmirror.com/playwright-core/1.36.0/files/lib/server/registry/index.js),
to update the binary download path information.

1. 🧶 Update Playwright configuration information.
--------

> Follow playwright-core 配置,
[ref](https://registry.npmmirror.com/playwright-core/1.36.0/files/lib/server/registry/index.js),更新
binary 下载路径信息
1. 🧶 更新 playwright 配置信息
2023-07-13 15:05:10 +08:00
semantic-release-bot
02d0d2b5a0 Release 3.37.0
[skip ci]

## [3.37.0](https://github.com/cnpm/cnpmcore/compare/v3.36.0...v3.37.0) (2023-07-12)

### Features

* add lastUsedAt for classic token ([#547](https://github.com/cnpm/cnpmcore/issues/547)) ([e061685](e0616859ff))
2023-07-12 07:12:37 +00:00
elrrrrrrr
e0616859ff feat: add lastUsedAt for classic token (#547)
> Follow https://github.com/cnpm/cnpmcore/pull/488 , add lastUsedAt for
classic tokens.
1. 🧶 Modify the `checkTokenExpired` method to `checkTokenStatus` which
update the token field internally.
2. ♻️ No compensation will be made for existing data, and it should be
updated by the consuming end.
---------
> Follow https://github.com/cnpm/cnpmcore/pull/488, 为 classic token 也添加
lastUsedAt 信息
1. 🧶 修改 `checkTokenExpired` 方法为 `checkTokenStatus`,内部进行 token 字段更新
2. ♻️ 存量数据不做补偿,由消费端控制
2023-07-12 15:11:20 +08:00
semantic-release-bot
f5da7e6c19 Release 3.36.0
[skip ci]

## [3.36.0](https://github.com/cnpm/cnpmcore/compare/v3.35.1...v3.36.0) (2023-07-09)

### Features

* support strictValidateTarballPkg ([#546](https://github.com/cnpm/cnpmcore/issues/546)) ([dd3438f](dd3438f470)), closes [#542](https://github.com/cnpm/cnpmcore/issues/542)
2023-07-09 15:34:17 +00:00
elrrrrrrr
dd3438f470 feat: support strictValidateTarballPkg (#546)
> Validate the manifest and tarball info to prevent contamination during
consumption, closes #542.
1. 🔨 Added the "strictValidateTarballPkg" mode to enable validation,
only applicable to the slef registry scenario.
2. 🧶 When the configuration is enabled, validate the relevant fields
during publishing, currently only validating the fields affecting
consumption.
3. ♻️ No corrective actions will be taken for existing scenario data.
-----

> 发布时校验 manifest 和 tarball 字段是否陪陪,防止消费时被污染 closes #542
1. 🔨 新增 strictValidateTarballPkg 配置,仅对在发布当前 registry 场景下生效
2. 🧶 配置开启时,发布时校验相关字段,目前仅校验影响消费相关字段
3. ♻️ 存量场景数据不做订正处理
2023-07-09 23:32:53 +08:00
semantic-release-bot
18af011a51 Release 3.35.1
[skip ci]

## [3.35.1](https://github.com/cnpm/cnpmcore/compare/v3.35.0...v3.35.1) (2023-06-29)

### Bug Fixes

* update source registry ([#537](https://github.com/cnpm/cnpmcore/issues/537)) ([ab2fde7](ab2fde7c80))
2023-06-29 07:51:11 +00:00
elrrrrrrr
ab2fde7c80 fix: update source registry (#537)
> the registryInfo in pkg fullManifest should be updated when the
package is migrated to another registry.

1. 🐞 when query from DB, dynamically add registry information.
2. ♻️ when hit cache, updating metadata should already trigger cache
modifications.
--------

> 当包从属 registryId 发生变化时,包 manifest 内的信息也需要同步更新
1. 🐞 从 db 读取元信息时,实时添加 registry 信息
2. ♻️ 从缓存读取元信息时,发布或修改元信息时已触发缓存修改
2023-06-29 15:49:56 +08:00
semantic-release-bot
ebcb65d27f Release 3.35.0
[skip ci]

## [3.35.0](https://github.com/cnpm/cnpmcore/compare/v3.34.10...v3.35.0) (2023-06-28)

### Features

* adaptive username ([#536](https://github.com/cnpm/cnpmcore/issues/536)) ([dd69606](dd69606365)), closes [/github.com/npm/cli/blob/latest/lib/commands/owner.js#L151](https://github.com/cnpm//github.com/npm/cli/blob/latest/lib/commands/owner.js/issues/L151)
2023-06-28 12:50:47 +00:00
elrrrrrrr
dd69606365 feat: adaptive username (#536)
> When executing npm cli owner add command,
[ref](https://github.com/npm/cli/blob/latest/lib/commands/owner.js#L151),
it causes errors due to duplicate additions,when the selfRegistry
configuration has the userPrefix option enabled.
1. 🧶 Modify the user query api to prioritize returning users from
selfRegistry
2. 🧶 Ensure that the query api uniformly returns displayName

---------

> npm cli 执行 owner add 时
[ref](https://github.com/npm/cli/blob/latest/lib/commands/owner.js#L151),依赖查询结果做去重,selfRegistry
配置 userPrefix 时会导致重复添加报错
1. 🧶 修改用户查询接口,优先返回 selfRegistry 内的用户
2. 🧶 查询接口统一返回 displayName
2023-06-28 20:49:32 +08:00
semantic-release-bot
a4a0f2df3a Release 3.34.10
[skip ci]

## [3.34.10](https://github.com/cnpm/cnpmcore/compare/v3.34.9...v3.34.10) (2023-06-28)

### Bug Fixes

* only syncUpstream in default registry ([#535](https://github.com/cnpm/cnpmcore/issues/535)) ([bb5d993](bb5d993030))
2023-06-28 05:58:08 +00:00
elrrrrrrr
bb5d993030 fix: only syncUpstream in default registry (#535)
> Only syncUpstream for default registry, to optimize the
synchronization speed.
* 🧶 Adjust the syncUpstream judgment process.

----

> 仅对公网包进行 syncUpstream 流程处理,内网包没有多级代理流程,优化同步速度。
* 🧶 调整 syncUpstream 判断流程
2023-06-28 13:56:54 +08:00
semantic-release-bot
506969615b Release 3.34.9
[skip ci]

## [3.34.9](https://github.com/cnpm/cnpmcore/compare/v3.34.8...v3.34.9) (2023-06-27)

### Bug Fixes

* console ([#534](https://github.com/cnpm/cnpmcore/issues/534)) ([4141003](4141003e13))
2023-06-27 04:30:55 +00:00
elrrrrrrr
4141003e13 fix: console (#534)
> remove console.log

----

去除 console.log
2023-06-27 12:29:14 +08:00
semantic-release-bot
241677687a Release 3.34.8
[skip ci]

## [3.34.8](https://github.com/cnpm/cnpmcore/compare/v3.34.7...v3.34.8) (2023-06-27)

### Bug Fixes

* legacy pkg publish ([#533](https://github.com/cnpm/cnpmcore/issues/533)) ([20ffba8](20ffba8d41))
2023-06-27 03:55:20 +00:00
elrrrrrrr
20ffba8d41 fix: legacy pkg publish (#533)
> pkgs sync from cnpmjs.org may contain uppercase characters.
1. 🧶 Update the validation rules, allow to publish existing packages.
---------
> 部分包从 cnpmjs.org 进行同步,可能含有大写字符
1. 🧶 更新校验规则,允许存量包修改
2023-06-27 11:54:02 +08:00
semantic-release-bot
c0415c01f7 Release 3.34.7
[skip ci]

## [3.34.7](https://github.com/cnpm/cnpmcore/compare/v3.34.6...v3.34.7) (2023-06-25)

### Bug Fixes

* sync self pkg ([#532](https://github.com/cnpm/cnpmcore/issues/532)) ([ada3e22](ada3e220a1))
2023-06-25 15:03:34 +00:00
elrrrrrrr
ada3e220a1 fix: sync self pkg (#532)
> During the syncUpstream process, it will attempt to create sync
repeatedly until it times out, when the pkg has been published in the
self registry.
1. 🐞 When executing the syncTask, filter out scenarios where the target
registry is the self registry.
-------

> 包迁移至当前 registry 时,收到同步请求会产生无效的同步任务,当 `syncUpstream` 时,会尝试重复创建 sync
直到超时。
1. 🐞 syncTask 执行时,先过滤目标 registry 是当前 registry 的场景。
2023-06-25 23:02:11 +08:00
semantic-release-bot
fece88201d Release 3.34.6
[skip ci]

## [3.34.6](https://github.com/cnpm/cnpmcore/compare/v3.34.5...v3.34.6) (2023-06-25)

### Bug Fixes

* only auto install s3-cnpmcore on Node.js 18+ ([#531](https://github.com/cnpm/cnpmcore/issues/531)) ([c9d9ce8](c9d9ce8205))
2023-06-25 09:49:58 +00:00
fengmk2
c9d9ce8205 fix: only auto install s3-cnpmcore on Node.js 18+ (#531)
closes https://github.com/cnpm/cnpmcore/issues/530
2023-06-25 17:48:39 +08:00
semantic-release-bot
ff8a81cde4 Release 3.34.5
[skip ci]

## [3.34.5](https://github.com/cnpm/cnpmcore/compare/v3.34.4...v3.34.5) (2023-06-21)

### Bug Fixes

* create sync task by 'GET /:fullname/-/:filenameWithVersion.tgz' ([#526](https://github.com/cnpm/cnpmcore/issues/526)) ([5ceaa6b](5ceaa6b8dd))
2023-06-21 00:48:33 +00:00
hezhengxu2018
5ceaa6b8dd fix: create sync task by 'GET /:fullname/-/:filenameWithVersion.tgz' (#526)
used by pnpm project with lock

closes https://github.com/cnpm/cnpmcore/issues/525
2023-06-21 08:46:48 +08:00
semantic-release-bot
110fdaef55 Release 3.34.4
[skip ci]

## [3.34.4](https://github.com/cnpm/cnpmcore/compare/v3.34.3...v3.34.4) (2023-06-20)

### Bug Fixes

* avoid db query on sync mode all ([#527](https://github.com/cnpm/cnpmcore/issues/527)) ([49855d9](49855d97e5)), closes [/github.com/cnpm/cnpmcore/pull/522/files#r1234655574](https://github.com/cnpm//github.com/cnpm/cnpmcore/pull/522/files/issues/r1234655574)
2023-06-20 02:16:40 +00:00
fengmk2
49855d97e5 fix: avoid db query on sync mode all (#527)
https://github.com/cnpm/cnpmcore/pull/522/files#r1234655574
2023-06-20 10:15:26 +08:00
semantic-release-bot
84499bc9f8 Release 3.34.3
[skip ci]

## [3.34.3](https://github.com/cnpm/cnpmcore/compare/v3.34.2...v3.34.3) (2023-06-17)

### Bug Fixes

* merge docker ENV into one layer ([#523](https://github.com/cnpm/cnpmcore/issues/523)) ([eb91b83](eb91b834c0))
2023-06-17 16:32:14 +00:00
fengmk2
eb91b834c0 fix: merge docker ENV into one layer (#523) 2023-06-18 00:29:46 +08:00
semantic-release-bot
182bc8b3e7 Release 3.34.2
[skip ci]

## [3.34.2](https://github.com/cnpm/cnpmcore/compare/v3.34.1...v3.34.2) (2023-06-17)

### Bug Fixes

* should redirect when nfs adapter support url ([#522](https://github.com/cnpm/cnpmcore/issues/522)) ([3d6864c](3d6864c713))
2023-06-17 16:13:29 +00:00
hezhengxu2018
3d6864c713 fix: should redirect when nfs adapter support url (#522)
will check package version on database before redirect to nfs store url

closes https://github.com/cnpm/cnpmcore/issues/521

---------

Co-authored-by: fengmk2 <fengmk2@gmail.com>
2023-06-18 00:02:12 +08:00
semantic-release-bot
713c879af0 Release 3.34.1
[skip ci]

## [3.34.1](https://github.com/cnpm/cnpmcore/compare/v3.34.0...v3.34.1) (2023-06-14)

### Bug Fixes

* add block package by packageId and name function ([#514](https://github.com/cnpm/cnpmcore/issues/514)) ([b81b2a0](b81b2a03f8))
2023-06-14 09:08:42 +00:00
Ke Wu
b81b2a03f8 fix: add block package by packageId and name function (#514) 2023-06-14 17:07:23 +08:00
semantic-release-bot
f588e272b3 Release 3.34.0
[skip ci]

## [3.34.0](https://github.com/cnpm/cnpmcore/compare/v3.33.0...v3.34.0) (2023-06-13)

### Features

* sync package readme ([#512](https://github.com/cnpm/cnpmcore/issues/512)) ([f64e273](f64e273566))
* use unpkg README.md to update package version readme property ([#511](https://github.com/cnpm/cnpmcore/issues/511)) ([56d8e1a](56d8e1ad87))
2023-06-13 03:29:45 +00:00
fengmk2
f64e273566 feat: sync package readme (#512) 2023-06-13 11:28:31 +08:00
fengmk2
56d8e1ad87 feat: use unpkg README.md to update package version readme property (#511)
closes https://github.com/cnpm/cnpmcore/issues/481
2023-06-13 08:42:26 +08:00
semantic-release-bot
7df9648e6b Release 3.33.0
[skip ci]

## [3.33.0](https://github.com/cnpm/cnpmcore/compare/v3.32.0...v3.33.0) (2023-06-12)

### Features

* support Dockerfile and S3 nfs ([#509](https://github.com/cnpm/cnpmcore/issues/509)) ([f61ef1c](f61ef1c058))
2023-06-12 01:20:41 +00:00
fengmk2
f61ef1c058 feat: support Dockerfile and S3 nfs (#509)
closes https://github.com/cnpm/cnpmcore/issues/507
2023-06-12 09:19:03 +08:00
semantic-release-bot
428b1f3299 Release 3.32.0
[skip ci]

## [3.32.0](https://github.com/cnpm/cnpmcore/compare/v3.31.0...v3.32.0) (2023-06-11)

### Features

* sync specified versions ([#487](https://github.com/cnpm/cnpmcore/issues/487)) ([a9bb81a](a9bb81adfb))
2023-06-11 12:14:23 +00:00
hezhengxu2018
a9bb81adfb feat: sync specified versions (#487)
允许同步指定版本
---------
Allow to sync the specified versions
2023-06-11 20:12:59 +08:00
semantic-release-bot
1dbf481a11 Release 3.31.0
[skip ci]

## [3.31.0](https://github.com/cnpm/cnpmcore/compare/v3.30.2...v3.31.0) (2023-06-11)

### Features

* Support environment variable for config 🚀  ([#489](https://github.com/cnpm/cnpmcore/issues/489)) ([d4d7a3d](d4d7a3d7c8))
2023-06-11 07:08:07 +00:00
Nadeshiko Manju
d4d7a3d7c8 feat: Support environment variable for config 🚀 (#489)
closes https://github.com/cnpm/cnpmcore/issues/465

---------

Co-authored-by: fengmk2 <fengmk2@gmail.com>
2023-06-11 15:06:37 +08:00
semantic-release-bot
f79ac030c1 Release 3.30.2
[skip ci]

## [3.30.2](https://github.com/cnpm/cnpmcore/compare/v3.30.1...v3.30.2) (2023-06-09)

### Bug Fixes

* sync DOWNLOAD_PATHS code update ([#506](https://github.com/cnpm/cnpmcore/issues/506)) ([bf2bf64](bf2bf64532))
2023-06-09 06:48:38 +00:00
fengmk2
bf2bf64532 fix: sync DOWNLOAD_PATHS code update (#506)
closes https://github.com/cnpm/cnpmcore/issues/505
2023-06-09 14:47:01 +08:00
semantic-release-bot
816fa16526 Release 3.30.1
[skip ci]

## [3.30.1](https://github.com/cnpm/cnpmcore/compare/v3.30.0...v3.30.1) (2023-06-07)

### Bug Fixes

* type import ([#502](https://github.com/cnpm/cnpmcore/issues/502)) ([27ee3d6](27ee3d61a3))
2023-06-07 10:11:51 +00:00
elrrrrrrr
27ee3d61a3 fix: type import (#502)
> fix loading issues in the integrate mode
* 🧶 Modify the spec validation to only support `version` `tag` `range`
and not `alias` method.
* 🤖 Remove ts baseUrl config, use relative paths uniformly.

----

> 修复集成模式加载失败
* 🧶 修改 Spec 校验,仅支持 `version` `tag` `range`,不支持 `alias` 方式
* 🤖 去除 ts baseUrl 配置,统一使用相对路径
2023-06-07 18:10:36 +08:00
semantic-release-bot
2001168ab8 Release 3.30.0
[skip ci]

## [3.30.0](https://github.com/cnpm/cnpmcore/compare/v3.29.4...v3.30.0) (2023-06-07)

### Features

* bulk update maintainers ([#501](https://github.com/cnpm/cnpmcore/issues/501)) ([1b6dce6](1b6dce65ac))
2023-06-07 08:13:12 +00:00
elrrrrrrr
1b6dce65ac feat: bulk update maintainers (#501)
> Some dirty data of userPrefix in the maintainers cannot be cleared
during synchronization. Adjust the synchronization logic for
maintainers.
1. 🧶 When synchronizing packages, uniformly update the manifest dist.
2. 🛠️ `replacereplacePackageMaintainers` =>
`replacePackageMaintainersAndDist`
-----
> 部分旧包 maintainer 中的 userPrefix 脏数据同步时由于没有成员变更,无法清除,调整 maintainer 同步逻辑
1. 🧶 包同步时,统一更新 maintainer 对应的 manifest dist
2. 🛠️ `replacereplacePackageMaintainers` =>
`replacePackageMaintainersAndDist`
2023-06-07 16:12:11 +08:00
semantic-release-bot
f1bbd267b4 Release 3.29.4
[skip ci]

## [3.29.4](https://github.com/cnpm/cnpmcore/compare/v3.29.3...v3.29.4) (2023-06-07)

### Bug Fixes

* validate pkg@version spec ([#500](https://github.com/cnpm/cnpmcore/issues/500)) ([a9d2ff7](a9d2ff7e54))
2023-06-07 08:01:13 +00:00
elrrrrrrr
a9d2ff7e54 fix: validate pkg@version spec (#500)
> follow https://github.com/cnpm/cnpmcore/pull/495 after supporting
spec, adjust the parameter validation rules
1. 🆕 Add `Spec` validation rule, validating the spec by npa
2. 🛠️ Upgrade versionOrTag to versionSpec to support semver expressions,
such as `^2.x || > 3.x`
---------

> follow https://github.com/cnpm/cnpmcore/pull/495 支持 spec 后,调整参数校验规则
1. 🆕 新增 `Sepc` 校验规则,使用 npa 拼接包名进行验证
2. 🛠️ versionOrTag 升级为 versionSpec 支持 semver 表达式,例如 `^2.x || > 3.x`
2023-06-07 15:59:58 +08:00
semantic-release-bot
efb6412bac Release 3.29.3
[skip ci]

## [3.29.3](https://github.com/cnpm/cnpmcore/compare/v3.29.2...v3.29.3) (2023-06-07)

### Bug Fixes

* auto fix invalid version to any version ([#499](https://github.com/cnpm/cnpmcore/issues/499)) ([0cf6470](0cf6470993))
2023-06-07 02:02:51 +00:00
fengmk2
0cf6470993 fix: auto fix invalid version to any version (#499) 2023-06-07 10:01:40 +08:00
fengmk2
7fbf79f038 chore: print more error infomation 2023-06-07 09:18:58 +08:00
semantic-release-bot
06eaa13116 Release 3.29.2
[skip ci]

## [3.29.2](https://github.com/cnpm/cnpmcore/compare/v3.29.1...v3.29.2) (2023-06-07)

### Bug Fixes

* ignore fix invalid version ([#498](https://github.com/cnpm/cnpmcore/issues/498)) ([f5607c0](f5607c0a87))
2023-06-07 00:59:48 +00:00
fengmk2
f5607c0a87 fix: ignore fix invalid version (#498) 2023-06-07 08:58:35 +08:00
semantic-release-bot
cb5117aa72 Release 3.29.1
[skip ci]

## [3.29.1](https://github.com/cnpm/cnpmcore/compare/v3.29.0...v3.29.1) (2023-06-07)

### Bug Fixes

* fix encoded semver spec ([#497](https://github.com/cnpm/cnpmcore/issues/497)) ([aa39ced](aa39cedf35))
2023-06-07 00:36:25 +00:00
killa
aa39cedf35 fix: fix encoded semver spec (#497) 2023-06-07 08:34:37 +08:00
semantic-release-bot
183223b67d Release 3.29.0
[skip ci]

## [3.29.0](https://github.com/cnpm/cnpmcore/compare/v3.28.0...v3.29.0) (2023-06-06)

### Features

* infer userPrefix when update maintainers  ([#496](https://github.com/cnpm/cnpmcore/issues/496)) ([e02ea2a](e02ea2a3e5))
2023-06-06 13:00:58 +00:00
elrrrrrrr
e02ea2a3e5 feat: infer userPrefix when update maintainers (#496)
> When update members , infer the userPrefix by default to be compatible
with the default npm cli.
* 🧶 When adding a member, query the userPrefix corresponding to the
registry.
```
 $ npm owner add elrrrrrrr @cnpm/example --registry=http://127.0.0.1:7001
 $ npm owner add cnpm:elrrrrrrr @cnpm/example --registry=http://127.0.0.1:7001
```

-------
> 使用 cli 更新成员时,默认推导 userPrefix 信息,兼容 npm 客户端默认流程
* 🧶 添加成员时,查询 registry 对应的 userPrefix
```
 $ npm owner add elrrrrrrr @cnpm/example --registry=http://127.0.0.1:7001               
 $ npm owner add cnpm:elrrrrrrr @cnpm/example --registry=http://127.0.0.1:7001
```
2023-06-06 20:59:46 +08:00
semantic-release-bot
df8e114a5b Release 3.28.0
[skip ci]

## [3.28.0](https://github.com/cnpm/cnpmcore/compare/v3.27.0...v3.28.0) (2023-06-06)

### Features

* impl fast semver search ([#495](https://github.com/cnpm/cnpmcore/issues/495)) ([a7fd3a8](a7fd3a8c8a))
2023-06-06 12:57:26 +00:00
killa
a7fd3a8c8a feat: impl fast semver search (#495)
Including SQL change: `sql/3.28.0.sql`
2023-06-06 20:56:05 +08:00
semantic-release-bot
926d724793 Release 3.27.0
[skip ci]

## [3.27.0](https://github.com/cnpm/cnpmcore/compare/v3.26.0...v3.27.0) (2023-06-04)

### Features

* source registry manifest ([#493](https://github.com/cnpm/cnpmcore/issues/493)) ([bbec9a3](bbec9a38bd))
2023-06-04 08:58:24 +00:00
elrrrrrrr
bbec9a38bd feat: source registry manifest (#493)
> Add the `_source_registry_manifest` in pkgFullManifest.
* 🧶 Set registryInfo during reading because of the excessive existing
data.
* ♻️ The change takes effect when redis cache expired.
----------

> 在 pkgFullManifest 中添加 _source_registry_manifest 相关字段
1. 🧶 存量数据过多,在读取时统一设置,展示当前对应 registryId
2. ♻️ 在读取 db 时生效,需要等缓存过期
2023-06-04 16:57:19 +08:00
semantic-release-bot
3a423a2eb2 Release 3.26.0
[skip ci]

## [3.26.0](https://github.com/cnpm/cnpmcore/compare/v3.25.1...v3.26.0) (2023-06-04)

### Features

* whoami return granular token info ([#494](https://github.com/cnpm/cnpmcore/issues/494)) ([d0d2f78](d0d2f78d7b))
2023-06-04 08:56:19 +00:00
elrrrrrrr
d0d2f78d7b feat: whoami return granular token info (#494)
> add token info when invoke `whoami` to notify the caller about the
token's current status.

* 🧶 Add token information to the "whoami" interface.
* 🔨 Modify the query logic for allowedPackages uniformly within the
repository.
-----------

> 当使用 granularToken 调用 whoami 信息时,返回当前 token 信息,告知调用方当前 token 状态
* 🧶 在whoami 接口中添加 token 信息
* 🔨 修改 allowedPackages 查询逻辑,统一在 repository 中集成
2023-06-04 16:54:48 +08:00
semantic-release-bot
ad9adf7cd0 Release 3.25.1
[skip ci]

## [3.25.1](https://github.com/cnpm/cnpmcore/compare/v3.25.0...v3.25.1) (2023-06-03)

### Bug Fixes

* hook enable ([#492](https://github.com/cnpm/cnpmcore/issues/492)) ([40e2f92](40e2f92b94))
2023-06-03 11:12:00 +00:00
elrrrrrrr
40e2f92b94 fix: hook enable (#492)
> Fix hookEnable config does not take effect
* 🐞 Modify config key in changesStream event
* 🤖 Add corresponding unit tests
---------
> 修复 hookEnable 开关配置不生效的问题
* 🐞 修改 events 中配置获取异常
* 🤖 添加对应单测
2023-06-03 19:10:33 +08:00
semantic-release-bot
e88a610011 Release 3.25.0
[skip ci]

## [3.25.0](https://github.com/cnpm/cnpmcore/compare/v3.24.0...v3.25.0) (2023-06-02)

### Features

* add _npmUser ([#491](https://github.com/cnpm/cnpmcore/issues/491)) ([f7b5d5a](f7b5d5af12))
2023-06-02 04:38:14 +00:00
elrrrrrrr
f7b5d5af12 feat: add _npmUser (#491)
> For private packages published in the current registry, add the
"_npmUser" field to align with the npm registry.

* 🧶 Add the "_npmUser" field for new scenarios, without modifying the
abbreviated data. Use the following command: curl -H 'Accept:
application/vnd.npm.install-v1+json'
'https://registry.npmjs.org/cnpmcore'
* ♻️ Existing data cannot be traced and will not be compensated.
-----

> 对于在当前 registry 发布的私有包,添加 _npmUser 字段,和公网 registry 保持一致
* 🧶 新增 _npmUser 字段,abbreviated 场景不做修改, (via `curl -H 'Accept:
application/vnd.npm.install-v1+json'
'https://registry.npmjs.org/cnpmcore'`)
* ♻️ 存量数据无法回溯,不做补偿
2023-06-02 12:36:53 +08:00
semantic-release-bot
f30e517f9e Release 3.24.0
[skip ci]

## [3.24.0](https://github.com/cnpm/cnpmcore/compare/v3.23.2...v3.24.0) (2023-06-01)

### Features

* token last used at ([#488](https://github.com/cnpm/cnpmcore/issues/488)) ([3a8a91a](3a8a91ae0b))
2023-06-01 11:19:35 +00:00
elrrrrrrr
3a8a91ae0b feat: token last used at (#488)
> Add the lastUsedBy field to the Token model

* 🧶 Add `lastUsedBy` field to the token, for platform display.
* 🐞 Fix the issue where the graunlarToken is not expired in read-only
scenarios.
------
> 为 Token 模型添加 lastUsedBy 字段
* 🧶 新增 lastUsedBy 字段,记录 token 最近使用时间,用于平台展示
* 🐞 修复 graunlarToken 过期时,只读场景没有禁用的问题
2023-06-01 19:18:17 +08:00
semantic-release-bot
0c1e2ceb7f Release 3.23.2
[skip ci]

## [3.23.2](https://github.com/cnpm/cnpmcore/compare/v3.23.1...v3.23.2) (2023-05-31)

### Bug Fixes

* unpkg support non-npm publish tgz file ([#485](https://github.com/cnpm/cnpmcore/issues/485)) ([5fe883f](5fe883f878)), closes [/github.com/cnpm/cnpmcore/issues/452#issuecomment-1570077310](https://github.com/cnpm//github.com/cnpm/cnpmcore/issues/452/issues/issuecomment-1570077310)
2023-05-31 12:44:11 +00:00
fengmk2
5fe883f878 fix: unpkg support non-npm publish tgz file (#485)
https://github.com/cnpm/cnpmcore/issues/452#issuecomment-1570077310
2023-05-31 20:42:19 +08:00
semantic-release-bot
a7258aa7ec Release 3.23.1
[skip ci]

## [3.23.1](https://github.com/cnpm/cnpmcore/compare/v3.23.0...v3.23.1) (2023-05-30)

### Bug Fixes

* use package version publishTime instead of file mtime ([#483](https://github.com/cnpm/cnpmcore/issues/483)) ([68f6b6b](68f6b6b944))
2023-05-30 15:58:42 +00:00
fengmk2
68f6b6b944 fix: use package version publishTime instead of file mtime (#483)
closes https://github.com/cnpm/cnpmcore/issues/482
2023-05-30 23:57:34 +08:00
semantic-release-bot
9e5e555552 Release 3.23.0
[skip ci]

## [3.23.0](https://github.com/cnpm/cnpmcore/compare/v3.22.3...v3.23.0) (2023-05-29)

### Features

* export getUserAndToken ([#480](https://github.com/cnpm/cnpmcore/issues/480)) ([aa4fdd3](aa4fdd3545))
2023-05-29 17:39:20 +00:00
elrrrrrrr
aa4fdd3545 feat: export getUserAndToken (#480)
> Export `getUserAndToken` method for authorization parsing in integrate
mode.

1. 🧶 TokenService adds getUserAndToken method

----------

> 暴露 `getUserAndToken` 方法进行 authorization 解析,面向集成模式的场景
1. 🧶 TokenService 新增 `getUserAndToken` 方法

---------

Co-authored-by: fengmk2 <fengmk2@gmail.com>
2023-05-30 01:37:22 +08:00
semantic-release-bot
1b89b64356 Release 3.22.3
[skip ci]

## [3.22.3](https://github.com/cnpm/npmcore/compare/v3.22.2...v3.22.3) (2023-05-29)

### Bug Fixes

* unpkg redirect ([#479](https://github.com/cnpm/npmcore/issues/479)) ([c395c79](c395c7906b))
2023-05-29 10:03:47 +00:00
elrrrrrrr
c395c7906b fix: unpkg redirect (#479) 2023-05-29 18:02:23 +08:00
fengmk2
cc01398a16 refactor: use binaries.prisma.sh to download prisma files (#478) 2023-05-25 17:52:46 +08:00
semantic-release-bot
be228399d1 Release 3.22.2
[skip ci]

## [3.22.2](https://github.com/cnpm/npmcore/compare/v3.22.1...v3.22.2) (2023-05-25)

### Bug Fixes

* use S3 url to download file ([#477](https://github.com/cnpm/npmcore/issues/477)) ([9bed829](9bed829628)), closes [/github.com/cnpm/cnpmcore/issues/472#issuecomment-1562452369](https://github.com/cnpm//github.com/cnpm/cnpmcore/issues/472/issues/issuecomment-1562452369)
2023-05-25 08:11:26 +00:00
fengmk2
9bed829628 fix: use S3 url to download file (#477)
https://github.com/cnpm/cnpmcore/issues/472#issuecomment-1562452369
2023-05-25 16:10:18 +08:00
semantic-release-bot
9c60a597f2 Release 3.22.1
[skip ci]

## [3.22.1](https://github.com/cnpm/npmcore/compare/v3.22.0...v3.22.1) (2023-05-25)

### Bug Fixes

* refactor config type ([#476](https://github.com/cnpm/npmcore/issues/476)) ([ebc8c98](ebc8c98fa4))
2023-05-25 07:20:59 +00:00
elrrrrrrr
ebc8c98fa4 fix: refactor config type (#476)
> New CnpmcoreConfig type to handle compatibility issues
https://github.com/cnpm/cnpmcore/pull/475
1. 🆕 add a new CnpmcoreConfig in port/config, referenced in
config.default.ts
2. 🆕 add `ChangesStreamMode` enums.
-------------
> 新增 CnpmcoreConfig 类型处理兼容问题 https://github.com/cnpm/cnpmcore/pull/475
1. 🆕 新增 CnpmcoreConfig,config.default.ts 中进行引用
2. 🆕 新增 `ChangesStreamMode` 枚举


![image](https://github.com/cnpm/cnpmcore/assets/5574625/1fd4afd0-0739-4021-a134-2311bbf78713)
2023-05-25 15:19:51 +08:00
semantic-release-bot
517bb8e8d4 Release 3.22.0
[skip ci]

## [3.22.0](https://github.com/cnpm/npmcore/compare/v3.21.0...v3.22.0) (2023-05-25)

### Features

* sync prisma binary from R2 ([#474](https://github.com/cnpm/npmcore/issues/474)) ([ce4e868](ce4e8681ae))
2023-05-25 03:45:07 +00:00
fengmk2
ce4e8681ae feat: sync prisma binary from R2 (#474)
closes https://github.com/cnpm/cnpmcore/issues/473
2023-05-25 11:43:52 +08:00
fengmk2
26f5eaf438 refactor: jsencrypt and jquery use unpkg (#471) 2023-05-24 11:09:22 +08:00
semantic-release-bot
81865a1790 Release 3.21.0
[skip ci]

## [3.21.0](https://github.com/cnpm/npmcore/compare/v3.20.3...v3.21.0) (2023-05-21)

### Features

* easy config ([#468](https://github.com/cnpm/npmcore/issues/468)) ([9208392](92083924ea))
2023-05-21 13:30:29 +00:00
elrrrrrrr
92083924ea feat: easy config (#468)
> Make the method for tegg integration mode to be more user-friendly.

* 🤖 Automatically add config.cnpmcore type hints.
* 🧶 Export the default `cnpmcoreConfig` , which needs to be explicitly
declared for app config.
* 📚 Supplement the documentation and field definitions.
------

> 对于 egg 集成模式,提供更加友好的自定义配置方式。
* 🤖 自动添加 config.cnpmcore 类型提示
* 🧶 输出默认的 cnpmcoreConfig 对象,应用集成需显式声明,防止新增配置丢失
* 📚 补充文档及字段定义信息


![image](https://github.com/cnpm/cnpmcore/assets/5574625/98d3e0df-32f5-4de5-990a-bc1561cd73be)

---------

Co-authored-by: fengmk2 <fengmk2@gmail.com>
2023-05-21 21:27:50 +08:00
elrrrrrrr
80ab0548f2 refactor: unpublish logic (#467)
> Adjust the logic for unpublishing a package
* 🧶 Determine if a call to unpublish within the removePackageVersion
function
* ♻️ Remove`forceRefresh` in unpublishPackage
-------
> 调整 unpublish package 逻辑
* 🧶 removePackageVersion 内判断是否需要调用 unpublish
* ♻️ unpublishPackage 删除 forceRefresh 逻辑
2023-05-18 22:45:18 +08:00
380 changed files with 60030 additions and 6746 deletions

36
.docker/alpine/Dockerfile Normal file
View File

@@ -0,0 +1,36 @@
FROM node:22-alpine
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
COPY . .
RUN chmod +x .docker/build.sh && .docker/build.sh
ENV NODE_ENV=production \
EGG_SERVER_ENV=prod \
CNPMCORE_CONFIG_REGISTRY= \
CNPMCORE_CONFIG_SOURCE_REGISTRY=https://registry.npmmirror.com \
CNPMCORE_CONFIG_SOURCE_REGISTRY_IS_CNPM=true \
CNPMCORE_DATABASE_TYPE= \
CNPMCORE_DATABASE_NAME= \
CNPMCORE_DATABASE_HOST= \
CNPMCORE_DATABASE_PORT=3306 \
CNPMCORE_DATABASE_USER= \
CNPMCORE_DATABASE_PASSWORD= \
CNPMCORE_REDIS_HOST= \
CNPMCORE_REDIS_PORT=6379 \
CNPMCORE_REDIS_PASSWORD= \
CNPMCORE_REDIS_DB= \
CNPMCORE_NFS_TYPE=s3 \
CNPMCORE_NFS_S3_CLIENT_ENDPOINT= \
CNPMCORE_NFS_S3_CLIENT_BUCKET= \
CNPMCORE_NFS_S3_CLIENT_ID= \
CNPMCORE_NFS_S3_CLIENT_SECRET= \
CNPMCORE_NFS_S3_CLIENT_FORCE_PATH_STYLE=true \
CNPMCORE_NFS_S3_CLIENT_DISABLE_URL=true \
TZ=Asia/Shanghai
EXPOSE 7001
CMD ["npm", "run", "start:foreground"]

6
.docker/build.sh Normal file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
npm install -g npminstall --registry=https://registry.npmmirror.com \
&& npminstall -c \
&& npm run tsc \
&& npmupdate -c --production

36
.docker/debian/Dockerfile Normal file
View File

@@ -0,0 +1,36 @@
FROM node:22
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
COPY . .
RUN chmod +x .docker/build.sh && .docker/build.sh
ENV NODE_ENV=production \
EGG_SERVER_ENV=prod \
CNPMCORE_CONFIG_REGISTRY= \
CNPMCORE_CONFIG_SOURCE_REGISTRY=https://registry.npmmirror.com \
CNPMCORE_CONFIG_SOURCE_REGISTRY_IS_CNPM=true \
CNPMCORE_DATABASE_TYPE= \
CNPMCORE_DATABASE_NAME= \
CNPMCORE_DATABASE_HOST= \
CNPMCORE_DATABASE_PORT=3306 \
CNPMCORE_DATABASE_USER= \
CNPMCORE_DATABASE_PASSWORD= \
CNPMCORE_REDIS_HOST= \
CNPMCORE_REDIS_PORT=6379 \
CNPMCORE_REDIS_PASSWORD= \
CNPMCORE_REDIS_DB= \
CNPMCORE_NFS_TYPE=s3 \
CNPMCORE_NFS_S3_CLIENT_ENDPOINT= \
CNPMCORE_NFS_S3_CLIENT_BUCKET= \
CNPMCORE_NFS_S3_CLIENT_ID= \
CNPMCORE_NFS_S3_CLIENT_SECRET= \
CNPMCORE_NFS_S3_CLIENT_FORCE_PATH_STYLE=true \
CNPMCORE_NFS_S3_CLIENT_DISABLE_URL=true \
TZ=Asia/Shanghai
EXPOSE 7001
CMD ["npm", "run", "start:foreground"]

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
logs
node_modules
run
typings
.cnpmcore*
coverage

50
.env.example Normal file
View File

@@ -0,0 +1,50 @@
# CNPMCORE_DATABASE_TYPE=MySQL
# CNPMCORE_DATABASE_USER=root
# CNPMCORE_DATABASE_PASSWORD=
# CNPMCORE_DATABASE_NAME=cnpmcore
# CNPMCORE_DATABASE_TYPE=PostgreSQL
# CNPMCORE_DATABASE_USER=postgres
# CNPMCORE_DATABASE_PASSWORD=postgres
# CNPMCORE_DATABASE_NAME=cnpmcore
# CNPMCORE_CONFIG_ENABLE_ES=true
# CNPMCORE_CONFIG_ES_CLIENT_NODE=http://localhost:9200
# CNPMCORE_CONFIG_ES_CLIENT_AUTH_USERNAME=elastic
# CNPMCORE_CONFIG_ES_CLIENT_AUTH_PASSWORD=abcdef
# https://github.com/cnpm/cnpmcore/blob/next/docs/elasticsearch-setup.md#%E6%96%B0%E5%BB%BA-env-%E6%96%87%E4%BB%B6
# Password for the 'elastic' user (at least 6 characters)
ELASTIC_PASSWORD="abcdef"
# Password for the 'kibana_system' user (at least 6 characters)
KIBANA_PASSWORD="abcdef"
# Version of Elastic products
STACK_VERSION=8.7.1
# enable for arm64
# STACK_VERSION_ARM64=-arm64
# STACK_PLATFORM=linux/arm64
# Set the cluster name
CLUSTER_NAME=docker-cluster
# Set to 'basic' or 'trial' to automatically start the 30-day trial
LICENSE=basic
#LICENSE=trial
# Port to expose Elasticsearch HTTP API to the host
ES_PORT=9200
#ES_PORT=127.0.0.1:9200
# Port to expose Kibana to the host
KIBANA_PORT=5601
#KIBANA_PORT=80
# Increase or decrease based on the available host memory (in bytes)
ES_MEM_LIMIT=1073741824
KB_MEM_LIMIT=1073741824
LS_MEM_LIMIT=1073741824
# SAMPLE Predefined Key only to be used in POC environments
ENCRYPTION_KEY=c34d38b3a14956121ff2170e5030b471551370178f43e5626eec58b04a30fae2

View File

@@ -1,7 +0,0 @@
app/proxy*
**/*.d.ts
node_modules/
dist/
coverage/
mocks/
.react_entries/

View File

@@ -1,6 +0,0 @@
{
"extends": "eslint-config-egg/typescript",
"rules": {
}
}

View File

@@ -1,23 +0,0 @@
name: 🤖 ChatGPT Code Review
permissions:
contents: read
pull-requests: write
on:
pull_request:
types: [opened, reopened, synchronize]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: anc95/ChatGPT-CodeReview@main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
# Optional
LANGUAGE: Chinese
MODEL:
top_p: 1
temperature: 1

View File

@@ -1,70 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '41 13 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'typescript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -3,9 +3,74 @@
name: Node.js CI
on: [push, pull_request]
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
test-postgresql-fs-nfs:
runs-on: ${{ matrix.os }}
services:
# https://docs.github.com/en/actions/use-cases-and-examples/using-containerized-services/creating-postgresql-service-containers
# Label used to access the service container
postgres:
# Docker Hub image
image: postgres
# Provide the password for postgres
env:
POSTGRES_PASSWORD: postgres
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 5432 on service container to the host
- 5432:5432
redis:
# https://docs.github.com/en/actions/using-containerized-services/about-service-containers#example-mapping-redis-ports
image: redis
ports:
# Opens tcp port 6379 on the host and service container
- 6379:6379
strategy:
fail-fast: false
matrix:
node-version: [20.18.0, 20, 22]
os: [ubuntu-latest]
steps:
- name: Checkout Git Source
uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install Dependencies
run: npm i -g npminstall && npminstall
- name: Continuous Integration
run: npm run ci:postgresql
env:
# The hostname used to communicate with the PostgreSQL service container
POSTGRES_HOST: localhost
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
# The default PostgreSQL port
POSTGRES_PORT: 5432
- name: Code Coverage
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
test-mysql57-fs-nfs:
runs-on: ${{ matrix.os }}
@@ -28,36 +93,86 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [16, 18, 20]
node-version: [20.18.0, 20, 22]
os: [ubuntu-latest]
steps:
- name: Checkout Git Source
uses: actions/checkout@v3
- name: Checkout Git Source
uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install Dependencies
run: npm i -g npminstall && npminstall
- name: Install Dependencies
run: npm i -g npminstall && npminstall
- name: Continuous Integration
run: npm run ci
- name: Continuous Integration
run: npm run ci
- name: Code Coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Code Coverage
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
test-mysql57-oss-nfs:
# test-mysql57-oss-nfs:
# runs-on: ${{ matrix.os }}
# if: |
# contains('
# refs/heads/master-skip-oss
# refs/heads/dev-skip-oss
# ', github.ref)
# services:
# mysql:
# image: mysql:5.7
# env:
# MYSQL_ALLOW_EMPTY_PASSWORD: true
# MYSQL_DATABASE: cnpmcore_unittest
# ports:
# - 3306:3306
# options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
# redis:
# image: redis
# ports:
# - 6379:6379
# strategy:
# fail-fast: false
# matrix:
# node-version: [20, 22]
# os: [ubuntu-latest]
# steps:
# - name: Checkout Git Source
# uses: actions/checkout@v4
# - name: Use Node.js ${{ matrix.node-version }}
# uses: actions/setup-node@v4
# with:
# node-version: ${{ matrix.node-version }}
# - name: Install Dependencies
# run: npm i
# - name: Continuous Integration
# run: npm run ci
# env:
# CNPMCORE_NFS_TYPE: oss
# CNPMCORE_NFS_OSS_BUCKET: cnpmcore-unittest-github-nodejs-${{ matrix.node-version }}
# CNPMCORE_NFS_OSS_ENDPOINT: https://oss-us-west-1.aliyuncs.com
# CNPMCORE_NFS_OSS_ID: ${{ secrets.CNPMCORE_NFS_OSS_ID }}
# CNPMCORE_NFS_OSS_SECRET: ${{ secrets.CNPMCORE_NFS_OSS_SECRET }}
# - name: Code Coverage
# uses: codecov/codecov-action@v5
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
test-mysql57-s3-nfs:
runs-on: ${{ matrix.os }}
if: |
contains('
refs/heads/master
refs/heads/dev
', github.ref)
services:
mysql:
@@ -70,40 +185,41 @@ jobs:
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
redis:
# https://docs.github.com/en/actions/using-containerized-services/about-service-containers#example-mapping-redis-ports
image: redis
ports:
# Opens tcp port 6379 on the host and service container
- 6379:6379
strategy:
fail-fast: false
matrix:
node-version: [16, 18, 20]
node-version: [20, 22]
os: [ubuntu-latest]
steps:
- name: Checkout Git Source
uses: actions/checkout@v3
- name: Checkout Git Source
uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install Dependencies
run: npm i
- name: Install Dependencies
run: npm i
- name: Continuous Integration
run: npm run ci
env:
CNPMCORE_NFS_TYPE: oss
CNPMCORE_NFS_OSS_BUCKET: cnpmcore-unittest-github-nodejs-${{ matrix.node-version }}
CNPMCORE_NFS_OSS_ENDPOINT: https://oss-us-west-1.aliyuncs.com
CNPMCORE_NFS_OSS_ID: ${{ secrets.CNPMCORE_NFS_OSS_ID }}
CNPMCORE_NFS_OSS_SECRET: ${{ secrets.CNPMCORE_NFS_OSS_SECRET }}
- name: Continuous Integration
run: npm run ci "test/cli/npm/install.test.ts"
env:
CNPMCORE_NFS_TYPE: s3
CNPMCORE_NFS_REMOVE_BEFORE_UPLOAD: true
CNPMCORE_NFS_S3_CLIENT_BUCKET: cnpmcore-unittest-github-nodejs-${{ matrix.node-version }}
CNPMCORE_NFS_S3_CLIENT_ENDPOINT: ${{ secrets.CNPMCORE_NFS_S3_ENDPOINT }}
CNPMCORE_NFS_S3_CLIENT_ID: ${{ secrets.CNPMCORE_NFS_S3_ID }}
CNPMCORE_NFS_S3_CLIENT_SECRET: ${{ secrets.CNPMCORE_NFS_S3_SECRET }}
CNPMCORE_NFS_S3_CLIENT_FORCE_PATH_STYLE: true
# CNPMCORE_NFS_S3_CLIENT_DISABLE_URL: true
- name: Code Coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Code Coverage
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -1,18 +1,12 @@
name: Release
on:
# 合并后自动发布
push:
branches: [ master, main ]
# 手动发布
workflow_dispatch: {}
branches: [ master ]
jobs:
release:
name: Node.js
uses: artusjs/github-actions/.github/workflows/node-release.yml@v1
uses: cnpm/github-actions/.github/workflows/node-release.yml@master
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
with:
checkTest: false

2
.gitignore vendored
View File

@@ -120,3 +120,5 @@ dist
.DS_Store
run
!test/ctx_register.js
.egg/

1
.husky/pre-commit Normal file
View File

@@ -0,0 +1 @@
npx lint-staged

52
.oxlintrc.json Normal file
View File

@@ -0,0 +1,52 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"env": {
"node": true,
"mocha": true
},
"categories": {
"correctness": "error",
"perf": "error"
},
"plugins": [
"import",
"typescript",
"unicorn",
"jsdoc",
"node",
"promise",
"oxc"
],
"rules": {
// eslint
"constructor-super": "error",
"getter-return": "error",
"no-undef": "error",
"no-unreachable": "error",
"no-var": "error",
"no-eq-null": "error",
"no-await-in-loop": "allow",
"eqeqeq": ["error", "smart"],
// import
"import/no-cycle": "error",
"import/no-anonymous-default-export": "error",
"import/no-namespace": "error",
"import/named": "error",
"import/export": "error",
// promise
"promise/no-return-wrap": "error",
"promise/param-names": "error",
"promise/prefer-await-to-callbacks": "error",
"promise/prefer-await-to-then": "error",
"promise/prefer-catch": "error",
"promise/no-return-in-finally": "error",
// unicorn
"unicorn/error-message": "error",
// "unicorn/no-null": "error",
"unicorn/throw-new-error": "error",
// oxc
"oxc/no-map-spread": "error",
// typescript
"typescript/consistent-type-imports": "error"
}
}

6
.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"arrowParens": "avoid"
}

4
.vscode/launch.json vendored
View File

@@ -17,8 +17,6 @@
],
"console": "integratedTerminal",
"restart": true,
"protocol": "auto",
"port": 9229,
"autoAttachChildProcesses": true
},
{
@@ -32,8 +30,6 @@
"--",
"--inspect-brk"
],
"protocol": "auto",
"port": 9229,
"autoAttachChildProcesses": true
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -2,19 +2,37 @@
## 环境初始化
本项目的外部服务依赖有MySQL 数据服务、Redis 缓存服务。
本项目的外部服务依赖有MySQL 数据库或 PostgreSQL 数据库、Redis 缓存服务。
生成本地开发环境配置文件:
```bash
cp .env.example .env
```
可以通过 Docker 来快速启动本地开发环境:
MySQL 开发环境:
```bash
# 启动本地依赖服务
docker-compose up -d
# 启动本地依赖服务 - MySQL + Redis
docker-compose -f docker-compose.yml up -d
# 关闭本地依赖服务
docker-compose down
docker-compose -f docker-compose.yml down
```
> 手动初始化依赖服务参见[文档](./docs/setup.md)
PostgreSQL 开发环境:
```bash
# 启动本地依赖服务 - PostgreSQL + Redis
docker-compose -f docker-compose-postgres.yml up -d
# 关闭本地依赖服务
docker-compose -f docker-compose-postgres.yml down
```
> 手动初始化依赖服务参见[本地开发环境 - MySQL](./docs/setup.md) 或 [本地开发环境 - PostgreSQL](./docs/setup-with-postgresql.md)
## 本地开发
@@ -24,11 +42,11 @@ docker-compose down
npm install
```
### 开发运行
### 开发运行 - MySQL
```bash
# 初始化数据库
MYSQL_DATABASE=cnpmcore bash ./prepare-database.sh
CNPMCORE_DATABASE_NAME=cnpmcore bash ./prepare-database-mysql.sh
# 启动 Web 服务
npm run dev
@@ -37,12 +55,53 @@ npm run dev
curl -v http://127.0.0.1:7001
```
### 开发运行 - PostgreSQL
```bash
# 初始化数据库
CNPMCORE_DATABASE_NAME=cnpmcore bash ./prepare-database-postgresql.sh
# 启动 Web 服务
npm run dev:postgresql
# 访问
curl -v http://127.0.0.1:7001
```
### 登录和测试发包
> cnpmcore 默认不开放注册,可以通过 `config.default.ts` 中的 `allowPublicRegistration` 配置开启,否则只有管理员可以登录
注册 cnpmcore_admin 管理员
```bash
npm login --registry=http://127.0.0.1:7001
# 验证登录
npm whoami --registry=http://127.0.0.1:7001
```
发包
```bash
npm publish --registry=http://127.0.0.1:7001
```
### 单元测试
MySQL
```bash
npm run test
```
PostgreSQL
```bash
npm run test:postgresql
```
## 项目结构
```txt
@@ -268,9 +327,9 @@ Repository 依赖 Model然后被 Service 和 Controller 依赖
可能需要涉及3个地方的修改
1. sql/*.sql
2. repository/model/*.ts
3. core/entity/*.ts
1. `sql/mysql/*.sql`, `sql/postgresql/*.sql`
2. `repository/model/*.ts`
3. `core/entity/*.ts`
目前还不会做 Model 到 SQL 的自动转换生成,核心原因有:

View File

@@ -1,317 +0,0 @@
2.9.0 / 2022-12-15
==================
**features**
* [[`c562645`](http://github.com/cnpm/cnpmcore/commit/c562645db7c88f9c3c5787fd450b457574d1cce6)] - feat: suspend task before app close (#365) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
2.8.1 / 2022-12-05
==================
**features**
* [[`fad30ad`](http://github.com/cnpm/cnpmcore/commit/fad30adc564c931c0bf63828d83bab84105aaef0)] - feat: npm command support npm v6 (#356) (laibao101 <<369632567@qq.com>>)
**fixes**
* [[`f961219`](http://github.com/cnpm/cnpmcore/commit/f961219dbe4676156e1766db82379ee40087bcd8)] - fix: Sync save ignore ER_DUP_ENTRY error (#364) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
**others**
* [[`7bc0fcc`](http://github.com/cnpm/cnpmcore/commit/7bc0fccaca880efe08228b4109953bd3974d2eb9)] - 🤖 TEST: Fix async function mock (fengmk2 <<fengmk2@gmail.com>>)
* [[`84ae9bc`](http://github.com/cnpm/cnpmcore/commit/84ae9bcfa06124255703b926f83fb5e6a6bf9d6b)] - 📖 DOC: Update contributors (fengmk2 <<fengmk2@gmail.com>>)
2.8.0 / 2022-11-29
==================
**others**
* [[`d55c680`](http://github.com/cnpm/cnpmcore/commit/d55c680ef906ecb27f7967782ad7d25987cef7d4)] - Event cork (#361) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
2.7.1 / 2022-11-25
==================
**fixes**
* [[`c6b8aec`](http://github.com/cnpm/cnpmcore/commit/c6b8aecfd0c2b0d454389e931747c431dac5742b)] - fix: request binary error (#360) (Ke Wu <<gemwuu@163.com>>)
2.7.0 / 2022-11-25
==================
**others**
* [[`5738d56`](http://github.com/cnpm/cnpmcore/commit/5738d569ea691c05c3f3b0b74a454a33fefb8fc7)] - refactor: binary sync task use binaryName by default (#358) (Ke Wu <<gemwuu@163.com>>)
2.6.1 / 2022-11-23
==================
**fixes**
* [[`0b35ead`](http://github.com/cnpm/cnpmcore/commit/0b35ead2a0cd73b89d2d961bafec13d7250fe805)] - 🐛 FIX: typo for canvas (fengmk2 <<fengmk2@gmail.com>>)
2.6.0 / 2022-11-23
==================
**features**
* [[`be8387d`](http://github.com/cnpm/cnpmcore/commit/be8387dfa48b9487156542000a93081fa823694a)] - feat: Support canvas sync from different binary (#357) (Ke Wu <<gemwuu@163.com>>)
**fixes**
* [[`d6c4cf5`](http://github.com/cnpm/cnpmcore/commit/d6c4cf5029ca6450064fc05696a8624b6c36f0b2)] - fix: duplicate binary task (#354) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
2.5.2 / 2022-11-11
==================
**fixes**
* [[`7eb209d`](http://github.com/cnpm/cnpmcore/commit/7eb209de1332417db2070846891d78f5afa0cd10)] - fix: create task when waiting (#352) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
2.5.1 / 2022-11-07
==================
**others**
* [[`e40c502`](http://github.com/cnpm/cnpmcore/commit/e40c5021bb2ba78f8879d19bc477883168560b85)] - 🐛 FIX: Mirror cypress arm64 binary (#351) (fengmk2 <<fengmk2@gmail.com>>)
2.5.0 / 2022-11-04
==================
**features**
* [[`43d77ee`](http://github.com/cnpm/cnpmcore/commit/43d77ee91e52bd74594d9d569b839c1a4b7fbac6)] - feat: long description (#349) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
2.4.1 / 2022-10-28
==================
**features**
* [[`92350a8`](http://github.com/cnpm/cnpmcore/commit/92350a864313ee42a048d9e83886ef42db3419de)] - 👌 IMPROVE: Show changes stream create task log (#347) (fengmk2 <<fengmk2@gmail.com>>)
**fixes**
* [[`28eeeaf`](http://github.com/cnpm/cnpmcore/commit/28eeeafd9870c6b1c5b4f4c23916f6ae73ddda12)] - fix: registry host config (#346) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
* [[`cd5bd92`](http://github.com/cnpm/cnpmcore/commit/cd5bd923b8d47bf90b5f077ce04777b38653b850)] - 🐛 FIX: Catch all error on changes stream handler (#344) (fengmk2 <<fengmk2@gmail.com>>)
2.4.0 / 2022-10-25
==================
**features**
* [[`6aa302d`](http://github.com/cnpm/cnpmcore/commit/6aa302d074f2c84f39e2065fa20853b007f6fa3b)] - 📦 NEW: Use oss-cnpm v4 (#340) (fengmk2 <<fengmk2@gmail.com>>)
* [[`a217fd0`](http://github.com/cnpm/cnpmcore/commit/a217fd07ccad3fe5058881654a13e0c69c758717)] - 👌 IMPROVE: Reduce warning log (#326) (fengmk2 <<fengmk2@gmail.com>>)
**fixes**
* [[`b19b0a0`](http://github.com/cnpm/cnpmcore/commit/b19b0a0496e35ac1c6b3de746b9221990ba9dc93)] - fix: Lazy set registryId when executeTask (#341) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
**others**
* [[`305175a`](http://github.com/cnpm/cnpmcore/commit/305175ab5fcdc3ad3b60055d45cfcacb23065a80)] - 🤖 TEST: Use enum define on unittest (#333) (fengmk2 <<fengmk2@gmail.com>>)
* [[`07f2eba`](http://github.com/cnpm/cnpmcore/commit/07f2eba137ba625b2d422677a465920617141b87)] - 🤖 TEST: Mock all binary http requests (#328) (fengmk2 <<fengmk2@gmail.com>>)
* [[`4b0c7dc`](http://github.com/cnpm/cnpmcore/commit/4b0c7dc6196960d34b2529bfde724e97f1af8444)] - 🤖 TEST: Mock all httpclient request (#327) (fengmk2 <<fengmk2@gmail.com>>)
2.3.1 / 2022-10-06
==================
**features**
* [[`bbc08fd`](http://github.com/cnpm/cnpmcore/commit/bbc08fd26887d55b98b70d1ed210caf81f9d5c22)] - 👌 IMPROVE: syncPackageWorkerMaxConcurrentTasks up to 20 (#322) (fengmk2 <<fengmk2@gmail.com>>)
* [[`5852f22`](http://github.com/cnpm/cnpmcore/commit/5852f22023525d857ff1ceea205e4315c8079877)] - feat: support sync exist mode (#275) (zhangyuantao <<zhangyuantao@163.com>>)
**fixes**
* [[`d79634e`](http://github.com/cnpm/cnpmcore/commit/d79634eea749fef1a420988a8599f156f28ee85a)] - 🐛 FIX: Should sync package when registry id is null (#324) (fengmk2 <<fengmk2@gmail.com>>)
* [[`24f920d`](http://github.com/cnpm/cnpmcore/commit/24f920d65b31f9eb83c1ecda36adf7f9e2c379c3)] - 🐛 FIX: Should run sync package on all worker (#323) (fengmk2 <<fengmk2@gmail.com>>)
2.3.0 / 2022-09-24
==================
**others**
* [[`bd83a19`](http://github.com/cnpm/cnpmcore/commit/bd83a19eca761c96bcee04e6ae91e68eac3cb6bf)] - 👌 IMPROVE: use urllib3 instead (#302) (fengmk2 <<fengmk2@gmail.com>>)
* [[`35e7d3a`](http://github.com/cnpm/cnpmcore/commit/35e7d3ad3c78712b507d522a0b72b5a6a5a4ec1c)] - 👌 IMPROVE: Enable phpmyadmin and DEBUG_LOCAL_SQL by default (#320) (fengmk2 <<fengmk2@gmail.com>>)
2.2.0 / 2022-09-22
==================
**features**
* [[`bca0fb3`](http://github.com/cnpm/cnpmcore/commit/bca0fb3c37b9f74f3c41ab181dd3113d9dab4c05)] - feat: only allow pkg sync from registry it belong (#317) (killa <<killa123@126.com>>)
**fixes**
* [[`7e9beea`](http://github.com/cnpm/cnpmcore/commit/7e9beead576a41de3aa042b92b788bde5d55f44a)] - fix: only append / if path is not empty and not ends with / (#316) (killa <<killa123@126.com>>)
* [[`4fe68cb`](http://github.com/cnpm/cnpmcore/commit/4fe68cbf38f303e797b80b88407f714ec76bfae0)] - fix: fix directory path (#313) (killa <<killa123@126.com>>)
**others**
* [[`e72ce35`](http://github.com/cnpm/cnpmcore/commit/e72ce3576f9a3cda095e3feac59eeb1d8c1e8033)] - 🤖 TEST: Skip unstable tests (#318) (fengmk2 <<fengmk2@gmail.com>>)
* [[`171b11f`](http://github.com/cnpm/cnpmcore/commit/171b11f7bba534c993af4088b00f8545216734a9)] - Revert "fix: fix directory path (#313)" (fengmk2 <<fengmk2@gmail.com>>)
2.1.1 / 2022-09-08
==================
**fixes**
* [[`8fb9dd8`](http://github.com/cnpm/cnpmcore/commit/8fb9dd8cf4800afe3f54aba9ee4c0ae05efb4f1d)] - fix: findExecuteTask only return waiting task (#312) (killa <<killa123@126.com>>)
2.1.0 / 2022-09-05
==================
**features**
* [[`c5d2b49`](http://github.com/cnpm/cnpmcore/commit/c5d2b49ab3a0ce0d67f6e7cc19e0be867c92d04c)] - feat: auto get next valid task (#311) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
2.0.0 / 2022-09-05
==================
**others**
* [[`fc4baff`](http://github.com/cnpm/cnpmcore/commit/fc4baff226540e7cfee9adc069e17a59f4050a43)] - chore: refactor schedule with @Schedule (#309) (killa <<killa123@126.com>>)
1.11.6 / 2022-09-04
==================
**fixes**
* [[`768f951`](http://github.com/cnpm/cnpmcore/commit/768f951b6f2509f14c30a70d86a6719107d963a4)] - fix: cnpmjsorg changesstream limit (#310) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
1.11.5 / 2022-09-02
==================
**fixes**
* [[`f673ab8`](http://github.com/cnpm/cnpmcore/commit/f673ab8ba1545909ff6b8e445364646511930891)] - fix: execute state check (#308) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
**others**
* [[`091420a`](http://github.com/cnpm/cnpmcore/commit/091420ae2677ecedd1a26a238921321c2a191675)] - 🤖 TEST: Add SQL Review Action (#307) (fengmk2 <<fengmk2@gmail.com>>)
1.11.4 / 2022-08-30
==================
**fixes**
* [[`f9210ca`](http://github.com/cnpm/cnpmcore/commit/f9210ca7e180e19bce08da9ef33e46e990b86ef1)] - fix: changes stream empty (#306) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
1.11.3 / 2022-08-29
==================
**fixes**
* [[`48f228d`](http://github.com/cnpm/cnpmcore/commit/48f228da447d8cde62849fa52cf43bae7754e2e3)] - fix: changes stream updatedAt (#304) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
* [[`87045ba`](http://github.com/cnpm/cnpmcore/commit/87045ba8b0e14547c93689600eb7e2c1de2a611b)] - fix: task updatedAt save (#305) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
1.11.2 / 2022-08-28
==================
**fixes**
* [[`4e8700c`](http://github.com/cnpm/cnpmcore/commit/4e8700c4f7c6fb5c4f4d4a2b9a9546096c5d10e2)] - fix: only create createHookTask if hook enable (#299) (killa <<killa123@126.com>>)
**others**
* [[`e06c841`](http://github.com/cnpm/cnpmcore/commit/e06c841537113fdb0c00beb22b0a55378c61ce80)] - 🐛 FIX: Should sync public package when registryName not exists (#303) (fengmk2 <<fengmk2@gmail.com>>)
* [[`f139444`](http://github.com/cnpm/cnpmcore/commit/f139444213403494ebe9bf073df62125413892d9)] - 📖 DOC: Update contributors (fengmk2 <<fengmk2@gmail.com>>)
* [[`c4a9de5`](http://github.com/cnpm/cnpmcore/commit/c4a9de598dce9a1b82bbcdd91968a15bbc5a4b6b)] - Create SECURITY.md (fengmk2 <<fengmk2@gmail.com>>)
* [[`709d65b`](http://github.com/cnpm/cnpmcore/commit/709d65bd0473856c9bfc4416ea2ca375136e354f)] - 🤖 TEST: Use diff bucket on OSS test (#301) (fengmk2 <<fengmk2@gmail.com>>)
* [[`9576699`](http://github.com/cnpm/cnpmcore/commit/95766990fa9c4c2c43d462f6b151557425b0c741)] - chore: use AsyncGenerator insteadof Transform stream (#300) (killa <<killa123@126.com>>)
* [[`3ed5269`](http://github.com/cnpm/cnpmcore/commit/3ed5269f1d22ca3aaca89a90a4fff90f293e2464)] - 📦 NEW: Mirror better-sqlite3 binary (#296) (fengmk2 <<fengmk2@gmail.com>>)
1.11.1 / 2022-08-24
==================
**fixes**
* [[`359a150`](http://github.com/cnpm/cnpmcore/commit/359a150eb450d69e6523b20efcc5c7cfe3efab4d)] - fix: changes stream (#297) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
1.11.0 / 2022-08-23
==================
**features**
* [[`a91c8ac`](http://github.com/cnpm/cnpmcore/commit/a91c8ac4d05dc903780fda516b09364a05a2b1e6)] - feat: sync package from spec regsitry (#293) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
* [[`de37008`](http://github.com/cnpm/cnpmcore/commit/de37008261b05845f392d66764cdfe14ae324756)] - feat: changesStream adapter & needSync() method (#292) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
* [[`4b506c8`](http://github.com/cnpm/cnpmcore/commit/4b506c8371697ddacdbe99a8ecb330bfc1911ec6)] - feat: init registry & scope (#286) (elrrrrrrr <<elrrrrrrr@gmail.com>>)
* [[`41c6e24`](http://github.com/cnpm/cnpmcore/commit/41c6e24c84d546eb9d5515cc0940cc3e4274687b)] - feat: impl trigger Hooks (#289) (killa <<killa123@126.com>>)
* [[`79cb826`](http://github.com/cnpm/cnpmcore/commit/79cb82615f04bdb3da3ccbe09bb6a861608b69c5)] - feat: impl migration sql (#290) (killa <<killa123@126.com>>)
* [[`4cfa8ed`](http://github.com/cnpm/cnpmcore/commit/4cfa8ed9d687ce7d950d7d20c0ea28221763ba5f)] - feat: impl hooks api (#287) (killa <<killa123@126.com>>)
* [[`47d53d2`](http://github.com/cnpm/cnpmcore/commit/47d53d22ad03c02ee9cb9035a38ae205a6d38381)] - feat: add bizId for task (#285) (killa <<killa123@126.com>>)
* [[`3b1536b`](http://github.com/cnpm/cnpmcore/commit/3b1536b070b2f9062bc2cc377db96d2f4a160efc)] - feat: add node-webrtc mirror (#274) (Opportunity <<opportunity@live.in>>)
**others**
* [[`7106807`](http://github.com/cnpm/cnpmcore/commit/710680742a078b2faf4cb18c3a39c0397308712e)] - 🐛 FIX: Should show queue size on logging (#280) (fengmk2 <<fengmk2@gmail.com>>)
* [[`3a41b21`](http://github.com/cnpm/cnpmcore/commit/3a41b2161cc99bb2f6f6dd7cbaa7abef25ff4393)] - 🐛 FIX: Handle binary configuration value (#278) (fengmk2 <<fengmk2@gmail.com>>)
1.10.0 / 2022-08-04
==================
**features**
* [[`c2b7d5a`](http://github.com/cnpm/cnpmcore/commit/c2b7d5aa98b5ba8649ec246c616574a22e9a74b8)] - feat: use sort set to impl queue (#277) (killa <<killa123@126.com>>)
1.9.1 / 2022-07-29
==================
**fixes**
* [[`c54aa21`](http://github.com/cnpm/cnpmcore/commit/c54aa2165c3938dcbb5a2b3b54e66a0d961cc813)] - fix: check executingCount after task is done (#276) (killa <<killa123@126.com>>)
**others**
* [[`3268d03`](http://github.com/cnpm/cnpmcore/commit/3268d030b620825c8c2e6331e1745c1788066c61)] - 🤖 TEST: show package not use cache if isSync (#273) (fengmk2 <<fengmk2@gmail.com>>)
1.9.0 / 2022-07-25
==================
**features**
* [[`af6a75a`](http://github.com/cnpm/cnpmcore/commit/af6a75af32ea04c90fda82be3a56c99ec77e5807)] - feat: add forceSyncHistory options (#271) (killa <<killa123@126.com>>)
1.8.0 / 2022-07-21
==================
**features**
* [[`b49a38c`](http://github.com/cnpm/cnpmcore/commit/b49a38c77e044c978e6de32a9d3e257cc90ea7c1)] - feat: use Model with inject (#269) (killa <<killa123@126.com>>)
1.7.1 / 2022-07-20
==================
**fixes**
* [[`52fca55`](http://github.com/cnpm/cnpmcore/commit/52fca55aa883865f0ae70bfc1ff274c313b8f76a)] - fix: show package not use cache if isSync (#268) (killa <<killa123@126.com>>)
1.7.0 / 2022-07-12
==================
**others**
* [[`4f7ce8b`](http://github.com/cnpm/cnpmcore/commit/4f7ce8b4b2a5806a225ce67228388e14388b7059)] - deps: upgrade leoric to 2.x (#262) (killa <<killa123@126.com>>)
1.6.0 / 2022-07-11
==================
**features**
* [[`1b9a9c7`](http://github.com/cnpm/cnpmcore/commit/1b9a9c70f66d8393e3b132f18713461a9243db73)] - feat: mirror nydus binaries (#261) (killa <<killa123@126.com>>)
**others**
* [[`c1256bf`](http://github.com/cnpm/cnpmcore/commit/c1256bf3807bcc9a5c8be2ec5bf5ca8a5eef112e)] - 🐛 FIX: Ignore 403 status on s3 download fail (#260) (fengmk2 <<fengmk2@gmail.com>>)
* [[`d685772`](http://github.com/cnpm/cnpmcore/commit/d6857724307fb0df0c4c118491784b30d19a9a15)] - 🐛 FIX: skia-canvas should use NodePreGypBinary (#259) (fengmk2 <<fengmk2@gmail.com>>)
1.5.0 / 2022-07-09
==================
**features**
* [[`b15b10c`](http://github.com/cnpm/cnpmcore/commit/b15b10c5c6cfb32bcc2b1d94434cdd16871ae565)] - feat(mirror): add skia-canvas mirror (#258) (Beace <<beaceshimin@gmail.com>>)
**others**
* [[`2bd6ed0`](http://github.com/cnpm/cnpmcore/commit/2bd6ed0e5dace1d8840c342ecf4c86e8973dc6b7)] - 👌 IMPROVE: use tegg@1.2.0 (fengmk2 <<fengmk2@gmail.com>>)
1.4.0 / 2022-06-28
==================
**features**
* [[`57da0a3`](http://github.com/cnpm/cnpmcore/commit/57da0a3c7e56d6613b57391948949ffea24ec058)] - feat: add configuration enableNopmClientAndVersionCheck (laibao101 <<369632567@qq.com>>)
**others**
* [[`bf62932`](http://github.com/cnpm/cnpmcore/commit/bf62932f2e5224de6e34b873bf690a6e887b94b0)] - 🤖 TEST: Fix unstable test cases on OSS env (#254) (fengmk2 <<fengmk2@gmail.com>>)
1.3.2 / 2022-06-27
==================
**fixes**
* [[`c63159d`](http://github.com/cnpm/cnpmcore/commit/c63159d8df804fe711b664606fe42be42010eb38)] - fix: valid npm client with correct pattern (#252) (TZ | 天猪 <<atian25@qq.com>>)
**others**
* [[`d578baf`](http://github.com/cnpm/cnpmcore/commit/d578bafff07a0f9d4dd75393492cffc7f5d2660b)] - 🐛 FIX: Ignore exists seq on changes worker (#253) (fengmk2 <<fengmk2@gmail.com>>)
1.3.1 / 2022-06-24
==================
**fixes**
* [[`4ea0ef6`](http://github.com/cnpm/cnpmcore/commit/4ea0ef63b7af9fd4dcc247c2c2ac8e4d579f941a)] - fix: query changes with order by id asc (#251) (killa <<killa123@126.com>>)
1.3.0 / 2022-06-24
==================
**features**
* [[`0948a71`](http://github.com/cnpm/cnpmcore/commit/0948a71a40ac4897d129ef56830665dc028f07c7)] - feat: read enableChangesStream when sync changes stream (#250) (killa <<killa123@126.com>>)
1.2.0 / 2022-06-20
==================
**others**
* [[`c0d8b52`](http://github.com/cnpm/cnpmcore/commit/c0d8b52ea09736ac11b0ef780aec781d172fb94c)] - refactor: move CacheAdapter to ContextProto (#249) (killa <<killa123@126.com>>)
1.1.0 / 2022-06-20
==================
**features**
* [[`66b411e`](http://github.com/cnpm/cnpmcore/commit/66b411ea5bf6192dc9509df408525078e7128a27)] - feat: add type for exports (#248) (killa <<killa123@126.com>>)
1.0.0 / 2022-06-17
==================
**others**
* [[`5cadbf4`](http://github.com/cnpm/cnpmcore/commit/5cadbf4b22bee7d85cd14526f5d6c6e2cd3a2e4b)] - refactor: add infra module (#245) (killa <<killa123@126.com>>),fatal: No names found, cannot describe anything.

View File

@@ -1,4 +1,5 @@
# 🥚 如何在 [tegg](https://github.com/eggjs/tegg) 中集成 cnpmcore
> 文档中的示例项目可以在 [这里](https://github.com/eggjs/examples/commit/bed580fe053ae573f8b63f6788002ff9c6e7a142) 查看,在开始前请确保已阅读 [DEVELOPER.md](DEVELOPER.md) 中的相关文档,完成本地开发环境搭建。
在生产环境中,我们也可以直接部署 cnpmcore 系统,实现完整的 Registry 镜像功能。
@@ -12,7 +13,8 @@
## 🚀 快速开始
### 🆕 新建一个 tegg 应用
> 我们以 https://github.com/eggjs/examples/tree/master/hello-tegg 为例
> 我们以 <https://github.com/eggjs/examples/tree/master/hello-tegg> 为例
```shell
.
@@ -37,25 +39,25 @@
```
1. 修改 `ts-config.json` 配置,这是因为 cnpmcore 使用了 [subPath](https://nodejs.org/api/packages.html#subpath-exports)
```json
{
"extends": "@eggjs/tsconfig",
"compilerOptions": {
"baseUrl": "./",
"moduleResolution": "NodeNext",
"target": "ES2020",
"module": "Node16"
"target": "ES2021"
}
}
```
2. 修改 `config/plugin.ts` 文件,开启 cnpmcore 依赖的一些插件
```typescript
// 开启如下插件
{
redis: {
enable: true,
package: 'egg-redis',
package: '@eggjs/redis',
},
teggOrm: {
enable: true,
@@ -67,7 +69,7 @@
},
tracer: {
enable: true,
package: 'egg-tracer',
package: '@eggjs/tracer',
},
typeboxValidate: {
enable: true,
@@ -76,7 +78,24 @@
}
```
3. 修改 `config.default.ts` 文件,可以直接复制 cnpmcore 中的内容
3. 修改 `config.default.ts` 文件,可以直接覆盖默认配置
```typescript
import { SyncMode } from 'cnpmcore/common/constants';
import { cnpmcoreConfig } from 'cnpmcore/common/config';
export default () => {
const config = {};
config.cnpmcore = {
...cnpmcoreConfig,
enableChangesStream: false,
syncMode: SyncMode.all,
allowPublicRegistration: true,
// 放开注册配置
};
return config;
}
```
### 🧑‍🤝‍🧑 集成 cnpmcore
@@ -90,7 +109,7 @@
│   └── package.json
```
* 添加 `package.json` ,声明 infra 作为一个 eggModule 单元
* 添加 `package.json` ,声明 infra 作为一个 eggModule 单元
```JSON
{
@@ -101,7 +120,7 @@
}
```
* 添加 `XXXAdapter.ts` 在对应的 Adapter 中继承 cnpmcore 默认的 Adapter以 AuthAdapter 为例
* 添加 `XXXAdapter.ts` 在对应的 Adapter 中继承 cnpmcore 默认的 Adapter以 AuthAdapter 为例
```typescript
import { AccessLevel, SingletonProto } from '@eggjs/tegg';
@@ -145,12 +164,14 @@
我们以 AuthAdapter 为例,来实现 npm cli 的 SSO 登录的功能。
我们需要实现了 getAuthUrl 和 ensureCurrentUser 这两个方法:
1. getAuthUrl 引导用户访问企业内实际的登录中心。
2. ensureCurrentUser 当用户完成访问后,需要回调到应用进行鉴权流程。
我们约定通过 `POST /-/v1/login/sso/:sessionId` 这个路由来进行登录验证。
当然,你也可以任意修改地址和登录回调,只需保证更新 redis 中的 token 状态即可。
修改 AuthAdapter.ts 文件
```typescript
import { AccessLevel, EggContext, SingletonProto } from '@eggjs/tegg';
import { AuthAdapter } from 'cnpmcore/infra/AuthAdapter';
@@ -185,6 +206,7 @@ export class MyAuthAdapter extends AuthAdapter {
```
修改 HelloController 的实现,实际也可以通过登录中心回调、页面确认等方式实现
```typescript
// 触发回调接口,会自动完成用户创建
await this.httpclient.request(`${ctx.origin}/-/v1/login/sso/${name}`, { method: 'POST' });
@@ -195,22 +217,24 @@ export class MyAuthAdapter extends AuthAdapter {
1. 在命令行输入 `npm login --registry=http://127.0.0.1:7001`
```shell
$ npm login --registry=http://127.0.0.1:7001
$ npm notice Log in on http://127.0.0.1:7001/
$ Login at:
$ http://127.0.0.1:7001/hello?name=e44e8c43-211a-4bcd-ae78-c4cbb1a78ae7
$ Press ENTER to open in the browser...
npm login --registry=http://127.0.0.1:7001
npm notice Log in on http://127.0.0.1:7001/
Login at:
http://127.0.0.1:7001/hello?name=e44e8c43-211a-4bcd-ae78-c4cbb1a78ae7
Press ENTER to open in the browser...
```
2. 界面提示回车打开浏览器访问登录中心,也就是我们在 getAuthUrl返回的 loginUrl 配置
3. 由于我们 mock 了对应实现,界面会直接显示登录成功
```shell
Logged in on http://127.0.0.1:7001/.
```
4. 在命令行输入 `npm whoami --registry=http://127.0.0.1:7001` 验证
```shell
$ npm whoami --registry=http://127.0.0.1:7001
$ hello
npm whoami --registry=http://127.0.0.1:7001
hello
```

View File

@@ -1,16 +1,17 @@
# Private NPM Registry for Enterprise
[![Node.js CI](https://github.com/cnpm/cnpmcore/actions/workflows/nodejs.yml/badge.svg)](https://github.com/cnpm/cnpmcore/actions/workflows/nodejs.yml)
[![codecov](https://codecov.io/gh/cnpm/cnpmcore/branch/main/graph/badge.svg)](https://codecov.io/gh/cnpm/cnpmcore)
[![CodeQL](https://github.com/cnpm/cnpmcore/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/cnpm/cnpmcore/actions/workflows/codeql-analysis.yml)
[![emoji-log](https://cdn.rawgit.com/ahmadawais/stuff/ca97874/emoji-log/non-flat-round.svg)](https://github.com/ahmadawais/Emoji-Log/)
[![Node.js CI](https://github.com/cnpm/cnpmcore/actions/workflows/nodejs.yml/badge.svg?branch=master)](https://github.com/cnpm/cnpmcore/actions/workflows/nodejs.yml)
[![codecov](https://codecov.io/gh/cnpm/cnpmcore/master/main/graph/badge.svg)](https://app.codecov.io/gh/cnpm/cnpmcore/tree/master)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fcnpm%2Fcnpmcore.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fcnpm%2Fcnpmcore?ref=badge_shield)
[![Node.js Version](https://img.shields.io/node/v/cnpmcore.svg?style=flat)](https://nodejs.org/en/download/)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)
![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/cnpm/cnpmcore)
Reimplementation based on [cnpmjs.org](https://github.com/cnpm/cnpmjs.org) with TypeScript.
Reimplement based on [cnpmjs.org](https://github.com/cnpm/cnpmjs.org) with TypeScript.
## Registry HTTP API
See https://github.com/cnpm/cnpmjs.org/blob/master/docs/registry-api.md#npm-registry-api
See [registry-api.md](docs/registry-api.md)
## How to contribute
@@ -24,18 +25,10 @@ See [INTEGRATE.md](INTEGRATE.md)
[MIT](LICENSE)
<!-- GITCONTRIBUTOR_START -->
## Contributors
|[<img src="https://avatars.githubusercontent.com/u/156269?v=4" width="100px;"/><br/><sub><b>fengmk2</b></sub>](https://github.com/fengmk2)<br/>|[<img src="https://avatars.githubusercontent.com/u/6897780?v=4" width="100px;"/><br/><sub><b>killagu</b></sub>](https://github.com/killagu)<br/>|[<img src="https://avatars.githubusercontent.com/u/32174276?v=4" width="100px;"/><br/><sub><b>semantic-release-bot</b></sub>](https://github.com/semantic-release-bot)<br/>|[<img src="https://avatars.githubusercontent.com/u/5574625?v=4" width="100px;"/><br/><sub><b>elrrrrrrr</b></sub>](https://github.com/elrrrrrrr)<br/>|[<img src="https://avatars.githubusercontent.com/u/35598090?v=4" width="100px;"/><br/><sub><b>hezhengxu2018</b></sub>](https://github.com/hezhengxu2018)<br/>|[<img src="https://avatars.githubusercontent.com/u/26033663?v=4" width="100px;"/><br/><sub><b>Zian502</b></sub>](https://github.com/Zian502)<br/>|
| :---: | :---: | :---: | :---: | :---: | :---: |
|[<img src="https://avatars.githubusercontent.com/u/4635838?v=4" width="100px;"/><br/><sub><b>gemwuu</b></sub>](https://github.com/gemwuu)<br/>|[<img src="https://avatars.githubusercontent.com/u/17879221?v=4" width="100px;"/><br/><sub><b>laibao101</b></sub>](https://github.com/laibao101)<br/>|[<img src="https://avatars.githubusercontent.com/u/3478550?v=4" width="100px;"/><br/><sub><b>coolyuantao</b></sub>](https://github.com/coolyuantao)<br/>|[<img src="https://avatars.githubusercontent.com/u/13284978?v=4" width="100px;"/><br/><sub><b>Beace</b></sub>](https://github.com/Beace)<br/>|[<img src="https://avatars.githubusercontent.com/u/10163680?v=4" width="100px;"/><br/><sub><b>Wellaiyo</b></sub>](https://github.com/Wellaiyo)<br/>|[<img src="https://avatars.githubusercontent.com/u/227713?v=4" width="100px;"/><br/><sub><b>atian25</b></sub>](https://github.com/atian25)<br/>|
|[<img src="https://avatars.githubusercontent.com/u/8198408?v=4" width="100px;"/><br/><sub><b>BlackHole1</b></sub>](https://github.com/BlackHole1)<br/>|[<img src="https://avatars.githubusercontent.com/u/1814071?v=4" width="100px;"/><br/><sub><b>xiekw2010</b></sub>](https://github.com/xiekw2010)<br/>|[<img src="https://avatars.githubusercontent.com/u/13471233?v=4" width="100px;"/><br/><sub><b>OpportunityLiu</b></sub>](https://github.com/OpportunityLiu)<br/>|[<img src="https://avatars.githubusercontent.com/u/958063?v=4" width="100px;"/><br/><sub><b>thonatos</b></sub>](https://github.com/thonatos)<br/>|[<img src="https://avatars.githubusercontent.com/u/11039003?v=4" width="100px;"/><br/><sub><b>chenpx976</b></sub>](https://github.com/chenpx976)<br/>|[<img src="https://avatars.githubusercontent.com/u/29791463?v=4" width="100px;"/><br/><sub><b>fossabot</b></sub>](https://github.com/fossabot)<br/>|
[<img src="https://avatars.githubusercontent.com/u/1119126?v=4" width="100px;"/><br/><sub><b>looksgood</b></sub>](https://github.com/looksgood)<br/>|[<img src="https://avatars.githubusercontent.com/u/23701019?v=4" width="100px;"/><br/><sub><b>laoboxie</b></sub>](https://github.com/laoboxie)<br/>|[<img src="https://avatars.githubusercontent.com/u/5550931?v=4" width="100px;"/><br/><sub><b>shinima</b></sub>](https://github.com/shinima)<br/>
[![Contributors](https://contrib.rocks/image?repo=cnpm/cnpmcore)](https://github.com/cnpm/cnpmcore/graphs/contributors)
This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Sat May 06 2023 12:40:20 GMT+0800`.
<!-- GITCONTRIBUTOR_END -->
Made with [contributors-img](https://contrib.rocks).
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fcnpm%2Fcnpmcore.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fcnpm%2Fcnpmcore?ref=badge_large)

View File

@@ -6,7 +6,7 @@ Currently being supported with security updates.
| Version | Supported |
| -------- | ------------------ |
| >= 1.0.0 | :white_check_mark: |
| >= 3.0.0 | :white_check_mark: |
## Reporting a Vulnerability
@@ -35,7 +35,7 @@ When the security team receives a security bug report, they will assign it
to a primary handler. This person will coordinate the fix and release
process, involving the following steps:
* Confirm the problem and determine the affected versions.
* Audit code to find any potential similar problems.
* Prepare fixes for all releases still under maintenance. These fixes
* Confirm the problem and determine the affected versions.
* Audit code to find any potential similar problems.
* Prepare fixes for all releases still under maintenance. These fixes
will be released as fast as possible to NPM.

21
app.ts
View File

@@ -1,14 +1,15 @@
import path from 'path';
import { readFile } from 'fs/promises';
import { Application } from 'egg';
import { ChangesStreamService } from './app/core/service/ChangesStreamService';
import path from 'node:path';
import { readFile } from 'node:fs/promises';
import type { Application, ILifecycleBoot } from 'egg';
import { ChangesStreamService } from './app/core/service/ChangesStreamService.js';
declare module 'egg' {
interface Application {
binaryHTML: string;
}
}
export default class CnpmcoreAppHook {
export default class CnpmcoreAppHook implements ILifecycleBoot {
private readonly app: Application;
constructor(app: Application) {
@@ -16,7 +17,7 @@ export default class CnpmcoreAppHook {
this.app.binaryHTML = '';
}
async configWillLoad() {
configWillLoad() {
const app = this.app;
// https://github.com/eggjs/tegg/blob/master/plugin/orm/app.ts#L37
// store query sql to log
@@ -33,13 +34,17 @@ export default class CnpmcoreAppHook {
// ready binary.html and replace registry
const filepath = path.join(this.app.baseDir, 'app/port/binary.html');
const text = await readFile(filepath, 'utf-8');
this.app.binaryHTML = text.replace('{{registry}}', this.app.config.cnpmcore.registry);
this.app.binaryHTML = text.replace(
'{{registry}}',
this.app.config.cnpmcore.registry
);
}
// 应用退出时执行
// 需要暂停当前执行的 changesStream task
async beforeClose() {
const changesStreamService = await this.app.getEggObject(ChangesStreamService);
const changesStreamService =
await this.app.getEggObject(ChangesStreamService);
await changesStreamService.suspendSync(true);
}
}

View File

@@ -1,10 +1,5 @@
import {
Inject,
} from '@eggjs/tegg';
import {
EggAppConfig,
EggLogger,
} from 'egg';
import { Inject } from '@eggjs/tegg';
import type { EggAppConfig, EggLogger } from 'egg';
export abstract class AbstractService {
@Inject()

View File

@@ -1,4 +1,5 @@
import { generateKeyPairSync, publicEncrypt, privateDecrypt, constants } from 'crypto';
import { generateKeyPairSync } from 'node:crypto';
import NodeRSA from 'node-rsa';
// generate rsa key pair
export function genRSAKeys(): { publicKey: string, privateKey: string } {
@@ -17,17 +18,19 @@ export function genRSAKeys(): { publicKey: string, privateKey: string } {
}
// encrypt rsa private key
export function encryptRSA(publicKey: string, data: string): string {
return publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_PADDING,
}, Buffer.from(data, 'utf8')).toString('base64');
export function encryptRSA(publicKey: string, plainText: string): string {
const key = new NodeRSA(publicKey, 'pkcs1-public-pem', {
encryptionScheme: 'pkcs1',
environment: 'browser',
});
return key.encrypt(plainText, 'base64');
}
// decrypt rsa private key
export function decryptRSA(privateKey: string, data: string) {
return privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_PADDING,
}, Buffer.from(data, 'base64')).toString('utf8');
export function decryptRSA(privateKey: string, encryptedBase64: string): string {
const key = new NodeRSA(privateKey, 'pkcs1-private-pem', {
encryptionScheme: 'pkcs1',
environment: 'browser',
});
return key.decrypt(encryptedBase64, 'utf8');
}

27
app/common/ErrorUtil.ts Normal file
View File

@@ -0,0 +1,27 @@
const TimeoutErrorNames = new Set([
'HttpClientRequestTimeoutError',
'HttpClientConnectTimeoutError',
'ConnectionError',
'ConnectTimeoutError',
'BodyTimeoutError',
'ResponseTimeoutError',
]);
export function isTimeoutError(err: Error) {
if (TimeoutErrorNames.has(err.name)) {
return true;
}
if (err instanceof AggregateError && err.errors) {
for (const subError of err.errors) {
if (TimeoutErrorNames.has(subError.name)) {
return true;
}
}
}
if ('cause' in err && err.cause instanceof Error) {
if (TimeoutErrorNames.has(err.cause.name)) {
return true;
}
}
return false;
}

View File

@@ -4,14 +4,14 @@ import { setTimeout } from 'node:timers/promises';
import path from 'node:path';
import url from 'node:url';
import { randomBytes } from 'node:crypto';
import { EggContextHttpClient, HttpClientResponse } from 'egg';
import type { EggContextHttpClient, HttpClientResponse } from 'egg';
import mime from 'mime-types';
import dayjs from './dayjs';
import dayjs from './dayjs.js';
interface DownloadToTempfileOptionalConfig {
retries?: number,
ignoreDownloadStatuses?: number[],
remoteAuthToken?: string
retries?: number;
ignoreDownloadStatuses?: number[];
remoteAuthToken?: string;
}
export async function createTempDir(dataDir: string, dirname?: string) {
@@ -28,17 +28,29 @@ export async function createTempfile(dataDir: string, filename: string) {
const tmpdir = await createTempDir(dataDir);
// The filename is a URL (from dist.tarball), which needs to be truncated, (`getconf NAME_MAX /` # max filename length: 255 bytes)
// https://github.com/cnpm/cnpmjs.org/pull/1345
const tmpfile = path.join(tmpdir, `${randomBytes(10).toString('hex')}-${path.basename(url.parse(filename).pathname!)}`);
const tmpfile = path.join(
tmpdir,
`${randomBytes(10).toString('hex')}-${path.basename(url.parse(filename).pathname!)}`
);
return tmpfile;
}
export async function downloadToTempfile(httpclient: EggContextHttpClient,
dataDir: string, url: string, optionalConfig?: DownloadToTempfileOptionalConfig) {
export async function downloadToTempfile(
httpclient: EggContextHttpClient,
dataDir: string,
url: string,
optionalConfig?: DownloadToTempfileOptionalConfig
) {
let retries = optionalConfig?.retries || 3;
let lastError: any;
while (retries > 0) {
try {
return await _downloadToTempfile(httpclient, dataDir, url, optionalConfig);
return await _downloadToTempfile(
httpclient,
dataDir,
url,
optionalConfig
);
} catch (err: any) {
if (err.name === 'DownloadNotFoundError') throw err;
lastError = err;
@@ -46,7 +58,8 @@ export async function downloadToTempfile(httpclient: EggContextHttpClient,
retries--;
if (retries > 0) {
// sleep 1s ~ 4s in random
const delay = process.env.NODE_ENV === 'test' ? 1 : 1000 + Math.random() * 4000;
const delay =
process.env.NODE_ENV === 'test' ? 1 : 1000 + Math.random() * 4000;
await setTimeout(delay);
}
}
@@ -57,22 +70,33 @@ export interface Tempfile {
headers: HttpClientResponse['res']['headers'];
timing: HttpClientResponse['res']['timing'];
}
async function _downloadToTempfile(httpclient: EggContextHttpClient,
dataDir: string, url: string, optionalConfig?: DownloadToTempfileOptionalConfig): Promise<Tempfile> {
async function _downloadToTempfile(
httpclient: EggContextHttpClient,
dataDir: string,
url: string,
optionalConfig?: DownloadToTempfileOptionalConfig
): Promise<Tempfile> {
const tmpfile = await createTempfile(dataDir, url);
const writeStream = createWriteStream(tmpfile);
try {
// max 10 mins to download
// FIXME: should show download progress
const authorization = optionalConfig?.remoteAuthToken ? `Bearer ${optionalConfig?.remoteAuthToken}` : '';
const { status, headers, res } = await httpclient.request(url, {
const requestHeaders: Record<string, string> = {};
if (optionalConfig?.remoteAuthToken) {
requestHeaders.authorization = `Bearer ${optionalConfig.remoteAuthToken}`;
}
const { status, headers, res } = (await httpclient.request(url, {
timeout: 60000 * 10,
headers: { authorization },
headers: requestHeaders,
writeStream,
timing: true,
followRedirect: true,
}) as HttpClientResponse;
if (status === 404 || (optionalConfig?.ignoreDownloadStatuses && optionalConfig.ignoreDownloadStatuses.includes(status))) {
})) as HttpClientResponse;
if (
status === 404 ||
(optionalConfig?.ignoreDownloadStatuses &&
optionalConfig.ignoreDownloadStatuses.includes(status))
) {
const err = new Error(`Not found, status(${status})`);
err.name = 'DownloadNotFoundError';
throw err;
@@ -105,13 +129,17 @@ const WHITE_FILENAME_CONTENT_TYPES = {
'.eslintignore': PLAIN_TEXT,
'.jshintrc': 'application/json',
'.eslintrc': 'application/json',
};
} as const;
export function mimeLookup(filepath: string) {
const filename = path.basename(filepath).toLowerCase();
if (filename.endsWith('.ts')) return PLAIN_TEXT;
if (filename.endsWith('.lock')) return PLAIN_TEXT;
return mime.lookup(filename) ||
WHITE_FILENAME_CONTENT_TYPES[filename] ||
DEFAULT_CONTENT_TYPE;
return (
mime.lookup(filename) ||
WHITE_FILENAME_CONTENT_TYPES[
filename as keyof typeof WHITE_FILENAME_CONTENT_TYPES
] ||
DEFAULT_CONTENT_TYPE
);
}

View File

@@ -1,8 +1,14 @@
import { createReadStream } from 'node:fs';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import * as ssri from 'ssri';
import tar from 'tar';
import type { HashLike } from 'ssri';
import { fromData, fromStream } from 'ssri';
// @ts-expect-error type error
import tar from '@fengmk2/tar';
import type {
AuthorType,
PackageJSONType,
} from '../repository/PackageRepository.js';
// /@cnpm%2ffoo
// /@cnpm%2Ffoo
@@ -10,13 +16,14 @@ import tar from 'tar';
// /foo
// name max length is 214 chars
// https://www.npmjs.com/package/path-to-regexp#custom-matching-parameters
export const FULLNAME_REG_STRING = '@[^/]{1,220}\/[^/]{1,220}|@[^%]+\%2[fF][^/]{1,220}|[^@/]{1,220}';
export const FULLNAME_REG_STRING =
'@[^/]{1,220}/[^/]{1,220}|@[^%]+%2[fF][^/]{1,220}|[^@/]{1,220}';
export function getScopeAndName(fullname: string): string[] {
if (fullname.startsWith('@')) {
return fullname.split('/', 2);
}
return [ '', fullname ];
return ['', fullname];
}
export function getFullname(scope: string, name: string): string {
@@ -27,15 +34,19 @@ export function cleanUserPrefix(username: string): string {
return username.replace(/^.*:/, '');
}
export function getPrefixedName(prefix: string, username: string): string {
return prefix ? `${prefix}${username}` : username;
}
export async function calculateIntegrity(contentOrFile: Uint8Array | string) {
let integrityObj;
let integrityObj: HashLike;
if (typeof contentOrFile === 'string') {
integrityObj = await ssri.fromStream(createReadStream(contentOrFile), {
algorithms: [ 'sha512', 'sha1' ],
integrityObj = await fromStream(createReadStream(contentOrFile), {
algorithms: ['sha512', 'sha1'],
});
} else {
integrityObj = ssri.fromData(contentOrFile, {
algorithms: [ 'sha512', 'sha1' ],
integrityObj = fromData(contentOrFile, {
algorithms: ['sha512', 'sha1'],
});
}
const integrity = integrityObj.sha512[0].toString() as string;
@@ -43,7 +54,12 @@ export async function calculateIntegrity(contentOrFile: Uint8Array | string) {
return { integrity, shasum };
}
export function formatTarball(registry: string, scope: string, name: string, version: string) {
export function formatTarball(
registry: string,
scope: string,
name: string,
version: string
) {
const fullname = getFullname(scope, name);
return `${registry}/${fullname}/-/${name}-${version}.tgz`;
}
@@ -62,7 +78,9 @@ export function detectInstallScript(manifest: any) {
}
/** 判断一个版本压缩包中是否包含 npm-shrinkwrap.json */
export async function hasShrinkWrapInTgz(contentOrFile: Uint8Array | string): Promise<boolean> {
export async function hasShrinkWrapInTgz(
contentOrFile: Uint8Array | string
): Promise<boolean> {
let readable: Readable;
if (typeof contentOrFile === 'string') {
readable = createReadStream(contentOrFile);
@@ -80,7 +98,7 @@ export async function hasShrinkWrapInTgz(contentOrFile: Uint8Array | string): Pr
const parser = tar.t({
// options.strict 默认为 false会忽略 Recoverable errors例如 tar 解析失败
// 详见 https://github.com/isaacs/node-tar#warnings-and-errors
onentry(entry) {
onentry(entry: any) {
if (entry.path === 'package/npm-shrinkwrap.json') {
hasShrinkWrap = true;
abortController.abort();
@@ -95,6 +113,48 @@ export async function hasShrinkWrapInTgz(contentOrFile: Uint8Array | string): Pr
if (e.code === 'ABORT_ERR') {
return hasShrinkWrap;
}
throw Object.assign(new Error('[hasShrinkWrapInTgz] Fail to parse input file'), { cause: e });
throw Object.assign(
new Error('[hasShrinkWrapInTgz] Fail to parse input file'),
{ cause: e }
);
}
}
/** 写入 ES 时,格式化 author */
export function formatAuthor(
author: string | AuthorType | undefined
): AuthorType | undefined {
if (author === undefined) {
return author;
}
if (typeof author === 'string') {
return { name: author };
}
return author;
}
export async function extractPackageJSON(
tarballBytes: Buffer
): Promise<PackageJSONType> {
return new Promise((resolve, reject) => {
Readable.from(tarballBytes).pipe(
tar.t({
filter: (name: string) => name === 'package/package.json',
onentry: async (entry: any) => {
const chunks: Buffer[] = [];
for await (const chunk of entry) {
chunks.push(chunk);
}
try {
const data = Buffer.concat(chunks);
return resolve(JSON.parse(data.toString()));
} catch {
reject(new Error('Error parsing package.json'));
}
},
})
);
});
}

View File

@@ -1,4 +1,4 @@
import { EggContext } from '@eggjs/tegg';
import type { EggContext } from '@eggjs/tegg';
export function isSyncWorkerRequest(ctx: EggContext) {
// sync request will contain this query params

View File

@@ -1,7 +1,7 @@
import crypto from 'crypto';
import crypto from 'node:crypto';
import base from 'base-x';
import { crc32 } from '@node-rs/crc32';
import * as ssri from 'ssri';
import { checkData, create } from 'ssri';
import UAParser from 'ua-parser-js';
const base62 = base('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
@@ -29,12 +29,12 @@ export function checkToken(token: string, prefix: string): boolean {
}
export function integrity(plain: string): string {
return ssri.create().update(plain).digest()
return create().update(plain).digest()
.toString();
}
export function checkIntegrity(plain: string, expectedIntegrity: string): boolean {
return ssri.checkData(plain, expectedIntegrity);
return !!checkData(plain, expectedIntegrity);
}
export function sha512(plain: string): string {

View File

@@ -1,5 +1,5 @@
import { AccessLevel, SingletonProto } from '@eggjs/tegg';
import { BugVersion } from '../../core/entity/BugVersion';
import type { BugVersion } from '../../core/entity/BugVersion.js';
@SingletonProto({
accessLevel: AccessLevel.PUBLIC,

View File

@@ -3,8 +3,8 @@ import {
AccessLevel,
Inject,
} from '@eggjs/tegg';
// FIXME: egg-redis should use ioredis v5
// https://github.com/eggjs/egg-redis/issues/35
// FIXME: @eggjs/redis should use ioredis v5
// https://github.com/eggjs/redis/issues/35
import type { Redis } from 'ioredis';
const ONE_DAY = 3600 * 24;
@@ -64,12 +64,13 @@ export class CacheAdapter {
async usingLock(key: string, seconds: number, func: () => Promise<void>) {
const lockTimestamp = await this.lock(key, seconds);
if (!lockTimestamp) return;
if (!lockTimestamp) return false;
try {
await func();
} finally {
await this.unlock(key, lockTimestamp);
}
return true;
}
private getLockName(key: string) {

View File

@@ -1,14 +1,10 @@
import { Readable } from 'stream';
import {
SingletonProto,
AccessLevel,
Inject,
} from '@eggjs/tegg';
import type { Readable } from 'node:stream';
import type { IncomingHttpHeaders } from 'node:http';
import { SingletonProto, AccessLevel, Inject } from '@eggjs/tegg';
import { Pointcut } from '@eggjs/tegg/aop';
import { EggLogger } from 'egg';
import { AsyncTimer } from '../aop/AsyncTimer';
import { NFSClient } from '../typing';
import { IncomingHttpHeaders } from 'http';
import type { EggLogger } from 'egg';
import { AsyncTimer } from '../aop/AsyncTimer.js';
import type { NFSClient } from '../typing.js';
const INSTANCE_NAME = 'nfsAdapter';
@@ -25,13 +21,23 @@ export class NFSAdapter {
@Pointcut(AsyncTimer)
async uploadBytes(storeKey: string, bytes: Uint8Array) {
this.logger.info('[%s:uploadBytes] key: %s, bytes: %d', INSTANCE_NAME, storeKey, bytes.length);
this.logger.info(
'[%s:uploadBytes] key: %s, bytes: %d',
INSTANCE_NAME,
storeKey,
bytes.length
);
await this.nfsClient.uploadBytes(bytes, { key: storeKey });
}
// will return next store position
@Pointcut(AsyncTimer)
async appendBytes(storeKey: string, bytes: Uint8Array, position?: string, headers?: IncomingHttpHeaders) {
async appendBytes(
storeKey: string,
bytes: Uint8Array,
position?: string,
headers?: IncomingHttpHeaders
) {
// make sure position is undefined by the first time
if (!position) position = undefined;
const options = {
@@ -45,14 +51,24 @@ export class NFSAdapter {
@Pointcut(AsyncTimer)
async uploadFile(storeKey: string, file: string) {
this.logger.info('[%s:uploadFile] key: %s, file: %s', INSTANCE_NAME, storeKey, file);
this.logger.info(
'[%s:uploadFile] key: %s, file: %s',
INSTANCE_NAME,
storeKey,
file
);
await this.nfsClient.upload(file, { key: storeKey });
}
@Pointcut(AsyncTimer)
async downloadFile(storeKey: string, file: string, timeout: number) {
this.logger.info('[%s:downloadFile] key: %s, file: %s, timeout: %s',
INSTANCE_NAME, storeKey, file, timeout);
this.logger.info(
'[%s:downloadFile] key: %s, file: %s, timeout: %s',
INSTANCE_NAME,
storeKey,
file,
timeout
);
await this.nfsClient.download(storeKey, file, { timeout });
}
@@ -79,7 +95,9 @@ export class NFSAdapter {
}
}
async getDownloadUrlOrStream(storeKey: string): Promise<string | Readable | undefined> {
async getDownloadUrlOrStream(
storeKey: string
): Promise<string | Readable | undefined> {
const downloadUrl = await this.getDownloadUrl(storeKey);
if (downloadUrl) {
return downloadUrl;

View File

@@ -1,16 +1,14 @@
import { setTimeout } from 'timers/promises';
import {
ContextProto,
AccessLevel,
Inject,
} from '@eggjs/tegg';
import {
import { setTimeout } from 'node:timers/promises';
import { ContextProto, AccessLevel, Inject } from '@eggjs/tegg';
import type {
EggLogger,
EggContextHttpClient,
EggAppConfig,
HttpClientRequestOptions,
HttpClientResponse,
} from 'egg';
import type { PackageManifestType } from '../../repository/PackageRepository.js';
import { isTimeoutError } from '../ErrorUtil.js';
type HttpMethod = HttpClientRequestOptions['method'];
@@ -40,7 +38,10 @@ export class NPMRegistry {
this.registryHost = registryHost;
}
public async getFullManifests(fullname: string, optionalConfig?: {retries?:number, remoteAuthToken?:string}): Promise<RegistryResponse> {
public async getFullManifests(
fullname: string,
optionalConfig?: { retries?: number; remoteAuthToken?: string }
): Promise<{ method: HttpMethod } & HttpClientResponse<PackageManifestType>> {
let retries = optionalConfig?.retries || 3;
// set query t=timestamp, make sure CDN cache disable
// cache=0 is sync worker request flag
@@ -50,16 +51,24 @@ export class NPMRegistry {
try {
// large package: https://r.cnpmjs.org/%40procore%2Fcore-icons
// https://r.cnpmjs.org/intraactive-sdk-ui 44s
const authorization = this.genAuthorizationHeader(optionalConfig?.remoteAuthToken);
return await this.request('GET', url, undefined, { timeout: 120000, headers: { authorization } });
const authorization = this.genAuthorizationHeader(
optionalConfig?.remoteAuthToken
);
return await this.request('GET', url, undefined, {
timeout: 120000,
headers: { authorization },
});
} catch (err: any) {
if (err.name === 'ResponseTimeoutError') throw err;
if (isTimeoutError(err)) {
throw err;
}
lastError = err;
}
retries--;
if (retries > 0) {
// sleep 1s ~ 4s in random
const delay = process.env.NODE_ENV === 'test' ? 1 : 1000 + Math.random() * 4000;
const delay =
process.env.NODE_ENV === 'test' ? 1 : 1000 + Math.random() * 4000;
await setTimeout(delay);
}
}
@@ -67,8 +76,13 @@ export class NPMRegistry {
}
// app.put('/:name/sync', sync.sync);
public async createSyncTask(fullname: string, optionalConfig?: { remoteAuthToken?:string}): Promise<RegistryResponse> {
const authorization = this.genAuthorizationHeader(optionalConfig?.remoteAuthToken);
public async createSyncTask(
fullname: string,
optionalConfig?: { remoteAuthToken?: string }
): Promise<RegistryResponse> {
const authorization = this.genAuthorizationHeader(
optionalConfig?.remoteAuthToken
);
const url = `${this.registry}/${encodeURIComponent(fullname)}/sync?sync_upstream=true&nodeps=true`;
// {
// ok: true,
@@ -78,38 +92,64 @@ export class NPMRegistry {
}
// app.get('/:name/sync/log/:id', sync.getSyncLog);
public async getSyncTask(fullname: string, id: string, offset: number, optionalConfig?:{ remoteAuthToken?:string }): Promise<RegistryResponse> {
const authorization = this.genAuthorizationHeader(optionalConfig?.remoteAuthToken);
public async getSyncTask(
fullname: string,
id: string,
offset: number,
optionalConfig?: { remoteAuthToken?: string }
): Promise<RegistryResponse> {
const authorization = this.genAuthorizationHeader(
optionalConfig?.remoteAuthToken
);
const url = `${this.registry}/${encodeURIComponent(fullname)}/sync/log/${id}?offset=${offset}`;
// { ok: true, syncDone: syncDone, log: log }
return await this.request('GET', url, undefined, { authorization });
}
public async getDownloadRanges(registry: string, fullname: string, start: string, end: string, optionalConfig?:{ remoteAuthToken?:string }): Promise<RegistryResponse> {
const authorization = this.genAuthorizationHeader(optionalConfig?.remoteAuthToken);
public async getDownloadRanges(
registry: string,
fullname: string,
start: string,
end: string,
optionalConfig?: { remoteAuthToken?: string }
): Promise<RegistryResponse> {
const authorization = this.genAuthorizationHeader(
optionalConfig?.remoteAuthToken
);
const url = `${registry}/downloads/range/${start}:${end}/${encodeURIComponent(fullname)}`;
return await this.request('GET', url, undefined, { authorization });
}
private async request(method: HttpMethod, url: string, params?: object, options?: object): Promise<RegistryResponse> {
const res = await this.httpclient.request(url, {
private async request(
method: HttpMethod,
url: string,
params?: object,
options?: object
): Promise<RegistryResponse> {
const res = (await this.httpclient.request(url, {
method,
data: params,
dataType: 'json',
timing: true,
retry: 3,
timeout: this.timeout,
followRedirect: true,
gzip: true,
...options,
}) as HttpClientResponse;
this.logger.info('[NPMRegistry:request] %s %s, status: %s', method, url, res.status);
})) as HttpClientResponse;
this.logger.info(
'[NPMRegistry:request] %s %s, status: %s',
method,
url,
res.status
);
return {
method,
...res,
};
}
private genAuthorizationHeader(remoteAuthToken?:string) {
public genAuthorizationHeader(remoteAuthToken?: string) {
return remoteAuthToken ? `Bearer ${remoteAuthToken}` : '';
}
}

View File

@@ -1,7 +1,11 @@
import { ImplDecorator, Inject, QualifierImplDecoratorUtil } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import { EggHttpClient, EggLogger } from 'egg';
import { BinaryName, BinaryTaskConfig } from '../../../../config/binaries';
import type { ImplDecorator } from '@eggjs/tegg';
import { Inject, QualifierImplDecoratorUtil } from '@eggjs/tegg';
import type { EggHttpClient, EggLogger } from 'egg';
import type { BinaryType } from '../../enum/Binary.js';
import type {
BinaryName,
BinaryTaskConfig,
} from '../../../../config/binaries.js';
export type BinaryItem = {
name: string;
@@ -17,6 +21,8 @@ export type FetchResult = {
nextParams?: any;
};
const platforms = ['darwin', 'linux', 'win32'] as const;
export const BINARY_ADAPTER_ATTRIBUTE = Symbol('BINARY_ADAPTER_ATTRIBUTE');
export abstract class AbstractBinary {
@@ -26,32 +32,55 @@ export abstract class AbstractBinary {
@Inject()
protected httpclient: EggHttpClient;
abstract init(binaryName: BinaryName): Promise<void>;
abstract fetch(dir: string, binaryName: BinaryName): Promise<FetchResult | undefined>;
abstract initFetch(binaryName: BinaryName): Promise<void>;
abstract fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async finishFetch(_success: boolean, _binaryName: BinaryName): Promise<void> {
// do not thing by default
}
protected async requestXml(url: string) {
const { status, data, headers } = await this.httpclient.request(url, {
timeout: 20000,
timeout: 30000,
followRedirect: true,
gzip: true,
});
const xml = data.toString() as string;
if (status !== 200) {
this.logger.warn('[AbstractBinary.requestXml:non-200-status] url: %s, status: %s, headers: %j, xml: %j', url, status, headers, xml);
this.logger.warn(
'[AbstractBinary.requestXml:non-200-status] url: %s, status: %s, headers: %j, xml: %j',
url,
status,
headers,
xml
);
return '';
}
return xml;
}
protected async requestJSON(url: string) {
protected async requestJSON(
url: string,
requestHeaders?: Record<string, string>
) {
const { status, data, headers } = await this.httpclient.request(url, {
timeout: 20000,
timeout: 30000,
dataType: 'json',
followRedirect: true,
gzip: true,
headers: requestHeaders,
});
if (status !== 200) {
this.logger.warn('[AbstractBinary.requestJSON:non-200-status] url: %s, status: %s, headers: %j', url, status, headers);
this.logger.warn(
'[AbstractBinary.requestJSON:non-200-status] url: %s, status: %s, headers: %j',
url,
status,
headers
);
return data;
}
return data;
@@ -60,11 +89,13 @@ export abstract class AbstractBinary {
// https://nodejs.org/api/n-api.html#n_api_node_api_version_matrix
protected async listNodeABIVersions() {
const nodeABIVersions: number[] = [];
const versions = await this.requestJSON('https://nodejs.org/dist/index.json');
const versions = await this.requestJSON(
'https://nodejs.org/dist/index.json'
);
for (const version of versions) {
if (!version.modules) continue;
const modulesVersion = parseInt(version.modules);
// node v6.0.0 moduels 48 min
// node v6.0.0 modules 48 min
if (modulesVersion >= 48 && !nodeABIVersions.includes(modulesVersion)) {
nodeABIVersions.push(modulesVersion);
}
@@ -74,28 +105,31 @@ export abstract class AbstractBinary {
protected listNodePlatforms() {
// https://nodejs.org/api/os.html#osplatform
return [ 'darwin', 'linux', 'win32' ];
return platforms;
}
protected listNodeArchs(binaryConfig?: BinaryTaskConfig) {
if (binaryConfig?.options?.nodeArchs) return binaryConfig.options.nodeArchs;
// https://nodejs.org/api/os.html#osarch
return {
linux: [ 'arm', 'arm64', 's390x', 'ia32', 'x64' ],
darwin: [ 'arm64', 'ia32', 'x64' ],
win32: [ 'ia32', 'x64' ],
linux: ['arm', 'arm64', 's390x', 'ia32', 'x64'],
darwin: ['arm64', 'ia32', 'x64'],
win32: ['ia32', 'x64'],
};
}
protected listNodeLibcs() {
protected listNodeLibcs(): Record<(typeof platforms)[number], string[]> {
// https://github.com/lovell/detect-libc/blob/master/lib/detect-libc.js#L42
return {
linux: [ 'glibc', 'musl' ],
darwin: [ 'unknown' ],
win32: [ 'unknown' ],
darwin: ['unknown'],
linux: ['glibc', 'musl'],
win32: ['unknown'],
};
}
}
export const BinaryAdapter: ImplDecorator<AbstractBinary, typeof BinaryType> =
QualifierImplDecoratorUtil.generatorDecorator(AbstractBinary, BINARY_ADAPTER_ATTRIBUTE);
QualifierImplDecoratorUtil.generatorDecorator(
AbstractBinary,
BINARY_ADAPTER_ATTRIBUTE
);

View File

@@ -1,7 +1,8 @@
import { AbstractBinary, FetchResult, BinaryItem, BinaryAdapter } from './AbstractBinary';
import { Inject, SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import { EggAppConfig } from 'egg';
import type { EggAppConfig } from 'egg';
import { BinaryType } from '../../enum/Binary.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Api)
@@ -9,17 +10,25 @@ export class ApiBinary extends AbstractBinary {
@Inject()
private readonly config: EggAppConfig;
async init() {
async initFetch() {
// do nothing
return;
}
async fetch(dir: string, binaryName: string): Promise<FetchResult | undefined> {
const apiUrl = this.config.cnpmcore.syncBinaryFromAPISource || `${this.config.cnpmcore.sourceRegistry}/-/binary`;
async fetch(
dir: string,
binaryName: string
): Promise<FetchResult | undefined> {
const apiUrl =
this.config.cnpmcore.syncBinaryFromAPISource ||
`${this.config.cnpmcore.sourceRegistry}/-/binary`;
const url = `${apiUrl}/${binaryName}${dir}`;
const data = await this.requestJSON(url);
if (!Array.isArray(data)) {
this.logger.warn('[ApiBinary.fetch:response-data-not-array] data: %j', data);
this.logger.warn(
'[ApiBinary.fetch:response-data-not-array] data: %j',
data
);
return;
}
const items: BinaryItem[] = [];

View File

@@ -1,18 +1,26 @@
import path from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import binaries, { BinaryName, BinaryTaskConfig } from '../../../../config/binaries';
import path from 'path';
import { AbstractBinary, FetchResult, BinaryItem, BinaryAdapter } from './AbstractBinary';
import { BinaryType } from '../../enum/Binary.js';
import type {
BinaryName,
BinaryTaskConfig,
} from '../../../../config/binaries.js';
import binaries from '../../../../config/binaries.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Bucket)
export class BucketBinary extends AbstractBinary {
async init() {
async initFetch() {
// do nothing
return;
}
async fetch(dir: string, binaryName: BinaryName): Promise<FetchResult | undefined> {
async fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined> {
// /foo/ => foo/
const binaryConfig = binaries[binaryName];
const subDir = dir.substring(1);
@@ -21,13 +29,18 @@ export class BucketBinary extends AbstractBinary {
return { items: this.parseItems(xml, dir, binaryConfig), nextParams: null };
}
protected parseItems(xml: string, dir: string, binaryConfig: BinaryTaskConfig): BinaryItem[] {
protected parseItems(
xml: string,
dir: string,
binaryConfig: BinaryTaskConfig
): BinaryItem[] {
const items: BinaryItem[] = [];
// https://nwjs2.s3.amazonaws.com/?prefix=v0.59.0%2Fx64%2F
// https://chromedriver.storage.googleapis.com/?delimiter=/&prefix=
// <Contents><Key>2.0/chromedriver_linux32.zip</Key><Generation>1380149859530000</Generation><MetaGeneration>2</MetaGeneration><LastModified>2013-09-25T22:57:39.349Z</LastModified><ETag>"c0d96102715c4916b872f91f5bf9b12c"</ETag><Size>7262134</Size><Owner/></Contents><Contents>
// <Contents><Key>v0.59.0/nwjs-v0.59.0-linux-ia32.tar.gz</Key><LastModified>2015-11-02T02:34:18.000Z</LastModified><ETag>&quot;b1b7a52928e9f874bad0cabf7f74ba8e&quot;</ETag><Size>22842</Size><StorageClass>STANDARD</StorageClass></Contents>
const fileRe = /<Contents><Key>([^<]+?)<\/Key>(?:<Generation>\d+?<\/Generation>)?(?:<MetaGeneration>\d+?<\/MetaGeneration>)?<LastModified>([^<]+?)<\/LastModified><ETag>[^<]+?<\/ETag><Size>(\d+?)<\/Size>/g;
const fileRe =
/<Contents><Key>([^<]+?)<\/Key>(?:<Generation>\d+?<\/Generation>)?(?:<MetaGeneration>\d+?<\/MetaGeneration>)?<LastModified>([^<]+?)<\/LastModified><ETag>[^<]+?<\/ETag><Size>(\d+?)<\/Size>/g;
let matchs = xml.matchAll(fileRe);
for (const m of matchs) {
const fullname = m[1].trim();
@@ -52,7 +65,8 @@ export class BucketBinary extends AbstractBinary {
});
}
// <CommonPrefixes><Prefix>v0.59.0/x64/</Prefix></CommonPrefixes>
const dirRe = /<CommonPrefixes><Prefix>([^<]+?)<\/Prefix><\/CommonPrefixes>/g;
const dirRe =
/<CommonPrefixes><Prefix>([^<]+?)<\/Prefix><\/CommonPrefixes>/g;
matchs = xml.matchAll(dirRe);
for (const m of matchs) {
// <Prefix>AWSLogs/</Prefix>

View File

@@ -1,69 +1,174 @@
import { basename } from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import { AbstractBinary, FetchResult, BinaryItem, BinaryAdapter } from './AbstractBinary';
import { BinaryType } from '../../enum/Binary.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.ChromeForTesting)
export class ChromeForTestingBinary extends AbstractBinary {
static lastTimestamp = '';
#timestamp = '';
private dirItems?: {
[key: string]: BinaryItem[];
};
async init() {
async initFetch() {
this.dirItems = undefined;
}
async fetch(dir: string): Promise<FetchResult | undefined> {
if (!this.dirItems) {
this.dirItems = {};
this.dirItems['/'] = [];
let chromeVersion = '';
async finishFetch(success: boolean) {
if (
success &&
this.#timestamp &&
ChromeForTestingBinary.lastTimestamp !== this.#timestamp
) {
ChromeForTestingBinary.lastTimestamp = this.#timestamp;
}
}
// exports.PUPPETEER_REVISIONS = Object.freeze({
// chrome: '113.0.5672.63',
// firefox: 'latest',
// });
const unpkgURL = 'https://unpkg.com/puppeteer-core@latest/lib/cjs/puppeteer/revisions.js';
const text = await this.requestXml(unpkgURL);
const m = /chrome:\s+\'([\d\.]+)\'\,/.exec(text);
if (m) {
chromeVersion = m[1];
async #syncDirItems() {
this.dirItems = {};
this.dirItems['/'] = [];
const jsonApiEndpoint =
'https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json';
const { data, status, headers } = await this.httpclient.request(
jsonApiEndpoint,
{
dataType: 'json',
timeout: 30000,
followRedirect: true,
gzip: true,
}
);
if (status !== 200) {
this.logger.warn(
'[ChromeForTestingBinary.request:non-200-status] url: %s, status: %s, headers: %j, data: %j',
jsonApiEndpoint,
status,
headers,
data
);
return;
}
this.#timestamp = data.timestamp;
const hasNewData = this.#timestamp !== ChromeForTestingBinary.lastTimestamp;
this.logger.info(
'[ChromeForTestingBinary] remote data timestamp: %j, last timestamp: %j, hasNewData: %s',
this.#timestamp,
ChromeForTestingBinary.lastTimestamp,
hasNewData
);
if (!hasNewData) {
return;
}
const platforms = [ 'linux64', 'mac-arm64', 'mac-x64', 'win32', 'win64' ];
const date = new Date().toISOString();
this.dirItems['/'].push({
name: 'known-good-versions-with-downloads.json',
date: data.timestamp,
size: '-',
isDir: false,
url: jsonApiEndpoint,
});
this.dirItems['/'].push({
name: 'latest-patch-versions-per-build.json',
date: data.timestamp,
size: '-',
isDir: false,
url: 'https://googlechromelabs.github.io/chrome-for-testing/latest-patch-versions-per-build.json',
});
this.dirItems['/'].push({
name: 'last-known-good-versions.json',
date: data.timestamp,
size: '-',
isDir: false,
url: 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json',
});
// "timestamp": "2023-09-16T00:21:21.964Z",
// "versions": [
// {
// "version": "113.0.5672.0",
// "revision": "1121455",
// "downloads": {
// "chrome": [
// {
// "platform": "linux64",
// "url": "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/linux64/chrome-linux64.zip"
// },
// {
// "platform": "mac-arm64",
// "url": "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/mac-arm64/chrome-mac-arm64.zip"
// },
// {
// "platform": "mac-x64",
// "url": "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/mac-x64/chrome-mac-x64.zip"
// },
// {
// "platform": "win32",
// "url": "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/win32/chrome-win32.zip"
// },
// {
// "platform": "win64",
// "url": "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/win64/chrome-win64.zip"
// }
// ]
// }
// },
const versions = data.versions as {
version: string;
revision: string;
downloads: {
[key: string]: {
platform: string;
url: string;
}[];
};
}[];
for (const item of versions) {
this.dirItems['/'].push({
name: `${chromeVersion}/`,
date,
name: `${item.version}/`,
date: item.revision,
size: '-',
isDir: true,
url: '',
});
this.dirItems[`/${chromeVersion}/`] = [];
for (const platform of platforms) {
this.dirItems[`/${chromeVersion}/`].push({
name: `${platform}/`,
date,
size: '-',
isDir: true,
url: '',
});
// https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.63/mac-arm64/chrome-mac-arm64.zip
const name = `chrome-${platform}.zip`;
this.dirItems[`/${chromeVersion}/${platform}/`] = [
{
name,
date,
const versionDir = `/${item.version}/`;
if (!this.dirItems[versionDir]) {
this.dirItems[versionDir] = [];
}
for (const category in item.downloads) {
const downloads = item.downloads[category];
for (const download of downloads) {
const platformDir = `${versionDir}${download.platform}/`;
if (!this.dirItems[platformDir]) {
this.dirItems[platformDir] = [];
this.dirItems[versionDir].push({
name: `${download.platform}/`,
date: item.revision,
size: '-',
isDir: true,
url: '',
});
}
this.dirItems[platformDir].push({
name: basename(download.url),
date: data.timestamp,
size: '-',
isDir: false,
url: `https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/${chromeVersion}/${platform}/${name}`,
},
];
url: download.url,
});
}
}
}
}
return { items: this.dirItems[dir], nextParams: null };
async fetch(dir: string): Promise<FetchResult | undefined> {
// use https://github.com/GoogleChromeLabs/chrome-for-testing#json-api-endpoints
if (!this.dirItems) {
await this.#syncDirItems();
}
return { items: this.dirItems![dir], nextParams: null };
}
}

View File

@@ -1,6 +1,7 @@
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import { AbstractBinary, FetchResult, BinaryItem, BinaryAdapter } from './AbstractBinary';
import { BinaryType } from '../../enum/Binary.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Cypress)
@@ -9,7 +10,7 @@ export class CypressBinary extends AbstractBinary {
[key: string]: BinaryItem[];
} | null;
async init() {
async initFetch() {
this.dirItems = undefined;
}
@@ -42,8 +43,23 @@ export class CypressBinary extends AbstractBinary {
// "https://cdn.cypress.io/desktop/9.2.0/darwin-arm64/cypress.zip"
// "https://cdn.cypress.io/desktop/9.2.0/darwin-x64/cypress.zip"
// "https://cdn.cypress.io/desktop/9.2.0/linux-x64/cypress.zip"
// "https://cdn.cypress.io/desktop/9.2.0/linux-arm64/cypress.zip"
// "https://cdn.cypress.io/desktop/9.2.0/win32-x64/cypress.zip"
const platforms = [ 'darwin-x64', 'darwin-arm64', 'linux-x64', 'win32-x64' ];
// https://github.com/cypress-io/cypress/blob/develop/scripts/binary/index.js#L146
// const systems = [
// { platform: 'linux', arch: 'x64' },
// { platform: 'linux', arch: 'arm64' },
// { platform: 'darwin', arch: 'x64' },
// { platform: 'darwin', arch: 'arm64' },
// { platform: 'win32', arch: 'x64' },
// ]
const platforms = [
'darwin-x64',
'darwin-arm64',
'linux-x64',
'linux-arm64',
'win32-x64',
];
for (const platform of platforms) {
this.dirItems[subDir].push({
name: `${platform}/`,

View File

@@ -0,0 +1,221 @@
import path from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
import { BinaryType } from '../../enum/Binary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Edgedriver)
export class EdgedriverBinary extends AbstractBinary {
private dirItems?: {
[key: string]: BinaryItem[];
};
async initFetch() {
this.dirItems = undefined;
}
async #syncDirItems() {
this.dirItems = {};
this.dirItems['/'] = [];
const jsonApiEndpoint = 'https://edgeupdates.microsoft.com/api/products';
const { data, status, headers } = await this.httpclient.request(
jsonApiEndpoint,
{
dataType: 'json',
timeout: 30000,
followRedirect: true,
gzip: true,
}
);
if (status !== 200) {
this.logger.warn(
'[EdgedriverBinary.request:non-200-status] url: %s, status: %s, headers: %j, data: %j',
jsonApiEndpoint,
status,
headers,
data
);
return;
}
this.logger.info('[EdgedriverBinary] remote data length: %s', data.length);
// [
// {
// "Product": "Stable",
// "Releases": [
// {
// "ReleaseId": 73376,
// "Platform": "iOS",
// "Architecture": "arm64",
// "CVEs": [],
// "ProductVersion": "124.0.2478.89",
// "Artifacts": [],
// "PublishedTime": "2024-05-07T02:57:00",
// "ExpectedExpiryDate": "2025-05-07T02:57:00"
// },
// {
// "ReleaseId": 73629,
// "Platform": "Windows",
// "Architecture": "x86",
// "CVEs": [
// "CVE-2024-4559",
// "CVE-2024-4671"
// ],
// "ProductVersion": "124.0.2478.97",
// "Artifacts": [
// {
// "ArtifactName": "msi",
// "Location": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/aa1c9fe3-bb9c-4a80-9ff7-5c109701fbfe/MicrosoftEdgeEnterpriseX86.msi",
// "Hash": "4CEF7B907D3E2371E953C41190E32C3560CEE7D3F16D7550CA156DC976EBCB80",
// "HashAlgorithm": "SHA256",
// "SizeInBytes": 162029568
// }
// ],
// "PublishedTime": "2024-05-11T06:47:00",
// "ExpectedExpiryDate": "2025-05-10T16:59:00"
// },
// {
// "ReleaseId": 73630,
// "Platform": "Linux",
// "Architecture": "x64",
// "CVEs": [
// "CVE-2024-4559"
// ],
// "ProductVersion": "124.0.2478.97",
// "Artifacts": [
// {
// "ArtifactName": "rpm",
// "Location": "https://packages.microsoft.com/yumrepos/edge/microsoft-edge-stable-124.0.2478.97-1.x86_64.rpm",
// "Hash": "32D9C333544DDD9C56FED54844E89EF00F3E5620942C07B9B68D214016687895",
// "HashAlgorithm": "SHA256",
// "SizeInBytes": 169877932
// },
// {
// "ArtifactName": "deb",
// "Location": "https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable/microsoft-edge-stable_124.0.2478.97-1_amd64.deb",
// "Hash": "85D0AD1D63847B3DD54F0F214D18A2B54462BB43291536E773AD1B8B29BBF799",
// "HashAlgorithm": "SHA256",
// "SizeInBytes": 167546042
// }
// ],
// "PublishedTime": "2024-05-10T17:01:00",
// "ExpectedExpiryDate": "2025-05-10T17:01:00"
// },
// {
// "Product": "EdgeUpdate",
// "Releases": [
// {
// "ReleaseId": 73493,
// "Platform": "Windows",
// "Architecture": "x86",
// "CVEs": [],
// "ProductVersion": "1.3.187.37",
// "Artifacts": [
// {
// "ArtifactName": "exe",
// "Location": "https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/a2fa84fe-796b-4f80-b1cd-f4d1f5731aa8/MicrosoftEdgeUpdateSetup_X86_1.3.187.37.exe",
// "Hash": "503088D22461FEE5D7B6B011609D73FFD5869D3ACE1DBB0F00F8F3B9D122C514",
// "HashAlgorithm": "SHA256",
// "SizeInBytes": 1622072
// }
// ],
// "PublishedTime": "2024-05-08T05:44:00",
// "ExpectedExpiryDate": "2025-05-08T05:44:00"
// }
// ]
// }
const products = data as {
Product: string;
Releases: {
ReleaseId: number;
Platform: string;
Architecture: string;
CVEs: string[];
ProductVersion: string;
Artifacts: {
ArtifactName: string;
Location: string;
Hash: string;
HashAlgorithm: string;
SizeInBytes: string;
}[];
PublishedTime: string;
ExpectedExpiryDate: string;
}[];
}[];
const existsVersions = new Set<string>();
for (const product of products) {
if (product.Product === 'EdgeUpdate') continue;
for (const release of product.Releases) {
if (!release.Artifacts || release.Artifacts.length === 0) continue;
if (existsVersions.has(release.ProductVersion)) continue;
this.dirItems['/'].push({
name: `${release.ProductVersion}/`,
date: release.PublishedTime,
size: '-',
isDir: true,
url: '',
});
existsVersions.add(release.ProductVersion);
}
}
}
async fetch(dir: string): Promise<FetchResult | undefined> {
if (!this.dirItems) {
await this.#syncDirItems();
}
// fetch root dir
if (dir === '/') {
return { items: this.dirItems![dir], nextParams: null };
}
// fetch sub dir
// /foo/ => foo/
const subDir = dir.substring(1);
// https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver?prefix=124.0.2478.97/&delimiter=/&maxresults=100&restype=container&comp=list
const url = `https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver?prefix=${encodeURIComponent(subDir)}&delimiter=/&maxresults=100&restype=container&comp=list`;
const xml = await this.requestXml(url);
return { items: this.#parseItems(xml), nextParams: null };
}
#parseItems(xml: string): BinaryItem[] {
const items: BinaryItem[] = [];
// <Blob><Name>124.0.2478.97/edgedriver_arm64.zip</Name><Url>https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver/124.0.2478.97/edgedriver_arm64.zip</Url><Properties><Last-Modified>Fri, 10 May 2024 18:35:44 GMT</Last-Modified><Etag>0x8DC712000713C13</Etag><Content-Length>9191362</Content-Length><Content-Type>application/octet-stream</Content-Type><Content-Encoding /><Content-Language /><Content-MD5>1tjPTf5JU6KKB06Qf1JOGw==</Content-MD5><Cache-Control /><BlobType>BlockBlob</BlobType><LeaseStatus>unlocked</LeaseStatus></Properties></Blob>
const fileRe =
/<Blob><Name>([^<]+?)<\/Name><Url>([^<]+?)<\/Url><Properties><Last-Modified>([^<]+?)<\/Last-Modified><Etag>(?:[^<]+?)<\/Etag><Content-Length>(\d+)<\/Content-Length>/g;
const matchItems = xml.matchAll(fileRe);
for (const m of matchItems) {
const fullname = m[1].trim();
// <Blob>
// <Name>124.0.2478.97/edgedriver_arm64.zip</Name>
// <Url>https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver/124.0.2478.97/edgedriver_arm64.zip</Url>
// <Properties>
// <Last-Modified>Fri, 10 May 2024 18:35:44 GMT</Last-Modified>
// <Etag>0x8DC712000713C13</Etag>
// <Content-Length>9191362</Content-Length>
// <Content-Type>application/octet-stream</Content-Type>
// <Content-Encoding/>
// <Content-Language/>
// <Content-MD5>1tjPTf5JU6KKB06Qf1JOGw==</Content-MD5>
// <Cache-Control/>
// <BlobType>BlockBlob</BlobType>
// <LeaseStatus>unlocked</LeaseStatus>
// </Properties>
// </Blob>
// ignore size = 0 dir
const name = path.basename(fullname);
const url = m[2].trim();
const date = m[3].trim();
const size = parseInt(m[4].trim());
items.push({
name,
isDir: false,
url,
size,
date,
});
}
return items;
}
}

View File

@@ -1,13 +1,18 @@
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import binaries, { BinaryName } from '../../../../config/binaries';
import { BinaryAdapter, BinaryItem, FetchResult } from './AbstractBinary';
import { GithubBinary } from './GithubBinary';
import type { BinaryName } from '../../../../config/binaries.js';
import binaries from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import type { BinaryItem, FetchResult } from './AbstractBinary.js';
import { BinaryAdapter } from './AbstractBinary.js';
import { GithubBinary } from './GithubBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Electron)
export class ElectronBinary extends GithubBinary {
async fetch(dir: string, binaryName: BinaryName = 'electron'): Promise<FetchResult | undefined> {
async fetch(
dir: string,
binaryName: BinaryName = 'electron'
): Promise<FetchResult | undefined> {
const releases = await this.initReleases(binaryName, binaries.electron);
if (!releases) return;
@@ -34,7 +39,10 @@ export class ElectronBinary extends GithubBinary {
}
} else {
for (const item of releases) {
if (dir === `/${item.tag_name}/` || dir === `/${item.tag_name.substring(1)}/`) {
if (
dir === `/${item.tag_name}/` ||
dir === `/${item.tag_name.substring(1)}/`
) {
items = this.formatItems(item, binaries.electron);
break;
}

View File

@@ -1,18 +1,26 @@
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import binaries, { BinaryName, BinaryTaskConfig } from '../../../../config/binaries';
import { AbstractBinary, FetchResult, BinaryItem, BinaryAdapter } from './AbstractBinary';
import type {
BinaryName,
BinaryTaskConfig,
} from '../../../../config/binaries.js';
import binaries from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.GitHub)
export class GithubBinary extends AbstractBinary {
private releases: Record<string, any[]> = {};
async init(binaryName: BinaryName) {
async initFetch(binaryName: BinaryName) {
delete this.releases[binaryName];
}
protected async initReleases(binaryName: BinaryName, binaryConfig: BinaryTaskConfig) {
protected async initReleases(
binaryName: BinaryName,
binaryConfig: BinaryTaskConfig
) {
if (!this.releases[binaryName]) {
// https://docs.github.com/en/rest/reference/releases get three pages
// https://api.github.com/repos/electron/electron/releases
@@ -21,14 +29,29 @@ export class GithubBinary extends AbstractBinary {
const maxPage = binaryConfig.options?.maxPage || 1;
for (let i = 0; i < maxPage; i++) {
const url = `https://api.github.com/repos/${binaryConfig.repo}/releases?per_page=100&page=${i + 1}`;
const data = await this.requestJSON(url);
const requestHeaders: Record<string, string> = {};
if (process.env.GITHUB_TOKEN) {
requestHeaders.Authorization = `token ${process.env.GITHUB_TOKEN}`;
}
const data = await this.requestJSON(url, requestHeaders);
if (!Array.isArray(data)) {
// {"message":"API rate limit exceeded for 47.57.239.54. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)","documentation_url":"https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting"}
if (typeof data?.message === 'string' && data.message.includes('rate limit')) {
this.logger.info('[GithubBinary.fetch:hit-rate-limit] skip sync this time, data: %j, url: %s', data, url);
if (
typeof data?.message === 'string' &&
data.message.includes('rate limit')
) {
this.logger.info(
'[GithubBinary.fetch:hit-rate-limit] skip sync this time, data: %j, url: %s',
data,
url
);
return;
}
this.logger.warn('[GithubBinary.fetch:response-data-not-array] data: %j, url: %s', data, url);
this.logger.warn(
'[GithubBinary.fetch:response-data-not-array] data: %j, url: %s',
data,
url
);
return;
}
releases = releases.concat(data);
@@ -40,10 +63,16 @@ export class GithubBinary extends AbstractBinary {
protected formatItems(releaseItem: any, binaryConfig: BinaryTaskConfig) {
const items: BinaryItem[] = [];
// 200MB
const maxFileSize = 1024 * 1024 * 200;
// 250MB
const maxFileSize = 1024 * 1024 * 250;
for (const asset of releaseItem.assets) {
if (asset.size > maxFileSize) continue;
if (asset.size > maxFileSize) {
this.logger.info(
'[GithubBinary.formatItems] asset reach max file size(> 250MB), ignore download it, asset: %j',
asset
);
continue;
}
items.push({
name: asset.name,
isDir: false,
@@ -76,7 +105,10 @@ export class GithubBinary extends AbstractBinary {
return items;
}
async fetch(dir: string, binaryName: BinaryName): Promise<FetchResult | undefined> {
async fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined> {
const binaryConfig = binaries[binaryName];
const releases = await this.initReleases(binaryName, binaryConfig);
if (!releases) return;

View File

@@ -1,17 +1,22 @@
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import binaries, { BinaryName } from '../../../../config/binaries';
import { AbstractBinary, FetchResult, BinaryItem, BinaryAdapter } from './AbstractBinary';
import type { BinaryName } from '../../../../config/binaries.js';
import binaries from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Imagemin)
export class ImageminBinary extends AbstractBinary {
async init() {
async initFetch() {
// do nothing
return;
}
async fetch(dir: string, binaryName: BinaryName): Promise<FetchResult | undefined> {
async fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined> {
const binaryConfig = binaries[binaryName];
const dirItems: {
[key: string]: BinaryItem[];
@@ -66,7 +71,7 @@ export class ImageminBinary extends AbstractBinary {
size: '-',
isDir: false,
url: `${binaryConfig.distUrl}/${binaryConfig.repo}${platformDir}${name}`,
ignoreDownloadStatuses: [ 404 ],
ignoreDownloadStatuses: [404],
});
}
} else {
@@ -88,7 +93,7 @@ export class ImageminBinary extends AbstractBinary {
size: '-',
isDir: false,
url: `${binaryConfig.distUrl}/${binaryConfig.repo}${platformArchDir}${name}`,
ignoreDownloadStatuses: [ 404 ],
ignoreDownloadStatuses: [404],
});
}
}

View File

@@ -1,17 +1,23 @@
import { basename } from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import binaries, { BinaryName } from '../../../../config/binaries';
import { AbstractBinary, FetchResult, BinaryItem, BinaryAdapter } from './AbstractBinary';
import { BinaryType } from '../../enum/Binary.js';
import type { BinaryName } from '../../../../config/binaries.js';
import binaries from '../../../../config/binaries.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Node)
export class NodeBinary extends AbstractBinary {
async init() {
async initFetch() {
// do nothing
return;
}
async fetch(dir: string, binaryName: BinaryName): Promise<FetchResult | undefined> {
async fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined> {
const binaryConfig = binaries[binaryName];
const url = `${binaryConfig.distUrl}${dir}`;
const html = await this.requestXml(url);
@@ -21,26 +27,40 @@ export class NodeBinary extends AbstractBinary {
// <a href="index.tab">index.tab</a> 17-Dec-2021 23:16 136319
// <a href="node-0.0.1.tar.gz">node-0.0.1.tar.gz</a> 26-Aug-2011 16:22 2846972
// <a href="node-v14.0.0-nightly20200119b318926634-linux-armv7l.tar.xz">node-v14.0.0-nightly20200119b318926634-linux-ar..&gt;</a> 19-Jan-2020 06:07 18565976
const re = /<a href="([^\"]+?)"[^>]*?>[^<]+?<\/a>\s+?([\w\-]+? \w{2}\:\d{2})\s+?(\d+|\-)/ig;
// new html format
// <a href="docs/">docs/</a> - -
// <a href="win-x64/">win-x64/</a> - -
// <a href="win-x86/">win-x86/</a> - -
// <a href="/dist/v18.15.0/SHASUMS256.txt.asc">SHASUMS256.txt.asc</a> 04-Nov-2024 17:29 3.7 KB
// <a href="/dist/v18.15.0/SHASUMS256.txt.sig">SHASUMS256.txt.sig</a> 04-Nov-2024 17:29 310 B
// <a href="/dist/v18.15.0/SHASUMS256.txt">SHASUMS256.txt</a> 04-Nov-2024 17:29 3.2 KB
const re =
/<a href="([^"]+?)"[^>]*?>[^<]+?<\/a>\s+?((?:[\w-]+? \w{2}:\d{2})|-)\s+?([\d.\-\s\w]+)/gi;
const matchs = html.matchAll(re);
const items: BinaryItem[] = [];
for (const m of matchs) {
const name = m[1];
let name = m[1];
const isDir = name.endsWith('/');
if (!isDir) {
// /dist/v18.15.0/SHASUMS256.txt => SHASUMS256.txt
name = basename(name);
}
const fileUrl = isDir ? '' : `${url}${name}`;
const date = m[2];
const size = m[3];
const size = m[3].trim();
if (size === '0') continue;
if (binaryConfig.ignoreFiles?.includes(`${dir}${name}`)) continue;
items.push({
const item = {
name,
isDir,
url: fileUrl,
size,
date,
ignoreDownloadStatuses: binaryConfig.options?.ignoreDownloadStatuses,
});
};
items.push(item);
}
return { items, nextParams: null };
}

View File

@@ -1,21 +1,27 @@
import { join } from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import binaries, { BinaryName } from '../../../../config/binaries';
import { join } from 'path';
import { AbstractBinary, FetchResult, BinaryItem, BinaryAdapter } from './AbstractBinary';
import type { BinaryName } from '../../../../config/binaries.js';
import binaries from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.NodePreGyp)
export class NodePreGypBinary extends AbstractBinary {
async init() {
async initFetch() {
// do nothing
return;
}
// https://github.com/mapbox/node-pre-gyp
async fetch(dir: string, binaryName: BinaryName): Promise<FetchResult | undefined> {
async fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined> {
const binaryConfig = binaries[binaryName];
const pkgUrl = `https://registry.npmjs.com/${binaryName}`;
const npmPackageName = binaryConfig.options?.npmPackageName ?? binaryName;
const pkgUrl = `https://registry.npmjs.com/${npmPackageName}`;
const data = await this.requestJSON(pkgUrl);
const dirItems: {
[key: string]: BinaryItem[];
@@ -32,20 +38,28 @@ export class NodePreGypBinary extends AbstractBinary {
if (!pkgVersion.binary) continue;
// https://github.com/mapbox/node-pre-gyp#package_name
// defaults to {module_name}-v{version}-{node_abi}-{platform}-{arch}.tar.gz
let binaryFile = pkgVersion.binary.package_name
|| '{module_name}-v{version}-{node_abi}-{platform}-{arch}.tar.gz';
let binaryFile =
pkgVersion.binary.package_name ||
'{module_name}-v{version}-{node_abi}-{platform}-{arch}.tar.gz';
if (!binaryFile) continue;
const moduleName = pkgVersion.binary.module_name || pkgVersion.name;
binaryFile = binaryFile.replace('{version}', version)
binaryFile = binaryFile
.replace('{version}', version)
.replace('{module_name}', moduleName);
let currentDir = dirItems['/'];
let versionPrefix = '';
let remotePath = pkgVersion.binary.remote_path;
const napiVersions = pkgVersion.binary.napi_versions ?? [];
if (binaryConfig.options?.requiredNapiVersions && napiVersions.length === 0) continue;
if (
binaryConfig.options?.requiredNapiVersions &&
napiVersions.length === 0
)
continue;
if (remotePath?.includes('{version}')) {
const dirName = remotePath.includes('v{version}') ? `v${version}` : version;
const dirName = remotePath.includes('v{version}')
? `v${version}`
: version;
versionPrefix = `/${dirName}`;
dirItems['/'].push({
name: `${dirName}/`,
@@ -66,17 +80,20 @@ export class NodePreGypBinary extends AbstractBinary {
// "remote_path": "{name}/v{version}",
// "package_name": "{node_abi}-{platform}-{arch}-{libc}.tar.gz"
// },
if (binaryFile.includes('{node_abi}')
&& binaryFile.includes('{platform}')
&& binaryFile.includes('{arch}')
&& binaryFile.includes('{libc}')) {
if (
binaryFile.includes('{node_abi}') &&
binaryFile.includes('{platform}') &&
binaryFile.includes('{arch}') &&
binaryFile.includes('{libc}')
) {
for (const nodeAbi of nodeABIVersions) {
for (const platform of nodePlatforms) {
const archs = nodeArchs[platform];
const libcs = nodeLibcs[platform];
for (const arch of archs) {
for (const libc of libcs) {
const name = binaryFile.replace('{node_abi}', `node-v${nodeAbi}`)
const name = binaryFile
.replace('{node_abi}', `node-v${nodeAbi}`)
.replace('{platform}', platform)
.replace('{arch}', arch)
.replace('{libc}', libc);
@@ -86,20 +103,23 @@ export class NodePreGypBinary extends AbstractBinary {
size: '-',
isDir: false,
url: `${binaryConfig.distUrl}/${binaryName}${versionPrefix}/${name}`,
ignoreDownloadStatuses: [ 404 ],
ignoreDownloadStatuses: [404],
});
}
}
}
}
} else if (binaryFile.includes('{node_abi}')
&& binaryFile.includes('{platform}')
&& binaryFile.includes('{arch}')) {
} else if (
binaryFile.includes('{node_abi}') &&
binaryFile.includes('{platform}') &&
binaryFile.includes('{arch}')
) {
for (const nodeAbi of nodeABIVersions) {
for (const platform of nodePlatforms) {
const archs = nodeArchs[platform];
for (const arch of archs) {
const name = binaryFile.replace('{node_abi}', `node-v${nodeAbi}`)
const name = binaryFile
.replace('{node_abi}', `node-v${nodeAbi}`)
.replace('{platform}', platform)
.replace('{arch}', arch);
currentDir.push({
@@ -108,12 +128,15 @@ export class NodePreGypBinary extends AbstractBinary {
size: '-',
isDir: false,
url: `${binaryConfig.distUrl}/${binaryName}${versionPrefix}/${name}`,
ignoreDownloadStatuses: [ 404 ],
ignoreDownloadStatuses: [404],
});
}
}
}
} else if (binaryFile.includes('{platform}-{arch}-{node_napi_label}-{libc}') && napiVersions.length > 0) {
} else if (
binaryFile.includes('{platform}-{arch}-{node_napi_label}-{libc}') &&
napiVersions.length > 0
) {
// https://skia-canvas.s3.us-east-1.amazonaws.com/v0.9.30/darwin-arm64-napi-v6-unknown.tar.gz
// https://github.com/samizdatco/skia-canvas/blob/2a75801d7cce3b4e4e6ad015a173daefaa8465e6/package.json#L48
// "binary": {
@@ -132,7 +155,8 @@ export class NodePreGypBinary extends AbstractBinary {
for (const arch of archs) {
for (const libc of libcs) {
for (const napiVersion of napiVersions) {
const name = binaryFile.replace('{platform}', platform)
const name = binaryFile
.replace('{platform}', platform)
.replace('{arch}', arch)
.replace('{node_napi_label}', `napi-v${napiVersion}`)
.replace('{libc}', libc);
@@ -142,7 +166,7 @@ export class NodePreGypBinary extends AbstractBinary {
size: '-',
isDir: false,
url: `${binaryConfig.distUrl}${versionPrefix}/${name}`,
ignoreDownloadStatuses: [ 404, 403 ],
ignoreDownloadStatuses: [404, 403],
});
}
}
@@ -164,10 +188,12 @@ export class NodePreGypBinary extends AbstractBinary {
const archs = nodeArchs[platform];
for (const arch of archs) {
for (const napiVersion of napiVersions) {
const binaryFileName = binaryFile.replace('{platform}', platform)
const binaryFileName = binaryFile
.replace('{platform}', platform)
.replace('{arch}', arch)
.replace('{node_napi_label}', napiVersion);
remotePath = remotePath.replace('{module_name}', moduleName)
remotePath = remotePath
.replace('{module_name}', moduleName)
.replace('{name}', binaryName)
.replace('{version}', version)
.replace('{configuration}', 'Release');
@@ -179,12 +205,15 @@ export class NodePreGypBinary extends AbstractBinary {
size: '-',
isDir: false,
url: remoteUrl,
ignoreDownloadStatuses: [ 404 ],
ignoreDownloadStatuses: [404],
});
}
}
}
} else if (binaryFile.includes('{platform}') && binaryFile.includes('{arch}')) {
} else if (
binaryFile.includes('{platform}') &&
binaryFile.includes('{arch}')
) {
// https://github.com/grpc/grpc-node/blob/master/packages/grpc-tools/package.json#L29
// "binary": {
// "module_name": "grpc_tools",
@@ -204,9 +233,11 @@ export class NodePreGypBinary extends AbstractBinary {
for (const platform of nodePlatforms) {
const archs = nodeArchs[platform];
for (const arch of archs) {
const binaryFileName = binaryFile.replace('{platform}', platform)
const binaryFileName = binaryFile
.replace('{platform}', platform)
.replace('{arch}', arch);
remotePath = remotePath.replace('{module_name}', moduleName)
remotePath = remotePath
.replace('{module_name}', moduleName)
.replace('{name}', binaryName)
.replace('{version}', version)
.replace('{configuration}', 'Release');
@@ -218,7 +249,7 @@ export class NodePreGypBinary extends AbstractBinary {
size: '-',
isDir: false,
url: remoteUrl,
ignoreDownloadStatuses: [ 404 ],
ignoreDownloadStatuses: [404],
});
}
}

View File

@@ -1,8 +1,9 @@
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import binaries from '../../../../config/binaries';
import { FetchResult, BinaryItem, BinaryAdapter } from './AbstractBinary';
import { BucketBinary } from './BucketBinary';
import binaries from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { BinaryAdapter } from './AbstractBinary.js';
import { BucketBinary } from './BucketBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Nwjs)
@@ -14,7 +15,9 @@ export class NwjsBinary extends BucketBinary {
const isRootDir = dir === '/';
// /foo/ => foo/
const subDir = dir.substring(1);
const url = isRootDir ? binaryConfig.distUrl : `${this.s3Url}${encodeURIComponent(subDir)}`;
const url = isRootDir
? binaryConfig.distUrl
: `${this.s3Url}${encodeURIComponent(subDir)}`;
const xml = await this.requestXml(url);
if (!xml) return;
@@ -25,7 +28,8 @@ export class NwjsBinary extends BucketBinary {
// <tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="v0.15.0-rc1/">v0.15.0-rc1/</a></td><td align="right">06-May-2016 12:24 </td><td align="right"> - </td><td>&nbsp;</td></tr>
// <tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="v0.15.0-rc2/">v0.15.0-rc2/</a></td><td align="right">13-May-2016 20:13 </td><td align="right"> - </td><td>&nbsp;</td></tr>
const items: BinaryItem[] = [];
const re = /<td><a [^>]+?>([^<]+?\/)<\/a><\/td><td [^>]+?>([^>]+?)<\/td>/ig;
const re =
/<td><a [^>]+?>([^<]+?\/)<\/a><\/td><td [^>]+?>([^>]+?)<\/td>/gi;
const matchs = xml.matchAll(re);
for (const m of matchs) {
const name = m[1].trim();

View File

@@ -1,9 +1,9 @@
import { AbstractBinary, BinaryAdapter, BinaryItem, FetchResult } from './AbstractBinary';
import util from 'util';
import path from 'path';
import util from 'node:util';
import path from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import { BinaryType } from '../../enum/Binary.js';
import type { BinaryItem, FetchResult } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
const PACKAGE_URL = 'https://registry.npmjs.com/playwright-core';
const DOWNLOAD_HOST = 'https://playwright.azureedge.net/';
@@ -11,146 +11,348 @@ const DOWNLOAD_HOST = 'https://playwright.azureedge.net/';
// https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/registry/index.ts
/* eslint-disable quote-props */
const DOWNLOAD_PATHS = {
'chromium': {
chromium: {
'<unknown>': undefined,
'generic-linux': 'builds/chromium/%s/chromium-linux.zip',
'generic-linux-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'ubuntu18.04': 'builds/chromium/%s/chromium-linux.zip',
'ubuntu20.04': 'builds/chromium/%s/chromium-linux.zip',
'ubuntu22.04': 'builds/chromium/%s/chromium-linux.zip',
'ubuntu18.04-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64': 'builds/chromium/%s/chromium-linux.zip',
'ubuntu22.04-x64': 'builds/chromium/%s/chromium-linux.zip',
'ubuntu24.04-x64': 'builds/chromium/%s/chromium-linux.zip',
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'ubuntu22.04-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'ubuntu24.04-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'debian11-x64': 'builds/chromium/%s/chromium-linux.zip',
'debian11-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'debian12-x64': 'builds/chromium/%s/chromium-linux.zip',
'debian12-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'mac10.13': 'builds/chromium/%s/chromium-mac.zip',
'mac10.14': 'builds/chromium/%s/chromium-mac.zip',
'mac10.15': 'builds/chromium/%s/chromium-mac.zip',
'mac11': 'builds/chromium/%s/chromium-mac.zip',
mac11: 'builds/chromium/%s/chromium-mac.zip',
'mac11-arm64': 'builds/chromium/%s/chromium-mac-arm64.zip',
'mac12': 'builds/chromium/%s/chromium-mac.zip',
mac12: 'builds/chromium/%s/chromium-mac.zip',
'mac12-arm64': 'builds/chromium/%s/chromium-mac-arm64.zip',
'win64': 'builds/chromium/%s/chromium-win64.zip',
mac13: 'builds/chromium/%s/chromium-mac.zip',
'mac13-arm64': 'builds/chromium/%s/chromium-mac-arm64.zip',
mac14: 'builds/chromium/%s/chromium-mac.zip',
'mac14-arm64': 'builds/chromium/%s/chromium-mac-arm64.zip',
mac15: 'builds/chromium/%s/chromium-mac.zip',
'mac15-arm64': 'builds/chromium/%s/chromium-mac-arm64.zip',
win64: 'builds/chromium/%s/chromium-win64.zip',
},
'chromium-headless-shell': {
'<unknown>': undefined,
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64': 'builds/chromium/%s/chromium-headless-shell-linux.zip',
'ubuntu22.04-x64': 'builds/chromium/%s/chromium-headless-shell-linux.zip',
'ubuntu24.04-x64': 'builds/chromium/%s/chromium-headless-shell-linux.zip',
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64':
'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
'ubuntu22.04-arm64':
'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
'ubuntu24.04-arm64':
'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
'debian11-x64': 'builds/chromium/%s/chromium-headless-shell-linux.zip',
'debian11-arm64':
'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
'debian12-x64': 'builds/chromium/%s/chromium-headless-shell-linux.zip',
'debian12-arm64':
'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
'mac10.13': undefined,
'mac10.14': undefined,
'mac10.15': undefined,
mac11: 'builds/chromium/%s/chromium-headless-shell-mac.zip',
'mac11-arm64': 'builds/chromium/%s/chromium-headless-shell-mac-arm64.zip',
mac12: 'builds/chromium/%s/chromium-headless-shell-mac.zip',
'mac12-arm64': 'builds/chromium/%s/chromium-headless-shell-mac-arm64.zip',
mac13: 'builds/chromium/%s/chromium-headless-shell-mac.zip',
'mac13-arm64': 'builds/chromium/%s/chromium-headless-shell-mac-arm64.zip',
mac14: 'builds/chromium/%s/chromium-headless-shell-mac.zip',
'mac14-arm64': 'builds/chromium/%s/chromium-headless-shell-mac-arm64.zip',
mac15: 'builds/chromium/%s/chromium-headless-shell-mac.zip',
'mac15-arm64': 'builds/chromium/%s/chromium-headless-shell-mac-arm64.zip',
win64: 'builds/chromium/%s/chromium-headless-shell-win64.zip',
},
'chromium-tip-of-tree': {
'<unknown>': undefined,
'generic-linux': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip',
'generic-linux-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'ubuntu18.04': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip',
'ubuntu20.04': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip',
'ubuntu22.04': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip',
'ubuntu18.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'ubuntu20.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'ubuntu22.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip',
'ubuntu22.04-x64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip',
'ubuntu24.04-x64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip',
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'ubuntu22.04-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'ubuntu24.04-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'debian11-x64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip',
'debian11-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'debian12-x64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip',
'debian12-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'mac10.13': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac10.14': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac10.15': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac11': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac11-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip',
'mac12': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac12-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip',
'win64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-win64.zip',
mac11: 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac11-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip',
mac12: 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac12-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip',
mac13: 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac13-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip',
mac14: 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac14-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip',
mac15: 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac15-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip',
win64: 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-win64.zip',
},
'chromium-with-symbols': {
'chromium-tip-of-tree-headless-shell': {
'<unknown>': undefined,
'generic-linux': 'builds/chromium/%s/chromium-with-symbols-linux.zip',
'generic-linux-arm64': 'builds/chromium/%s/chromium-with-symbols-linux-arm64.zip',
'ubuntu18.04': 'builds/chromium/%s/chromium-with-symbols-linux.zip',
'ubuntu20.04': 'builds/chromium/%s/chromium-with-symbols-linux.zip',
'ubuntu22.04': 'builds/chromium/%s/chromium-with-symbols-linux.zip',
'ubuntu18.04-arm64': 'builds/chromium/%s/chromium-with-symbols-linux-arm64.zip',
'ubuntu20.04-arm64': 'builds/chromium/%s/chromium-with-symbols-linux-arm64.zip',
'ubuntu22.04-arm64': 'builds/chromium/%s/chromium-with-symbols-linux-arm64.zip',
'mac10.13': 'builds/chromium/%s/chromium-with-symbols-mac.zip',
'mac10.14': 'builds/chromium/%s/chromium-with-symbols-mac.zip',
'mac10.15': 'builds/chromium/%s/chromium-with-symbols-mac.zip',
'mac11': 'builds/chromium/%s/chromium-with-symbols-mac.zip',
'mac11-arm64': 'builds/chromium/%s/chromium-with-symbols-mac-arm64.zip',
'mac12': 'builds/chromium/%s/chromium-with-symbols-mac.zip',
'mac12-arm64': 'builds/chromium/%s/chromium-with-symbols-mac-arm64.zip',
'win64': 'builds/chromium/%s/chromium-with-symbols-win64.zip',
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip',
'ubuntu22.04-x64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip',
'ubuntu24.04-x64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip',
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'ubuntu22.04-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'ubuntu24.04-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'debian11-x64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip',
'debian11-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'debian12-x64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip',
'debian12-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'mac10.13': undefined,
'mac10.14': undefined,
'mac10.15': undefined,
mac11:
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip',
'mac11-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip',
mac12:
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip',
'mac12-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip',
mac13:
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip',
'mac13-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip',
mac14:
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip',
'mac14-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip',
mac15:
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip',
'mac15-arm64':
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip',
win64:
'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-win64.zip',
},
'firefox': {
firefox: {
'<unknown>': undefined,
'generic-linux': 'builds/firefox/%s/firefox-ubuntu-20.04.zip',
'generic-linux-arm64': 'builds/firefox/%s/firefox-ubuntu-20.04-arm64.zip',
'ubuntu18.04': 'builds/firefox/%s/firefox-ubuntu-18.04.zip',
'ubuntu20.04': 'builds/firefox/%s/firefox-ubuntu-20.04.zip',
'ubuntu22.04': 'builds/firefox/%s/firefox-ubuntu-22.04.zip',
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64': 'builds/firefox/%s/firefox-ubuntu-20.04.zip',
'ubuntu22.04-x64': 'builds/firefox/%s/firefox-ubuntu-22.04.zip',
'ubuntu24.04-x64': 'builds/firefox/%s/firefox-ubuntu-24.04.zip',
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': 'builds/firefox/%s/firefox-ubuntu-20.04-arm64.zip',
'ubuntu22.04-arm64': 'builds/firefox/%s/firefox-ubuntu-22.04-arm64.zip',
'mac10.13': 'builds/firefox/%s/firefox-mac-11.zip',
'mac10.14': 'builds/firefox/%s/firefox-mac-11.zip',
'mac10.15': 'builds/firefox/%s/firefox-mac-11.zip',
'mac11': 'builds/firefox/%s/firefox-mac-11.zip',
'mac11-arm64': 'builds/firefox/%s/firefox-mac-11-arm64.zip',
'mac12': 'builds/firefox/%s/firefox-mac-11.zip',
'mac12-arm64': 'builds/firefox/%s/firefox-mac-11-arm64.zip',
'win64': 'builds/firefox/%s/firefox-win64.zip',
'ubuntu24.04-arm64': 'builds/firefox/%s/firefox-ubuntu-24.04-arm64.zip',
'debian11-x64': 'builds/firefox/%s/firefox-debian-11.zip',
'debian11-arm64': 'builds/firefox/%s/firefox-debian-11-arm64.zip',
'debian12-x64': 'builds/firefox/%s/firefox-debian-12.zip',
'debian12-arm64': 'builds/firefox/%s/firefox-debian-12-arm64.zip',
'mac10.13': 'builds/firefox/%s/firefox-mac.zip',
'mac10.14': 'builds/firefox/%s/firefox-mac.zip',
'mac10.15': 'builds/firefox/%s/firefox-mac.zip',
mac11: 'builds/firefox/%s/firefox-mac.zip',
'mac11-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
mac12: 'builds/firefox/%s/firefox-mac.zip',
'mac12-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
mac13: 'builds/firefox/%s/firefox-mac.zip',
'mac13-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
mac14: 'builds/firefox/%s/firefox-mac.zip',
'mac14-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
mac15: 'builds/firefox/%s/firefox-mac.zip',
'mac15-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
win64: 'builds/firefox/%s/firefox-win64.zip',
},
'firefox-beta': {
'<unknown>': undefined,
'generic-linux': 'builds/firefox-beta/%s/firefox-beta-ubuntu-20.04.zip',
'generic-linux-arm64': undefined,
'ubuntu18.04': 'builds/firefox-beta/%s/firefox-beta-ubuntu-18.04.zip',
'ubuntu20.04': 'builds/firefox-beta/%s/firefox-beta-ubuntu-20.04.zip',
'ubuntu22.04': 'builds/firefox-beta/%s/firefox-beta-ubuntu-22.04.zip',
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64': 'builds/firefox-beta/%s/firefox-beta-ubuntu-20.04.zip',
'ubuntu22.04-x64': 'builds/firefox-beta/%s/firefox-beta-ubuntu-22.04.zip',
'ubuntu24.04-x64': 'builds/firefox-beta/%s/firefox-beta-ubuntu-24.04.zip',
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': undefined,
'ubuntu22.04-arm64': 'builds/firefox-beta/%s/firefox-beta-ubuntu-22.04-arm64.zip',
'mac10.13': 'builds/firefox-beta/%s/firefox-beta-mac-11.zip',
'mac10.14': 'builds/firefox-beta/%s/firefox-beta-mac-11.zip',
'mac10.15': 'builds/firefox-beta/%s/firefox-beta-mac-11.zip',
'mac11': 'builds/firefox-beta/%s/firefox-beta-mac-11.zip',
'mac11-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-11-arm64.zip',
'mac12': 'builds/firefox-beta/%s/firefox-beta-mac-11.zip',
'mac12-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-11-arm64.zip',
'win64': 'builds/firefox-beta/%s/firefox-beta-win64.zip',
'ubuntu22.04-arm64':
'builds/firefox-beta/%s/firefox-beta-ubuntu-22.04-arm64.zip',
'ubuntu24.04-arm64':
'builds/firefox-beta/%s/firefox-beta-ubuntu-24.04-arm64.zip',
'debian11-x64': 'builds/firefox-beta/%s/firefox-beta-debian-11.zip',
'debian11-arm64': 'builds/firefox-beta/%s/firefox-beta-debian-11-arm64.zip',
'debian12-x64': 'builds/firefox-beta/%s/firefox-beta-debian-12.zip',
'debian12-arm64': 'builds/firefox-beta/%s/firefox-beta-debian-12-arm64.zip',
'mac10.13': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
'mac10.14': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
'mac10.15': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
mac11: 'builds/firefox-beta/%s/firefox-beta-mac.zip',
'mac11-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
mac12: 'builds/firefox-beta/%s/firefox-beta-mac.zip',
'mac12-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
mac13: 'builds/firefox-beta/%s/firefox-beta-mac.zip',
'mac13-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
mac14: 'builds/firefox-beta/%s/firefox-beta-mac.zip',
'mac14-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
mac15: 'builds/firefox-beta/%s/firefox-beta-mac.zip',
'mac15-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
win64: 'builds/firefox-beta/%s/firefox-beta-win64.zip',
},
'webkit': {
webkit: {
'<unknown>': undefined,
'generic-linux': 'builds/webkit/%s/webkit-ubuntu-20.04.zip',
'generic-linux-arm64': 'builds/webkit/%s/webkit-ubuntu-20.04-arm64.zip',
'ubuntu18.04': 'builds/webkit/%s/webkit-ubuntu-18.04.zip',
'ubuntu20.04': 'builds/webkit/%s/webkit-ubuntu-20.04.zip',
'ubuntu22.04': 'builds/webkit/%s/webkit-ubuntu-22.04.zip',
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64': 'builds/webkit/%s/webkit-ubuntu-20.04.zip',
'ubuntu22.04-x64': 'builds/webkit/%s/webkit-ubuntu-22.04.zip',
'ubuntu24.04-x64': 'builds/webkit/%s/webkit-ubuntu-24.04.zip',
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': 'builds/webkit/%s/webkit-ubuntu-20.04-arm64.zip',
'ubuntu22.04-arm64': 'builds/webkit/%s/webkit-ubuntu-22.04-arm64.zip',
'ubuntu24.04-arm64': 'builds/webkit/%s/webkit-ubuntu-24.04-arm64.zip',
'debian11-x64': 'builds/webkit/%s/webkit-debian-11.zip',
'debian11-arm64': 'builds/webkit/%s/webkit-debian-11-arm64.zip',
'debian12-x64': 'builds/webkit/%s/webkit-debian-12.zip',
'debian12-arm64': 'builds/webkit/%s/webkit-debian-12-arm64.zip',
'mac10.13': undefined,
'mac10.14': 'builds/deprecated-webkit-mac-10.14/%s/deprecated-webkit-mac-10.14.zip',
'mac10.15': 'builds/webkit/%s/webkit-mac-10.15.zip',
'mac11': 'builds/webkit/%s/webkit-mac-11.zip',
'mac10.14':
'builds/deprecated-webkit-mac-10.14/%s/deprecated-webkit-mac-10.14.zip',
'mac10.15':
'builds/deprecated-webkit-mac-10.15/%s/deprecated-webkit-mac-10.15.zip',
mac11: 'builds/webkit/%s/webkit-mac-11.zip',
'mac11-arm64': 'builds/webkit/%s/webkit-mac-11-arm64.zip',
'mac12': 'builds/webkit/%s/webkit-mac-12.zip',
mac12: 'builds/webkit/%s/webkit-mac-12.zip',
'mac12-arm64': 'builds/webkit/%s/webkit-mac-12-arm64.zip',
'win64': 'builds/webkit/%s/webkit-win64.zip',
mac13: 'builds/webkit/%s/webkit-mac-13.zip',
'mac13-arm64': 'builds/webkit/%s/webkit-mac-13-arm64.zip',
mac14: 'builds/webkit/%s/webkit-mac-14.zip',
'mac14-arm64': 'builds/webkit/%s/webkit-mac-14-arm64.zip',
mac15: 'builds/webkit/%s/webkit-mac-15.zip',
'mac15-arm64': 'builds/webkit/%s/webkit-mac-15-arm64.zip',
win64: 'builds/webkit/%s/webkit-win64.zip',
},
'ffmpeg': {
ffmpeg: {
'<unknown>': undefined,
'generic-linux': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'generic-linux-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'ubuntu18.04': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'ubuntu20.04': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'ubuntu22.04': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'ubuntu18.04-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'ubuntu22.04-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'ubuntu24.04-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'ubuntu22.04-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'ubuntu24.04-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'debian11-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'debian11-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'debian12-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'debian12-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'mac10.13': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac10.14': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac10.15': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac11': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
mac11: 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac11-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
'mac12': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
mac12: 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac12-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
'win64': 'builds/ffmpeg/%s/ffmpeg-win64.zip',
mac13: 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac13-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
mac14: 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac14-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
mac15: 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac15-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
win64: 'builds/ffmpeg/%s/ffmpeg-win64.zip',
},
};
winldd: {
'<unknown>': undefined,
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64': undefined,
'ubuntu22.04-x64': undefined,
'ubuntu24.04-x64': undefined,
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': undefined,
'ubuntu22.04-arm64': undefined,
'ubuntu24.04-arm64': undefined,
'debian11-x64': undefined,
'debian11-arm64': undefined,
'debian12-x64': undefined,
'debian12-arm64': undefined,
'mac10.13': undefined,
'mac10.14': undefined,
'mac10.15': undefined,
mac11: undefined,
'mac11-arm64': undefined,
mac12: undefined,
'mac12-arm64': undefined,
mac13: undefined,
'mac13-arm64': undefined,
mac14: undefined,
'mac14-arm64': undefined,
mac15: undefined,
'mac15-arm64': undefined,
win64: 'builds/winldd/%s/winldd-win64.zip',
},
android: {
'<unknown>': 'builds/android/%s/android.zip',
'ubuntu18.04-x64': undefined,
'ubuntu20.04-x64': 'builds/android/%s/android.zip',
'ubuntu22.04-x64': 'builds/android/%s/android.zip',
'ubuntu24.04-x64': 'builds/android/%s/android.zip',
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': 'builds/android/%s/android.zip',
'ubuntu22.04-arm64': 'builds/android/%s/android.zip',
'ubuntu24.04-arm64': 'builds/android/%s/android.zip',
'debian11-x64': 'builds/android/%s/android.zip',
'debian11-arm64': 'builds/android/%s/android.zip',
'debian12-x64': 'builds/android/%s/android.zip',
'debian12-arm64': 'builds/android/%s/android.zip',
'mac10.13': 'builds/android/%s/android.zip',
'mac10.14': 'builds/android/%s/android.zip',
'mac10.15': 'builds/android/%s/android.zip',
mac11: 'builds/android/%s/android.zip',
'mac11-arm64': 'builds/android/%s/android.zip',
mac12: 'builds/android/%s/android.zip',
'mac12-arm64': 'builds/android/%s/android.zip',
mac13: 'builds/android/%s/android.zip',
'mac13-arm64': 'builds/android/%s/android.zip',
mac14: 'builds/android/%s/android.zip',
'mac14-arm64': 'builds/android/%s/android.zip',
mac15: 'builds/android/%s/android.zip',
'mac15-arm64': 'builds/android/%s/android.zip',
win64: 'builds/android/%s/android.zip',
},
} as const;
@SingletonProto()
@BinaryAdapter(BinaryType.Playwright)
export class PlaywrightBinary extends AbstractBinary {
private dirItems?: Record<string, BinaryItem[]>;
async init() {
async initFetch() {
this.dirItems = undefined;
}
@@ -158,23 +360,59 @@ export class PlaywrightBinary extends AbstractBinary {
if (!this.dirItems) {
const packageData = await this.requestJSON(PACKAGE_URL);
const nowDateISO = new Date().toISOString();
const buildDirs: BinaryItem[] = [];
for (const browserName of Object.keys(DOWNLOAD_PATHS)) {
if (
browserName === 'chromium-headless-shell' ||
browserName === 'chromium-tip-of-tree-headless-shell'
) {
continue;
}
buildDirs.push({
name: `${browserName}/`,
isDir: true,
url: '',
size: '-',
date: nowDateISO,
});
}
this.dirItems = {
'/': [{ name: 'builds/', isDir: true, url: '', size: '-', date: nowDateISO }],
'/builds/': Object.keys(DOWNLOAD_PATHS).map(
dist => ({ name: `${dist}/`, isDir: true, url: '', size: '-', date: nowDateISO })),
...Object.fromEntries(Object.keys(DOWNLOAD_PATHS).map(dist => [ `/builds/${dist}/`, []])),
'/': [
{
name: 'builds/',
isDir: true,
url: '',
size: '-',
date: nowDateISO,
},
],
'/builds/': buildDirs,
};
for (const browserName of Object.keys(DOWNLOAD_PATHS)) {
if (
browserName === 'chromium-headless-shell' ||
browserName === 'chromium-tip-of-tree-headless-shell'
) {
continue;
}
this.dirItems[`/builds/${browserName}/`] = [];
}
// Only download beta and release versions of packages to reduce amount of request
const packageVersions = Object.keys(packageData.versions)
.filter(version => version.match(/^(?:\d+\.\d+\.\d+)(?:-beta-\d+)?$/))
// select recently update 20 items
.slice(-20);
const browsers: { name: string; revision: string; browserVersion: string; revisionOverrides?: Record<string, string> }[] = [];
const browsers: {
name: keyof typeof DOWNLOAD_PATHS;
revision: string;
browserVersion: string;
revisionOverrides?: Record<string, string>;
}[] = [];
await Promise.all(
packageVersions.map(version =>
this.requestJSON(
`https://unpkg.com/playwright-core@${version}/browsers.json`,
`https://unpkg.com/playwright-core@${version}/browsers.json`
)
.then(data => {
// browsers: [
@@ -190,24 +428,72 @@ export class PlaywrightBinary extends AbstractBinary {
})
.catch(err => {
/* c8 ignore next 2 */
this.logger.warn('[PlaywrightBinary.fetch:error] Playwright version %s browser data request failed: %s',
version, err);
}),
),
this.logger.warn(
'[PlaywrightBinary.fetch:error] Playwright version %s browser data request failed: %s',
version,
err
);
})
)
);
// if chromium-headless-shell not exists on browsers, copy chromium to chromium-headless-shell
if (
!browsers.find(browser => browser.name === 'chromium-headless-shell')
) {
const chromium = browsers.find(browser => browser.name === 'chromium');
// {
// "name": "chromium",
// "revision": "1155",
// "installByDefault": true,
// "browserVersion": "133.0.6943.16"
// }
if (chromium) {
browsers.push({
...chromium,
name: 'chromium-headless-shell',
});
}
}
// if chromium-tip-of-tree-headless-shell not exists on browsers, copy chromium-tip-of-tree to chromium-tip-of-tree-headless-shell
if (
!browsers.find(
browser => browser.name === 'chromium-tip-of-tree-headless-shell'
)
) {
const chromiumTipOfTree = browsers.find(
browser => browser.name === 'chromium-tip-of-tree'
);
if (chromiumTipOfTree) {
browsers.push({
...chromiumTipOfTree,
name: 'chromium-tip-of-tree-headless-shell',
});
}
}
for (const browser of browsers) {
const downloadPaths = DOWNLOAD_PATHS[browser.name];
if (!downloadPaths) continue;
for (const [ platform, remotePath ] of Object.entries(downloadPaths)) {
let browserDirname = browser.name;
if (browser.name === 'chromium-headless-shell') {
// chromium-headless-shell should be under chromium
// https://playwright.azureedge.net/builds/chromium/1155/chromium-headless-shell-mac-arm64.zip
browserDirname = 'chromium';
} else if (browser.name === 'chromium-tip-of-tree-headless-shell') {
// chromium-tip-of-tree-headless-shell should be under chromium-tip-of-tree
// https://playwright.azureedge.net/builds/chromium-tip-of-tree/1293/chromium-tip-of-tree-headless-shell-mac-arm64.zip
browserDirname = 'chromium-tip-of-tree';
}
for (const [platform, remotePath] of Object.entries(downloadPaths)) {
if (typeof remotePath !== 'string') continue;
const revision = browser.revisionOverrides?.[platform] ?? browser.revision;
const revision =
browser.revisionOverrides?.[platform] ?? browser.revision;
const itemDate = browser.browserVersion || revision;
const url = DOWNLOAD_HOST + util.format(remotePath, revision);
const name = path.basename(remotePath);
const dir = `/builds/${browser.name}/${revision}/`;
const dir = `/builds/${browserDirname}/${revision}/`;
if (!this.dirItems[dir]) {
this.dirItems[`/builds/${browser.name}/`].push({
this.dirItems[`/builds/${browserDirname}/`].push({
name: `${revision}/`,
isDir: true,
url: '',
@@ -217,7 +503,13 @@ export class PlaywrightBinary extends AbstractBinary {
this.dirItems[dir] = [];
}
if (!this.dirItems[dir].find(item => item.name === name)) {
this.dirItems[dir].push({ name, isDir: false, url, size: '-', date: itemDate });
this.dirItems[dir].push({
name,
isDir: false,
url,
size: '-',
date: itemDate,
});
}
}
}

View File

@@ -0,0 +1,139 @@
import path from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary.js';
import type { BinaryName } from '../../../../config/binaries.js';
import binaries from '../../../../config/binaries.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Prisma)
export class PrismaBinary extends AbstractBinary {
private dirItems: {
[key: string]: BinaryItem[];
} = {};
async initFetch() {
// https://github.com/cnpm/cnpmcore/issues/473#issuecomment-1562115738
const pkgUrl = 'https://registry.npmjs.com/@prisma/engines';
const data = await this.requestJSON(pkgUrl);
const modified = data.time.modified;
this.dirItems = {};
this.dirItems['/'] = [
{
name: 'all_commits/',
date: modified,
size: '-',
isDir: true,
url: '',
},
];
this.dirItems['/all_commits/'] = [];
const commitIdMap: Record<string, boolean> = {};
// https://list-binaries.prisma-orm.workers.dev/?delimiter=/&prefix=all_commits/61023c35d2c8762f66f09bc4183d2f630b541d08/
for (const version in data.versions) {
const major = parseInt(version.split('.', 1)[0]);
// need >= 3.0.0
if (major < 3) continue;
const date = data.time[version];
const pkg = data.versions[version];
// https://registry.npmjs.com/@prisma/engines/4.14.1
// https://registry.npmjs.com/@prisma/engines/5.7.0 should read from dependencies
const enginesVersion =
pkg.devDependencies?.['@prisma/engines-version'] ||
pkg.dependencies?.['@prisma/engines-version'] ||
'';
// "@prisma/engines-version": "4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c"
// "@prisma/engines-version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9"
const matched = /\.(\w{30,})$/.exec(enginesVersion);
if (!matched) continue;
const commitId = matched[1];
if (commitIdMap[commitId]) continue;
commitIdMap[commitId] = true;
this.dirItems['/all_commits/'].push({
name: `${commitId}/`,
date,
size: '-',
isDir: true,
url: '',
});
}
}
async fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined> {
const existsItems = this.dirItems[dir];
if (existsItems) {
return { items: existsItems, nextParams: null };
}
// /foo/ => foo/
const binaryConfig = binaries[binaryName];
const subDir = dir.substring(1);
const url = `${binaryConfig.distUrl}?delimiter=/&prefix=${encodeURIComponent(subDir)}`;
const result = await this.requestJSON(url);
return { items: this.#parseItems(result), nextParams: null };
}
#parseItems(result: any): BinaryItem[] {
const items: BinaryItem[] = [];
// objects": [
// {
// "uploaded": "2023-05-23T15:43:05.772Z",
// "checksums": {
// "md5": "d41d8cd98f00b204e9800998ecf8427e"
// },
// "httpEtag": "\"d41d8cd98f00b204e9800998ecf8427e\"",
// "etag": "d41d8cd98f00b204e9800998ecf8427e",
// "size": 0,
// "version": "7e77b6b8c1d214f2c6be3c959749b5a6",
// "key": "all_commits/61023c35d2c8762f66f09bc4183d2f630b541d08/darwin-arm64/.finished"
// },
// {
// "uploaded": "2023-05-23T15:41:33.861Z",
// "checksums": {
// "md5": "4822215a13ae372ae82afd12689fce37"
// },
// "httpEtag": "\"4822215a13ae372ae82afd12689fce37\"",
// "etag": "4822215a13ae372ae82afd12689fce37",
// "size": 96,
// "version": "7e77b6ba29d4e776023e4fa62825c13a",
// "key": "all_commits/61023c35d2c8762f66f09bc4183d2f630b541d08/darwin-arm64/libquery_engine.dylib.node.gz.sha256"
// },
// https://list-binaries.prisma-orm.workers.dev/?delimiter=/&prefix=all_commits/61023c35d2c8762f66f09bc4183d2f630b541d08/darwin-arm64/
const objects: {
uploaded: string;
size: number;
key: string;
}[] = result.objects || [];
for (const o of objects) {
const fullname = o.key;
// ignore size = 0
if (o.size === 0) continue;
const name = path.basename(fullname);
items.push({
name,
isDir: false,
// https://binaries.prisma.sh/all_commits/2452cc6313d52b8b9a96999ac0e974d0aedf88db/darwin-arm64/prisma-fmt.gz
url: `https://binaries.prisma.sh/${fullname}`,
size: o.size,
date: o.uploaded,
});
}
// delimitedPrefixes: [ 'all_commits/61023c35d2c8762f66f09bc4183d2f630b541d08/darwin-arm64/' ]
// https://list-binaries.prisma-orm.workers.dev/?delimiter=/&prefix=all_commits/61023c35d2c8762f66f09bc4183d2f630b541d08/
const delimitedPrefixes: string[] = result.delimitedPrefixes || [];
for (const fullname of delimitedPrefixes) {
const name = `${path.basename(fullname)}/`;
items.push({
name,
isDir: true,
url: '',
size: '-',
date: new Date().toISOString(),
});
}
return items;
}
}

View File

@@ -1,6 +1,7 @@
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import { AbstractBinary, FetchResult, BinaryItem, BinaryAdapter } from './AbstractBinary';
import { BinaryType } from '../../enum/Binary.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Puppeteer)
@@ -9,7 +10,7 @@ export class PuppeteerBinary extends AbstractBinary {
[key: string]: BinaryItem[];
};
async init() {
async initFetch() {
this.dirItems = undefined;
}
@@ -23,7 +24,9 @@ export class PuppeteerBinary extends AbstractBinary {
for (const version in data.versions) {
// find chromium versions
const pkg = data.versions[version];
const revision = pkg.puppeteer?.chromium_revision ? String(pkg.puppeteer.chromium_revision) : '';
const revision = pkg.puppeteer?.chromium_revision
? String(pkg.puppeteer.chromium_revision)
: '';
if (revision && !chromiumRevisions.has(revision)) {
chromiumRevisions.set(revision, data.time[version]);
}
@@ -35,16 +38,18 @@ export class PuppeteerBinary extends AbstractBinary {
// chromium: '768783',
// firefox: 'latest',
// };
const unpkgURL = 'https://unpkg.com/puppeteer-core@latest/lib/cjs/puppeteer/revisions.js';
const unpkgURL =
'https://unpkg.com/puppeteer-core@latest/lib/cjs/puppeteer/revisions.js';
const text = await this.requestXml(unpkgURL);
const m = /chromium:\s+\'(\d+)\'\,/.exec(text);
const m = /chromium:\s+'(\d+)',/.exec(text);
if (m && !chromiumRevisions.has(m[1])) {
chromiumRevisions.set(m[1], new Date().toISOString());
}
// download LAST_CHANGE
// https://github.com/chaopeng/chromium-downloader/blob/master/get-chromium#L28
const LAST_CHANGE_URL = 'https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2FLAST_CHANGE?alt=media';
const LAST_CHANGE_URL =
'https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2FLAST_CHANGE?alt=media';
const lastRevision = await this.requestXml(LAST_CHANGE_URL);
if (lastRevision) {
chromiumRevisions.set(lastRevision, new Date().toISOString());
@@ -77,7 +82,7 @@ export class PuppeteerBinary extends AbstractBinary {
// "11.0.0":"2021-11-03T09:29:12.751Z"
chromiumRevisions.set('901912', data.time['11.0.0']);
const platforms = [ 'Linux_x64', 'Mac', 'Mac_Arm', 'Win', 'Win_x64' ];
const platforms = ['Linux_x64', 'Mac', 'Mac_Arm', 'Win', 'Win_x64'];
for (const platform of platforms) {
this.dirItems['/'].push({
name: `${platform}/`,
@@ -88,7 +93,7 @@ export class PuppeteerBinary extends AbstractBinary {
});
this.dirItems[`/${platform}/`] = [];
}
for (const [ revision, date ] of chromiumRevisions.entries()) {
for (const [revision, date] of chromiumRevisions.entries()) {
// https://github.com/puppeteer/puppeteer/blob/eebf452d38b79bb2ea1a1ba84c3d2ea6f2f9f899/src/node/BrowserFetcher.ts#L40
// chrome: {
// linux: '%s/chromium-browser-snapshots/Linux_x64/%d/%s.zip',
@@ -113,7 +118,7 @@ export class PuppeteerBinary extends AbstractBinary {
size: '-',
isDir: false,
url: `https://storage.googleapis.com/chromium-browser-snapshots/${platform}/${revision}/${name}`,
ignoreDownloadStatuses: [ 404 ],
ignoreDownloadStatuses: [404],
},
];
}
@@ -124,10 +129,7 @@ export class PuppeteerBinary extends AbstractBinary {
}
// https://github.com/puppeteer/puppeteer/blob/eebf452d38b79bb2ea1a1ba84c3d2ea6f2f9f899/src/node/BrowserFetcher.ts#L72
private archiveName(
platform: string,
revision: string,
): string {
private archiveName(platform: string, revision: string): string {
if (platform === 'Linux_x64') return 'chrome-linux';
if (platform === 'Mac' || platform === 'Mac_Arm') return 'chrome-mac';
if (platform === 'Win' || platform === 'Win_x64') {

View File

@@ -1,11 +1,12 @@
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary';
import { AbstractBinary, FetchResult, BinaryItem, BinaryAdapter } from './AbstractBinary';
import { BinaryType } from '../../enum/Binary.js';
import type { FetchResult, BinaryItem } from './AbstractBinary.js';
import { AbstractBinary, BinaryAdapter } from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Sqlcipher)
export class SqlcipherBinary extends AbstractBinary {
async init() {
async initFetch() {
// do nothing
return;
}
@@ -16,7 +17,8 @@ export class SqlcipherBinary extends AbstractBinary {
} = {
'/': [],
};
const s3Url = 'https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher';
const s3Url =
'https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher';
const pkgUrl = 'https://registry.npmjs.com/@journeyapps/sqlcipher';
const data = await this.requestJSON(pkgUrl);
// https://github.com/journeyapps/node-sqlcipher/blob/master/.circleci/config.yml#L407
@@ -48,7 +50,8 @@ export class SqlcipherBinary extends AbstractBinary {
if (major < 5) continue;
// >= 5.0.0
const pkgVersion = data.versions[version];
const napiVersions = pkgVersion.binary && pkgVersion.binary.napi_versions || [];
const napiVersions =
(pkgVersion.binary && pkgVersion.binary.napi_versions) || [];
const date = data.time[version];
dirItems['/'].push({
name: `v${version}/`,
@@ -74,7 +77,7 @@ export class SqlcipherBinary extends AbstractBinary {
size: '-',
isDir: false,
url: `${s3Url}/v${version}/${name}`,
ignoreDownloadStatuses: [ 404, 403 ],
ignoreDownloadStatuses: [404, 403],
});
}
}

View File

@@ -1,14 +1,8 @@
import {
ImplDecorator,
Inject,
QualifierImplDecoratorUtil,
} from '@eggjs/tegg';
import { RegistryType } from '../../../common/enum/Registry';
import { Registry } from '../../../core/entity/Registry';
import {
EggHttpClient,
EggLogger,
} from 'egg';
import type { ImplDecorator } from '@eggjs/tegg';
import { Inject, QualifierImplDecoratorUtil } from '@eggjs/tegg';
import type { RegistryType } from '../../../common/enum/Registry.js';
import type { Registry } from '../../../core/entity/Registry.js';
import type { EggHttpClient, EggLogger } from 'egg';
export const CHANGE_STREAM_ATTRIBUTE = 'CHANGE_STREAM_ATTRIBUTE';
export type ChangesStreamChange = {
@@ -24,9 +18,16 @@ export abstract class AbstractChangeStream {
protected httpclient: EggHttpClient;
abstract getInitialSince(registry: Registry): Promise<string>;
abstract fetchChanges(registry: Registry, since: string): AsyncGenerator<ChangesStreamChange>;
abstract fetchChanges(
registry: Registry,
since: string
): AsyncGenerator<ChangesStreamChange>;
getChangesStreamUrl(registry: Registry, since: string, limit?: number): string {
getChangesStreamUrl(
registry: Registry,
since: string,
limit?: number
): string {
const url = new URL(registry.changeStream);
url.searchParams.set('since', since);
if (limit) {
@@ -36,5 +37,10 @@ export abstract class AbstractChangeStream {
}
}
export const RegistryChangesStream: ImplDecorator<AbstractChangeStream, typeof RegistryType> =
QualifierImplDecoratorUtil.generatorDecorator(AbstractChangeStream, CHANGE_STREAM_ATTRIBUTE);
export const RegistryChangesStream: ImplDecorator<
AbstractChangeStream,
typeof RegistryType
> = QualifierImplDecoratorUtil.generatorDecorator(
AbstractChangeStream,
CHANGE_STREAM_ATTRIBUTE
);

View File

@@ -1,15 +1,17 @@
import { SingletonProto } from '@eggjs/tegg';
import { RegistryType } from '../../../common/enum/Registry';
import { Registry } from '../../../core/entity/Registry';
import { E500 } from 'egg-errors';
import { AbstractChangeStream, RegistryChangesStream } from './AbstractChangesStream';
import { RegistryType } from '../../../common/enum/Registry.js';
import type { Registry } from '../../../core/entity/Registry.js';
import {
AbstractChangeStream,
RegistryChangesStream,
} from './AbstractChangesStream.js';
@SingletonProto()
@RegistryChangesStream(RegistryType.Cnpmcore)
export class CnpmcoreChangesStream extends AbstractChangeStream {
async getInitialSince(registry: Registry): Promise<string> {
const db = (new URL(registry.changeStream)).origin;
const db = new URL(registry.changeStream).origin;
const { status, data } = await this.httpclient.request(db, {
followRedirect: true,
timeout: 10000,
@@ -19,12 +21,17 @@ export class CnpmcoreChangesStream extends AbstractChangeStream {
throw new E500(`get getInitialSince failed: ${data.update_seq}`);
}
const since = String(data.update_seq - 10);
this.logger.warn('[NpmChangesStream.getInitialSince:firstSeq] GET %s status: %s, data: %j, since: %s',
registry.name, status, data, since);
this.logger.warn(
'[NpmChangesStream.getInitialSince:firstSeq] GET %s status: %s, data: %j, since: %s',
registry.name,
status,
data,
since
);
return since;
}
async* fetchChanges(registry: Registry, since: string) {
async *fetchChanges(registry: Registry, since: string) {
const db = this.getChangesStreamUrl(registry, since);
// json mode
const { data } = await this.httpclient.request(db, {

View File

@@ -1,30 +1,50 @@
import { SingletonProto } from '@eggjs/tegg';
import { RegistryType } from '../../../common/enum/Registry';
import { Registry } from '../../../core/entity/Registry';
import { E500 } from 'egg-errors';
import { AbstractChangeStream, RegistryChangesStream } from './AbstractChangesStream';
import { RegistryType } from '../../../common/enum/Registry.js';
import type { Registry } from '../../../core/entity/Registry.js';
import {
AbstractChangeStream,
RegistryChangesStream,
} from './AbstractChangesStream.js';
const MAX_LIMIT = 10000;
type FetchResults = {
results: {
seq: number;
type: string;
id: string;
changes: Record<string, string>[];
gmt_modified: Date;
}[];
};
@SingletonProto()
@RegistryChangesStream(RegistryType.Cnpmjsorg)
export class CnpmjsorgChangesStream extends AbstractChangeStream {
// cnpmjsorg 未实现 update_seq 字段
// 默认返回当前时间戳字符串
async getInitialSince(registry: Registry): Promise<string> {
const since = String((new Date()).getTime());
this.logger.warn(`[CnpmjsorgChangesStream.getInitialSince] since: ${since}, skip query ${registry.changeStream}`);
const since = String(new Date().getTime());
this.logger.warn(
`[CnpmjsorgChangesStream.getInitialSince] since: ${since}, skip query ${registry.changeStream}`
);
return since;
}
private async tryFetch(registry: Registry, since: string, limit = 1000) {
private async tryFetch(
registry: Registry,
since: string,
limit = 1000
): Promise<{ data: FetchResults }> {
if (limit > MAX_LIMIT) {
throw new E500(`limit too large, current since: ${since}, limit: ${limit}`);
throw new E500(
`limit too large, current since: ${since}, limit: ${limit}`
);
}
const db = this.getChangesStreamUrl(registry, since, limit);
// json mode
const res = await this.httpclient.request(db, {
const res = await this.httpclient.request<FetchResults>(db, {
followRedirect: true,
timeout: 30000,
dataType: 'json',
@@ -32,7 +52,7 @@ export class CnpmjsorgChangesStream extends AbstractChangeStream {
});
const { results = [] } = res.data;
if (results?.length >= limit) {
const [ first ] = results;
const [first] = results;
const last = results[results.length - 1];
if (first.gmt_modified === last.gmt_modified) {
return await this.tryFetch(registry, since, limit + 1000);
@@ -42,7 +62,7 @@ export class CnpmjsorgChangesStream extends AbstractChangeStream {
return res;
}
async* fetchChanges(registry: Registry, since: string) {
async *fetchChanges(registry: Registry, since: string) {
// ref: https://github.com/cnpm/cnpmjs.org/pull/1734
// 由于 cnpmjsorg 无法计算准确的 seq
// since 是一个时间戳,需要确保一次返回的结果中首尾两个 gmtModified 不相等

View File

@@ -1,15 +1,18 @@
import { SingletonProto } from '@eggjs/tegg';
import { E500 } from 'egg-errors';
import { RegistryType } from '../../../common/enum/Registry';
import { Registry } from '../../../core/entity/Registry';
import { AbstractChangeStream, ChangesStreamChange, RegistryChangesStream } from './AbstractChangesStream';
import { RegistryType } from '../../../common/enum/Registry.js';
import type { Registry } from '../../../core/entity/Registry.js';
import type { ChangesStreamChange } from './AbstractChangesStream.js';
import {
AbstractChangeStream,
RegistryChangesStream,
} from './AbstractChangesStream.js';
@SingletonProto()
@RegistryChangesStream(RegistryType.Npm)
export class NpmChangesStream extends AbstractChangeStream {
async getInitialSince(registry: Registry): Promise<string> {
const db = (new URL(registry.changeStream)).origin;
const db = new URL(registry.changeStream).origin;
const { status, data } = await this.httpclient.request(db, {
followRedirect: true,
timeout: 10000,
@@ -19,16 +22,22 @@ export class NpmChangesStream extends AbstractChangeStream {
if (!data.update_seq) {
throw new E500(`get getInitialSince failed: ${data.update_seq}`);
}
this.logger.warn('[NpmChangesStream.getInitialSince] GET %s status: %s, data: %j, since: %s',
registry.name, registry.changeStream, status, data, since);
this.logger.warn(
'[NpmChangesStream.getInitialSince] GET %s status: %s, data: %j, since: %s',
registry.name,
registry.changeStream,
status,
data,
since
);
return since;
}
async* fetchChanges(registry: Registry, since: string) {
async *fetchChanges(registry: Registry, since: string) {
const db = this.getChangesStreamUrl(registry, since);
const { res } = await this.httpclient.request(db, {
streaming: true,
timeout: 10000,
timeout: 60000,
});
let buf = '';
@@ -51,5 +60,4 @@ export class NpmChangesStream extends AbstractChangeStream {
}
}
}
}

View File

@@ -1,27 +1,35 @@
import { performance } from 'perf_hooks';
import { Advice, AdviceContext, IAdvice } from '@eggjs/tegg/aop';
import { performance } from 'node:perf_hooks';
import type { AdviceContext, IAdvice } from '@eggjs/tegg/aop';
import { Advice } from '@eggjs/tegg/aop';
import { Inject } from '@eggjs/tegg';
import { EggLogger } from 'egg';
import type { EggLogger } from 'egg';
const START = Symbol('AsyncTimer#start');
const SUCCEED = Symbol('AsyncTimer#succeed');
// auto print async function call performance timer log into logger
@Advice()
export class AsyncTimer implements IAdvice {
@Inject()
private readonly logger: EggLogger;
private start: number;
private succeed = true;
async beforeCall() {
this.start = performance.now();
async beforeCall(ctx: AdviceContext) {
ctx.set(START, performance.now());
ctx.set(SUCCEED, true);
}
async afterThrow() {
this.succeed = false;
async afterThrow(ctx: AdviceContext) {
ctx.set(SUCCEED, false);
}
async afterFinally(ctx: AdviceContext) {
const ms = Math.floor((performance.now() - this.start) * 1000) / 1000;
this.logger.info('[%s] [%s:%s|%s]',
ms, ctx.that.constructor.name, ctx.method, this.succeed ? 'T' : 'F');
const ms = Math.floor((performance.now() - ctx.get(START)!) * 1000) / 1000;
this.logger.info(
'[%s] [%s:%s|%s]',
ms,
ctx.that.constructor.name,
ctx.method,
ctx.get(SUCCEED)! ? 'T' : 'F'
);
}
}

View File

@@ -1,12 +1,21 @@
export const BUG_VERSIONS = 'bug-versions';
export const LATEST_TAG = 'latest';
export const GLOBAL_WORKER = 'GLOBAL_WORKER';
export const PROXY_CACHE_DIR_NAME = 'proxy-cache-packages';
export const ABBREVIATED_META_TYPE = 'application/vnd.npm.install-v1+json';
export const NOT_IMPLEMENTED_PATH = [ '/-/npm/v1/security/audits/quick', '/-/npm/v1/security/advisories/bulk' ];
export enum SyncMode {
none = 'none',
admin = 'admin',
proxy = 'proxy',
exist = 'exist',
all = 'all',
}
export enum ChangesStreamMode {
json = 'json',
streaming = 'streaming',
}
export enum SyncDeleteMode {
ignore = 'ignore',
block = 'block',

View File

@@ -1,5 +1,5 @@
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import customParseFormat from 'dayjs/plugin/customParseFormat.js';
dayjs.extend(customParseFormat);
export default dayjs;

View File

@@ -10,6 +10,8 @@ export enum BinaryType {
Nwjs = 'nwjs',
Playwright = 'playwright',
Puppeteer = 'puppeteer',
Prisma = 'prisma',
Sqlcipher = 'sqlcipher',
ChromeForTesting = 'chromeForTesting',
Edgedriver = 'edgedriver',
}

View File

@@ -2,6 +2,7 @@ export enum TaskType {
SyncPackage = 'sync_package',
ChangesStream = 'changes_stream',
SyncBinary = 'sync_binary',
UpdateProxyCache = 'update_proxy_cache',
CreateHook = 'create_hook',
TriggerHook = 'trigger_hook',
}

View File

@@ -2,5 +2,6 @@
"name": "cnpmcore-common",
"eggModule": {
"name": "cnpmcoreCommon"
}
},
"type": "module"
}

View File

@@ -1,6 +1,8 @@
import { Readable } from 'stream';
import { IncomingHttpHeaders } from 'http';
import { EggContext } from '@eggjs/tegg';
import type { Readable } from 'node:stream';
import type { IncomingHttpHeaders } from 'node:http';
import type { EggContext } from '@eggjs/tegg';
import type { estypes } from '@elastic/elasticsearch';
import type { CnpmcoreConfig } from '../port/config.js';
export interface UploadResult {
key: string;
@@ -17,8 +19,8 @@ export interface UploadOptions {
export interface AppendOptions {
key: string;
position?: string,
headers?: IncomingHttpHeaders,
position?: string;
headers?: IncomingHttpHeaders;
}
export interface DownloadOptions {
@@ -38,7 +40,11 @@ export interface NFSClient {
createDownloadStream(key: string): Promise<Readable | undefined>;
download(key: string, filepath: string, options: DownloadOptions): Promise<void>;
download(
key: string,
filepath: string,
options: DownloadOptions
): Promise<void>;
url?(key: string): string;
}
@@ -49,6 +55,12 @@ export interface QueueAdapter {
length(key: string): Promise<number>;
}
export interface SearchAdapter {
search<T>(query: any): Promise<estypes.SearchHitsMetadata<T>>;
upsert<T>(id: string, document: T): Promise<string>;
delete(id: string): Promise<string>;
}
export interface AuthUrlResult {
loginUrl: string;
doneUrl: string;
@@ -62,3 +74,12 @@ export interface AuthClient {
getAuthUrl(ctx: EggContext): Promise<AuthUrlResult>;
ensureCurrentUser(): Promise<userResult | null>;
}
declare module 'egg' {
// eslint-disable-next-line
// @ts-ignore
// avoid TS2310 Type 'EggAppConfig' recursively references itself as a base type.
interface EggAppConfig {
cnpmcore: CnpmcoreConfig;
}
}

View File

@@ -1,5 +1,7 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
interface BinaryData extends EntityData {
binaryId: string;

View File

@@ -8,7 +8,7 @@ export type BugVersionPackages = Record<string, BugVersionPackage>;
export class BugVersion {
private readonly data: BugVersionPackages;
constructor(data) {
constructor(data: BugVersionPackages) {
this.data = data;
}

View File

@@ -1,5 +1,7 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
interface ChangeData extends EntityData {
changeId: string;

View File

@@ -1,5 +1,7 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
interface DistData extends EntityData {
distId: string;

View File

@@ -1,9 +1,14 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import { HookType } from '../../common/enum/Hook';
import crypto from 'crypto';
import crypto from 'node:crypto';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
import type { HookType } from '../../common/enum/Hook.js';
export type CreateHookData = Omit<EasyData<HookData, 'hookId'>, 'enable' | 'latestTaskId'>;
export type CreateHookData = Omit<
EasyData<HookData, 'hookId'>,
'enable' | 'latestTaskId'
>;
export interface HookData extends EntityData {
hookId: string;
@@ -48,9 +53,10 @@ export class Hook extends Entity {
}
// payload 可能会特别大,如果做多次 stringify 浪费太多 cpu
signPayload(payload: object): { digest, payloadStr } {
signPayload(payload: object) {
const payloadStr = JSON.stringify(payload);
const digest = crypto.createHmac('sha256', this.secret)
const digest = crypto
.createHmac('sha256', this.secret)
.update(JSON.stringify(payload))
.digest('hex');
return {

View File

@@ -1,4 +1,4 @@
import { HookEventType } from '../../common/enum/Hook';
import { HookEventType } from '../../common/enum/Hook.js';
export interface PublishChangePayload {
'dist-tag'?: string;

View File

@@ -1,7 +1,9 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import { Dist } from './Dist';
import { getFullname } from '../../common/PackageUtil';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
import { Dist } from './Dist.js';
import { getFullname } from '../../common/PackageUtil.js';
interface PackageData extends EntityData {
scope: string;
@@ -22,6 +24,13 @@ export enum DIST_NAMES {
ABBREVIATED_MANIFESTS = 'abbreviated_manifests.json',
}
export function isPkgManifest(fileType: DIST_NAMES) {
return (
fileType === DIST_NAMES.FULL_MANIFESTS ||
fileType === DIST_NAMES.ABBREVIATED_MANIFESTS
);
}
interface FileInfo {
size: number;
shasum: string;

View File

@@ -1,5 +1,7 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
interface PackageTagData extends EntityData {
packageId: string;

View File

@@ -1,6 +1,9 @@
import { Dist } from './Dist';
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { Dist } from './Dist.js';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
import { PaddingSemVer } from './PaddingSemVer.js';
interface PackageVersionData extends EntityData {
packageId: string;
@@ -11,6 +14,8 @@ interface PackageVersionData extends EntityData {
tarDist: Dist;
readmeDist: Dist;
publishTime: Date;
paddingVersion?: string | null;
isPreRelease?: boolean | null;
}
export class PackageVersion extends Entity {
@@ -22,6 +27,8 @@ export class PackageVersion extends Entity {
tarDist: Dist;
readmeDist: Dist;
publishTime: Date;
paddingVersion: string;
isPreRelease: boolean;
constructor(data: PackageVersionData) {
super(data);
@@ -33,9 +40,19 @@ export class PackageVersion extends Entity {
this.tarDist = data.tarDist;
this.readmeDist = data.readmeDist;
this.publishTime = data.publishTime;
if (data.paddingVersion && typeof data.isPreRelease === 'boolean') {
this.paddingVersion = data.paddingVersion;
this.isPreRelease = data.isPreRelease;
} else {
const paddingSemVer = new PaddingSemVer(this.version);
this.paddingVersion = paddingSemVer.paddingVersion;
this.isPreRelease = paddingSemVer.isPreRelease;
}
}
static create(data: EasyData<PackageVersionData, 'packageVersionId'>): PackageVersion {
static create(
data: EasyData<PackageVersionData, 'packageVersionId'>
): PackageVersion {
const newData = EntityUtil.defaultData(data, 'packageVersionId');
return new PackageVersion(newData);
}

View File

@@ -1,5 +1,7 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
interface PackageVersionBlockData extends EntityData {
packageVersionBlockId: string;
@@ -22,7 +24,9 @@ export class PackageVersionBlock extends Entity {
this.reason = data.reason;
}
static create(data: EasyData<PackageVersionBlockData, 'packageVersionBlockId'>): PackageVersionBlock {
static create(
data: EasyData<PackageVersionBlockData, 'packageVersionBlockId'>
): PackageVersionBlock {
const newData = EntityUtil.defaultData(data, 'packageVersionBlockId');
return new PackageVersionBlock(newData);
}

View File

@@ -1,6 +1,8 @@
import { Dist } from './Dist';
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { Dist } from './Dist.js';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
interface PackageVersionFileData extends EntityData {
packageVersionFileId: string;
@@ -33,10 +35,14 @@ export class PackageVersionFile extends Entity {
}
get path() {
return this.directory === '/' ? `/${this.name}` : `${this.directory}/${this.name}`;
return this.directory === '/'
? `/${this.name}`
: `${this.directory}/${this.name}`;
}
static create(data: EasyData<PackageVersionFileData, 'packageVersionFileId'>): PackageVersionFile {
static create(
data: EasyData<PackageVersionFileData, 'packageVersionFileId'>
): PackageVersionFile {
const newData = EntityUtil.defaultData(data, 'packageVersionFileId');
return new PackageVersionFile(newData);
}

View File

@@ -1,5 +1,7 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
interface PackageVersionManifestData extends EntityData {
packageId: string;
@@ -22,7 +24,9 @@ export class PackageVersionManifest extends Entity {
this.manifest = data.manifest;
}
static create(data: EasyData<PackageVersionManifestData, 'packageVersionManifestId'>): PackageVersionManifest {
static create(
data: EasyData<PackageVersionManifestData, 'packageVersionManifestId'>
): PackageVersionManifest {
const newData = EntityUtil.defaultData(data, 'packageVersionManifestId');
return new PackageVersionManifest(newData);
}

View File

@@ -0,0 +1,50 @@
import { SemVer, valid } from 'semver';
export class PaddingSemVer {
private readonly semver: SemVer;
// 跳过 semver 中的 buildInfo, buildInfo 不参与版本比较
private _paddingVersion: string;
readonly isPreRelease: boolean;
constructor(semver: string | SemVer) {
// ignore invalid version, e.g.: '1000000000000000000.0.0' on https://registry.npmjs.com/latentflip-test
if (!valid(semver)) {
this.isPreRelease = true;
this._paddingVersion = PaddingSemVer.anyVersion();
return;
}
this.semver = new SemVer(semver);
if ((this.semver as any).includePrerelease) {
this.isPreRelease = true;
} else if (this.semver.prerelease && this.semver.prerelease.length) {
this.isPreRelease = true;
} else {
this.isPreRelease = false;
}
}
get paddingVersion(): string {
if (!this._paddingVersion) {
this._paddingVersion = PaddingSemVer.paddingVersion(this.semver.major)
+ PaddingSemVer.paddingVersion(this.semver.minor)
+ PaddingSemVer.paddingVersion(this.semver.patch);
}
return this._paddingVersion;
}
// 版本信息中为纯数字, JS 中支持的最大整型为 16 位
// 因此填充成 16 位对齐,如果版本号超过 16 位,则抛出异常
static paddingVersion(v: number) {
const t = String(v);
if (t.length <= 16) {
const padding = Array.from({ length: 16 - t.length }).fill(0)
.join('');
return padding + t;
}
throw new Error(`v ${v} too long`);
}
static anyVersion() {
return '000000000000000000000000000000000000000000000000';
}
}

View File

@@ -0,0 +1,45 @@
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import type { DIST_NAMES } from './Package.js';
import { isPkgManifest } from './Package.js';
import { PROXY_CACHE_DIR_NAME } from '../../common/constants.js';
interface ProxyCacheData extends EntityData {
fullname: string;
fileType: DIST_NAMES;
version?: string;
}
export type CreateProxyCacheData = Omit<
EasyData<ProxyCacheData, 'id'>,
'id' | 'filePath'
>;
export class ProxyCache extends Entity {
readonly fullname: string;
readonly fileType: DIST_NAMES;
readonly filePath: string;
readonly version?: string;
constructor(data: ProxyCacheData) {
super(data);
this.fullname = data.fullname;
this.fileType = data.fileType;
this.version = data.version;
if (isPkgManifest(data.fileType)) {
this.filePath = `/${PROXY_CACHE_DIR_NAME}/${data.fullname}/${data.fileType}`;
} else {
this.filePath = `/${PROXY_CACHE_DIR_NAME}/${data.fullname}/${data.version}/${data.fileType}`;
}
}
public static create(data: CreateProxyCacheData): ProxyCache {
const newData = { ...data, createdAt: new Date(), updatedAt: new Date() };
return new ProxyCache(newData);
}
public static update(data: ProxyCache): ProxyCache {
data.updatedAt = new Date();
return data;
}
}

View File

@@ -1,6 +1,8 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { RegistryType } from '../../common/enum/Registry';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
import type { RegistryType } from '../../common/enum/Registry.js';
interface RegistryData extends EntityData {
name: string;
@@ -9,9 +11,13 @@ interface RegistryData extends EntityData {
changeStream: string;
userPrefix: string;
type: RegistryType;
authToken?: string;
}
export type CreateRegistryData = Omit<EasyData<RegistryData, 'registryId'>, 'id'>;
export type CreateRegistryData = Omit<
EasyData<RegistryData, 'registryId'>,
'id'
>;
export class Registry extends Entity {
name: string;
@@ -20,6 +26,7 @@ export class Registry extends Entity {
changeStream: string;
userPrefix: string;
type: RegistryType;
authToken?: string;
constructor(data: RegistryData) {
super(data);
@@ -29,10 +36,14 @@ export class Registry extends Entity {
this.changeStream = data.changeStream;
this.userPrefix = data.userPrefix;
this.type = data.type;
this.authToken = data.authToken;
}
public static create(data: CreateRegistryData): Registry {
const newData = EntityUtil.defaultData(data, 'registryId');
const newData = EntityUtil.defaultData<RegistryData, 'registryId'>(
data,
'registryId'
);
return new Registry(newData);
}
}

View File

@@ -1,5 +1,7 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
interface ScopeData extends EntityData {
name: string;

View File

@@ -0,0 +1,81 @@
import { Range, Comparator } from 'semver';
import { PaddingSemVer } from './PaddingSemVer.js';
const OPERATOR_MAP = {
'<': '$lt',
'<=': '$lte',
'>': '$gt',
'>=': '$gte',
'': '$eq',
};
export class SqlRange {
private readonly range: Range;
private _containPreRelease: boolean;
readonly condition: object;
constructor(range: string | Range) {
this.range = new Range(range);
this._containPreRelease = false;
this.condition = this.generateWhere();
}
private comparatorToSql(comparator: Comparator) {
if (comparator.semver === (Comparator as any).ANY) {
return {
$and: [
{
isPreRelease: {
$lte: 0,
},
},
{
paddingVersion: {
$gte: PaddingSemVer.anyVersion(),
},
},
],
};
}
const paddingSemver = new PaddingSemVer(comparator.semver);
const operator = OPERATOR_MAP[comparator.operator as keyof typeof OPERATOR_MAP];
if (!operator) {
throw new Error(`unknown operator ${comparator.operator}`);
}
this._containPreRelease = this._containPreRelease || paddingSemver.isPreRelease;
return {
$and: [
{
isPreRelease: {
$lte: paddingSemver.isPreRelease ? 1 : 0,
},
},
{
paddingVersion: {
[operator]: paddingSemver.paddingVersion,
},
},
],
};
}
private comparatorSetToSql(comparatorSet: Array<Comparator>) {
const condition: Array<object> = [];
for (const comparator of comparatorSet) {
condition.push(this.comparatorToSql(comparator));
}
return { $and: condition };
}
private generateWhere() {
const conditions: Array<object> = [];
for (const rangeSet of this.range.set) {
conditions.push(this.comparatorSetToSql(rangeSet as Comparator[]));
}
return { $or: conditions };
}
get containPreRelease(): boolean {
return this._containPreRelease;
}
}

View File

@@ -1,10 +1,16 @@
import os from 'os';
import path from 'path';
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import { TaskType, TaskState } from '../../common/enum/Task';
import dayjs from '../../common/dayjs';
import { HookEvent } from './HookEvent';
import os from 'node:os';
import path from 'node:path';
import { InternalServerError } from 'egg-errors';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
import { TaskType, TaskState } from '../../common/enum/Task.js';
import { PROXY_CACHE_DIR_NAME } from '../../common/constants.js';
import dayjs from '../../common/dayjs.js';
import type { HookEvent } from './HookEvent.js';
import type { DIST_NAMES } from './Package.js';
import { isPkgManifest } from './Package.js';
export const HOST_NAME = os.hostname();
export const PID = process.pid;
@@ -31,13 +37,19 @@ export interface TaskData<T = TaskBaseData> extends EntityData {
export type SyncPackageTaskOptions = {
authorId?: string;
authorIp?: string;
remoteAuthToken?: string;
tips?: string;
skipDependencies?: boolean;
syncDownloadData?: boolean;
// force sync history version
forceSyncHistory?: boolean;
registryId?: string;
specificVersions?: Array<string>;
};
export type UpdateProxyCacheTaskOptions = {
fullname: string;
version?: string;
fileType: DIST_NAMES;
};
export interface CreateHookTaskData extends TaskBaseData {
@@ -51,19 +63,26 @@ export interface TriggerHookTaskData extends TaskBaseData {
}
export interface CreateSyncPackageTaskData extends TaskBaseData {
remoteAuthToken?: string;
tips?: string;
skipDependencies?: boolean;
syncDownloadData?: boolean;
forceSyncHistory?: boolean;
specificVersions?: Array<string>;
}
export interface CreateUpdateProxyCacheTaskData extends TaskBaseData {
fullname: string;
version?: string;
fileType: DIST_NAMES;
filePath: string;
}
export interface ChangesStreamTaskData extends TaskBaseData {
since: string;
last_package?: string,
last_package_created?: Date,
task_count?: number,
registryId?: string,
last_package?: string;
last_package_created?: Date;
task_count?: number;
registryId?: string;
}
export interface TaskUpdateCondition {
@@ -75,6 +94,7 @@ export type CreateHookTask = Task<CreateHookTaskData>;
export type TriggerHookTask = Task<TriggerHookTaskData>;
export type CreateSyncPackageTask = Task<CreateSyncPackageTaskData>;
export type ChangesStreamTask = Task<ChangesStreamTaskData>;
export type CreateUpdateProxyCacheTask = Task<CreateUpdateProxyCacheTaskData>;
export class Task<T extends TaskBaseData = TaskBaseData> extends Entity {
taskId: string;
@@ -116,12 +136,17 @@ export class Task<T extends TaskBaseData = TaskBaseData> extends Entity {
this.data.taskWorker = `${HOST_NAME}:${PID}`;
}
private static create<T extends TaskBaseData>(data: EasyData<TaskData<T>, 'taskId'>): Task<T> {
private static create<T extends TaskBaseData>(
data: EasyData<TaskData<T>, 'taskId'>
): Task<T> {
const newData = EntityUtil.defaultData(data, 'taskId');
return new Task(newData);
}
public static createSyncPackage(fullname: string, options?: SyncPackageTaskOptions): CreateSyncPackageTask {
public static createSyncPackage(
fullname: string,
options?: SyncPackageTaskOptions
): CreateSyncPackageTask {
const data = {
type: TaskType.SyncPackage,
state: TaskState.Waiting,
@@ -131,12 +156,12 @@ export class Task<T extends TaskBaseData = TaskBaseData> extends Entity {
data: {
// task execute worker
taskWorker: '',
remoteAuthToken: options?.remoteAuthToken,
tips: options?.tips,
registryId: options?.registryId ?? '',
skipDependencies: options?.skipDependencies,
syncDownloadData: options?.syncDownloadData,
forceSyncHistory: options?.forceSyncHistory,
specificVersions: options?.specificVersions,
},
};
const task = this.create(data);
@@ -144,7 +169,11 @@ export class Task<T extends TaskBaseData = TaskBaseData> extends Entity {
return task;
}
public static createChangesStream(targetName: string, registryId = '', since = ''): ChangesStreamTask {
public static createChangesStream(
targetName: string,
registryId = '',
since = ''
): ChangesStreamTask {
const data = {
type: TaskType.ChangesStream,
state: TaskState.Waiting,
@@ -192,7 +221,10 @@ export class Task<T extends TaskBaseData = TaskBaseData> extends Entity {
return task;
}
public static createTriggerHookTask(hookEvent: HookEvent, hookId: string): TriggerHookTask {
public static createTriggerHookTask(
hookEvent: HookEvent,
hookId: string
): TriggerHookTask {
const data = {
type: TaskType.TriggerHook,
state: TaskState.Waiting,
@@ -231,6 +263,39 @@ export class Task<T extends TaskBaseData = TaskBaseData> extends Entity {
return task;
}
public static needMergeWhenWaiting(type: TaskType) {
return [TaskType.SyncBinary, TaskType.SyncPackage].includes(type);
}
public static createUpdateProxyCache(
targetName: string,
options: UpdateProxyCacheTaskOptions
): CreateUpdateProxyCacheTask {
if (!isPkgManifest(options.fileType)) {
throw new InternalServerError(
'should not update package version manifest.'
);
}
const filePath = `/${PROXY_CACHE_DIR_NAME}/${options.fullname}/${options.fileType}`;
const data = {
type: TaskType.UpdateProxyCache,
state: TaskState.Waiting,
targetName,
authorId: `pid_${PID}`,
authorIp: HOST_NAME,
data: {
taskWorker: '',
fullname: options.fullname,
version: options?.version,
fileType: options.fileType,
filePath,
},
};
const task = this.create(data);
task.logPath = `/${PROXY_CACHE_DIR_NAME}/${options.fullname}/update-manifest-log/${options.fileType.split('.json')[0]}-${dayjs().format('YYYY/MM/DDHHmm')}-${task.taskId}.log`;
return task;
}
start(): TaskUpdateCondition {
const condition = {
taskId: this.taskId,

View File

@@ -1,6 +1,8 @@
import dayjs from 'dayjs';
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
export enum TokenType {
granular = 'granular',
@@ -13,10 +15,11 @@ interface BaseTokenData extends EntityData {
cidrWhitelist?: string[];
userId: string;
isReadonly?: boolean;
type?: TokenType;
type?: TokenType | string;
lastUsedAt?: Date;
}
interface ClassicTokenData extends BaseTokenData{
interface ClassicTokenData extends BaseTokenData {
isAutomation?: boolean;
}
interface GranularTokenData extends BaseTokenData {
@@ -30,7 +33,9 @@ interface GranularTokenData extends BaseTokenData {
type TokenData = ClassicTokenData | GranularTokenData;
export function isGranularToken(data: TokenData): data is GranularTokenData {
export function isGranularToken(
data: TokenData | Token
): data is GranularTokenData {
return data.type === TokenType.granular;
}
@@ -48,6 +53,7 @@ export class Token extends Entity {
readonly allowedScopes?: string[];
readonly expiredAt?: Date;
readonly expires?: number;
lastUsedAt: Date | null;
allowedPackages?: string[];
token?: string;
@@ -59,7 +65,8 @@ export class Token extends Entity {
this.tokenKey = data.tokenKey;
this.cidrWhitelist = data.cidrWhitelist || [];
this.isReadonly = data.isReadonly || false;
this.type = data.type || TokenType.classic;
this.type = (data.type as TokenType) || TokenType.classic;
this.lastUsedAt = data.lastUsedAt || null;
if (isGranularToken(data)) {
this.name = data.name;
@@ -67,6 +74,7 @@ export class Token extends Entity {
this.allowedScopes = data.allowedScopes;
this.expiredAt = data.expiredAt;
this.allowedPackages = data.allowedPackages;
this.isAutomation = false;
} else {
this.isAutomation = data.isAutomation || false;
}
@@ -75,9 +83,10 @@ export class Token extends Entity {
static create(data: EasyData<TokenData, 'tokenId'>): Token {
const newData = EntityUtil.defaultData(data, 'tokenId');
if (isGranularToken(newData) && !newData.expiredAt) {
newData.expiredAt = dayjs(newData.createdAt).add(newData.expires, 'days').toDate();
newData.expiredAt = dayjs(newData.createdAt)
.add(newData.expires, 'days')
.toDate();
}
return new Token(newData);
}
}

View File

@@ -1,6 +1,8 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import { cleanUserPrefix } from '../../common/PackageUtil';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
import { cleanUserPrefix } from '../../common/PackageUtil.js';
interface UserData extends EntityData {
userId: string;

View File

@@ -1,5 +1,7 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { EntityData } from './Entity.js';
import { Entity } from './Entity.js';
import type { EasyData } from '../util/EntityUtil.js';
import { EntityUtil } from '../util/EntityUtil.js';
interface WebauthnCredentialData extends EntityData {
wancId: string;
@@ -25,7 +27,9 @@ export class WebauthnCredential extends Entity {
this.browserType = data.browserType;
}
static create(data: EasyData<WebauthnCredentialData, 'wancId'>): WebauthnCredential {
static create(
data: EasyData<WebauthnCredentialData, 'wancId'>
): WebauthnCredential {
const newData = EntityUtil.defaultData(data, 'wancId');
return new WebauthnCredential(newData);
}

View File

@@ -1,16 +1,13 @@
import { Event, Inject } from '@eggjs/tegg';
import { EggLogger } from 'egg';
import { PACKAGE_VERSION_ADDED } from './index';
import { BUG_VERSIONS } from '../../common/constants';
import { PackageManagerService } from '../service/PackageManagerService';
import { BugVersionService } from '../service/BugVersionService';
import type { EggLogger } from 'egg';
import { PACKAGE_VERSION_ADDED } from './index.js';
import { BUG_VERSIONS } from '../../common/constants.js';
import type { BugVersionService } from '../service/BugVersionService.js';
@Event(PACKAGE_VERSION_ADDED)
export class BugVersionFixHandler {
@Inject()
private readonly bugVersionService: BugVersionService;
@Inject()
private readonly packageManagerService: PackageManagerService;
@Inject()
private readonly logger: EggLogger;
@@ -18,7 +15,7 @@ export class BugVersionFixHandler {
async handle(fullname: string) {
if (fullname !== BUG_VERSIONS) return;
try {
const bugVersion = await this.packageManagerService.getBugVersion();
const bugVersion = await this.bugVersionService.getBugVersion();
if (!bugVersion) return;
await this.bugVersionService.cleanBugVersionPackageCaches(bugVersion);
} catch (e) {

View File

@@ -11,8 +11,8 @@ import {
PACKAGE_MAINTAINER_CHANGED,
PACKAGE_MAINTAINER_REMOVED,
PACKAGE_META_CHANGED,
} from './index';
import { CacheService } from '../../core/service/CacheService';
} from './index.js';
import type { CacheService } from '../../core/service/CacheService.js';
class CacheCleanerEvent {
@Inject()
@@ -24,77 +24,77 @@ class CacheCleanerEvent {
}
@Event(PACKAGE_UNPUBLISHED)
export class PackageUnpublished extends CacheCleanerEvent {
export class PackageUnpublishedCacheCleanEvent extends CacheCleanerEvent {
async handle(fullname: string) {
await this.removeCache(fullname);
}
}
@Event(PACKAGE_BLOCKED)
export class PackageBlocked extends CacheCleanerEvent {
export class PackageBlockedCacheCleanEvent extends CacheCleanerEvent {
async handle(fullname: string) {
await this.removeCache(fullname);
}
}
@Event(PACKAGE_UNBLOCKED)
export class PackageUnblocked extends CacheCleanerEvent {
export class PackageUnblockedCacheCleanEvent extends CacheCleanerEvent {
async handle(fullname: string) {
await this.removeCache(fullname);
}
}
@Event(PACKAGE_VERSION_ADDED)
export class PackageVersionAdded extends CacheCleanerEvent {
export class PackageVersionAddedCacheCleanEvent extends CacheCleanerEvent {
async handle(fullname: string) {
await this.removeCache(fullname);
}
}
@Event(PACKAGE_VERSION_REMOVED)
export class PackageVersionRemoved extends CacheCleanerEvent {
export class PackageVersionRemovedCacheCleanEvent extends CacheCleanerEvent {
async handle(fullname: string) {
await this.removeCache(fullname);
}
}
@Event(PACKAGE_TAG_ADDED)
export class PackageTagAdded extends CacheCleanerEvent {
export class PackageTagAddedCacheCleanEvent extends CacheCleanerEvent {
async handle(fullname: string) {
await this.removeCache(fullname);
}
}
@Event(PACKAGE_TAG_CHANGED)
export class PackageTagChanged extends CacheCleanerEvent {
export class PackageTagChangedCacheCleanEvent extends CacheCleanerEvent {
async handle(fullname: string) {
await this.removeCache(fullname);
}
}
@Event(PACKAGE_TAG_REMOVED)
export class PackageTagRemoved extends CacheCleanerEvent {
export class PackageTagRemovedCacheCleanEvent extends CacheCleanerEvent {
async handle(fullname: string) {
await this.removeCache(fullname);
}
}
@Event(PACKAGE_MAINTAINER_CHANGED)
export class PackageMaintainerChanged extends CacheCleanerEvent {
export class PackageMaintainerChangedCacheCleanEvent extends CacheCleanerEvent {
async handle(fullname: string) {
await this.removeCache(fullname);
}
}
@Event(PACKAGE_MAINTAINER_REMOVED)
export class PackageMaintainerRemoved extends CacheCleanerEvent {
export class PackageMaintainerRemovedCacheCleanEvent extends CacheCleanerEvent {
async handle(fullname: string) {
await this.removeCache(fullname);
}
}
@Event(PACKAGE_META_CHANGED)
export class PackageMetaChanged extends CacheCleanerEvent {
export class PackageMetaChangedCacheCleanEvent extends CacheCleanerEvent {
async handle(fullname: string) {
await this.removeCache(fullname);
}

View File

@@ -1,5 +1,6 @@
import { EggAppConfig } from 'egg';
import type { EggAppConfig } from 'egg';
import { Event, Inject } from '@eggjs/tegg';
import type { PackageMetaChange } from './index.js';
import {
PACKAGE_UNPUBLISHED,
PACKAGE_VERSION_ADDED,
@@ -9,14 +10,14 @@ import {
PACKAGE_TAG_REMOVED,
PACKAGE_MAINTAINER_CHANGED,
PACKAGE_MAINTAINER_REMOVED,
PACKAGE_META_CHANGED, PackageMetaChange,
} from './index';
import { ChangeRepository } from '../../repository/ChangeRepository';
import { Change } from '../entity/Change';
import { HookEvent } from '../entity/HookEvent';
import { Task } from '../entity/Task';
import { User } from '../entity/User';
import { TaskService } from '../service/TaskService';
PACKAGE_META_CHANGED,
} from './index.js';
import type { ChangeRepository } from '../../repository/ChangeRepository.js';
import { Change } from '../entity/Change.js';
import { HookEvent } from '../entity/HookEvent.js';
import { Task } from '../entity/Task.js';
import type { User } from '../entity/User.js';
import type { TaskService } from '../service/TaskService.js';
class ChangesStreamEvent {
@Inject()
@@ -29,10 +30,14 @@ class ChangesStreamEvent {
protected readonly config: EggAppConfig;
protected get hookEnable() {
return this.config.hookEnable;
return this.config.cnpmcore.hookEnable;
}
protected async addChange(type: string, fullname: string, data: object): Promise<Change> {
protected async addChange(
type: string,
fullname: string,
data: object
): Promise<Change> {
const change = Change.create({
type,
targetName: fullname,
@@ -44,79 +49,101 @@ class ChangesStreamEvent {
}
@Event(PACKAGE_UNPUBLISHED)
export class PackageUnpublished extends ChangesStreamEvent {
export class PackageUnpublishedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string) {
const change = await this.addChange(PACKAGE_UNPUBLISHED, fullname, {});
if (this.hookEnable) {
const task = Task.createCreateHookTask(HookEvent.createUnpublishEvent(fullname, change.changeId));
const task = Task.createCreateHookTask(
HookEvent.createUnpublishEvent(fullname, change.changeId)
);
await this.taskService.createTask(task, true);
}
}
}
@Event(PACKAGE_VERSION_ADDED)
export class PackageVersionAdded extends ChangesStreamEvent {
export class PackageVersionAddedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, version: string, tag?: string) {
const change = await this.addChange(PACKAGE_VERSION_ADDED, fullname, { version });
const change = await this.addChange(PACKAGE_VERSION_ADDED, fullname, {
version,
});
if (this.hookEnable) {
const task = Task.createCreateHookTask(HookEvent.createPublishEvent(fullname, change.changeId, version, tag));
const task = Task.createCreateHookTask(
HookEvent.createPublishEvent(fullname, change.changeId, version, tag)
);
await this.taskService.createTask(task, true);
}
}
}
@Event(PACKAGE_VERSION_REMOVED)
export class PackageVersionRemoved extends ChangesStreamEvent {
export class PackageVersionRemovedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, version: string, tag?: string) {
const change = await this.addChange(PACKAGE_VERSION_REMOVED, fullname, { version });
const change = await this.addChange(PACKAGE_VERSION_REMOVED, fullname, {
version,
});
if (this.hookEnable) {
const task = Task.createCreateHookTask(HookEvent.createUnpublishEvent(fullname, change.changeId, version, tag));
const task = Task.createCreateHookTask(
HookEvent.createUnpublishEvent(fullname, change.changeId, version, tag)
);
await this.taskService.createTask(task, true);
}
}
}
@Event(PACKAGE_TAG_ADDED)
export class PackageTagAdded extends ChangesStreamEvent {
export class PackageTagAddedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, tag: string) {
const change = await this.addChange(PACKAGE_TAG_ADDED, fullname, { tag });
if (this.hookEnable) {
const task = Task.createCreateHookTask(HookEvent.createDistTagEvent(fullname, change.changeId, tag));
const task = Task.createCreateHookTask(
HookEvent.createDistTagEvent(fullname, change.changeId, tag)
);
await this.taskService.createTask(task, true);
}
}
}
@Event(PACKAGE_TAG_CHANGED)
export class PackageTagChanged extends ChangesStreamEvent {
export class PackageTagChangedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, tag: string) {
const change = await this.addChange(PACKAGE_TAG_CHANGED, fullname, { tag });
if (this.hookEnable) {
const task = Task.createCreateHookTask(HookEvent.createDistTagEvent(fullname, change.changeId, tag));
const task = Task.createCreateHookTask(
HookEvent.createDistTagEvent(fullname, change.changeId, tag)
);
await this.taskService.createTask(task, true);
}
}
}
@Event(PACKAGE_TAG_REMOVED)
export class PackageTagRemoved extends ChangesStreamEvent {
export class PackageTagRemovedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, tag: string) {
const change = await this.addChange(PACKAGE_TAG_REMOVED, fullname, { tag });
if (this.hookEnable) {
const task = Task.createCreateHookTask(HookEvent.createDistTagRmEvent(fullname, change.changeId, tag));
const task = Task.createCreateHookTask(
HookEvent.createDistTagRmEvent(fullname, change.changeId, tag)
);
await this.taskService.createTask(task, true);
}
}
}
@Event(PACKAGE_MAINTAINER_CHANGED)
export class PackageMaintainerChanged extends ChangesStreamEvent {
export class PackageMaintainerChangedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, maintainers: User[]) {
const change = await this.addChange(PACKAGE_MAINTAINER_CHANGED, fullname, {});
const change = await this.addChange(
PACKAGE_MAINTAINER_CHANGED,
fullname,
{}
);
// TODO 应该比较差值,而不是全量推送
if (this.hookEnable) {
for (const maintainer of maintainers) {
const task = Task.createCreateHookTask(HookEvent.createOwnerEvent(fullname, change.changeId, maintainer.name));
const task = Task.createCreateHookTask(
HookEvent.createOwnerEvent(fullname, change.changeId, maintainer.name)
);
await this.taskService.createTask(task, true);
}
}
@@ -124,24 +151,36 @@ export class PackageMaintainerChanged extends ChangesStreamEvent {
}
@Event(PACKAGE_MAINTAINER_REMOVED)
export class PackageMaintainerRemoved extends ChangesStreamEvent {
export class PackageMaintainerRemovedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, maintainer: string) {
const change = await this.addChange(PACKAGE_MAINTAINER_REMOVED, fullname, { maintainer });
const change = await this.addChange(PACKAGE_MAINTAINER_REMOVED, fullname, {
maintainer,
});
if (this.hookEnable) {
const task = Task.createCreateHookTask(HookEvent.createOwnerRmEvent(fullname, change.changeId, maintainer));
const task = Task.createCreateHookTask(
HookEvent.createOwnerRmEvent(fullname, change.changeId, maintainer)
);
await this.taskService.createTask(task, true);
}
}
}
@Event(PACKAGE_META_CHANGED)
export class PackageMetaChanged extends ChangesStreamEvent {
export class PackageMetaChangedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, meta: PackageMetaChange) {
const change = await this.addChange(PACKAGE_META_CHANGED, fullname, { ...meta });
const change = await this.addChange(PACKAGE_META_CHANGED, fullname, {
...meta,
});
const { deprecateds } = meta;
if (this.hookEnable) {
for (const deprecated of deprecateds || []) {
const task = Task.createCreateHookTask(HookEvent.createDeprecatedEvent(fullname, change.changeId, deprecated.version));
const task = Task.createCreateHookTask(
HookEvent.createDeprecatedEvent(
fullname,
change.changeId,
deprecated.version
)
);
await this.taskService.createTask(task, true);
}
}

View File

@@ -1,12 +1,10 @@
import { Event, Inject } from '@eggjs/tegg';
import {
EggAppConfig,
} from 'egg';
import { PACKAGE_VERSION_ADDED } from './index';
import { getScopeAndName } from '../../common/PackageUtil';
import { PackageVersionManifest as PackageVersionManifestEntity } from '../entity/PackageVersionManifest';
import { PackageRepository } from '../../repository/PackageRepository';
import { DistRepository } from '../../repository/DistRepository';
import type { EggAppConfig } from 'egg';
import { PACKAGE_VERSION_ADDED } from './index.js';
import { getScopeAndName } from '../../common/PackageUtil.js';
import { PackageVersionManifest as PackageVersionManifestEntity } from '../entity/PackageVersionManifest.js';
import type { PackageRepository } from '../../repository/PackageRepository.js';
import type { DistRepository } from '../../repository/DistRepository.js';
class StoreManifestEvent {
@Inject()
@@ -16,15 +14,25 @@ class StoreManifestEvent {
@Inject()
private readonly distRepository: DistRepository;
protected async savePackageVersionManifest(fullname: string, version: string) {
if (!this.config.cnpmcore.enableStoreFullPackageVersionManifestsToDatabase) return;
protected async savePackageVersionManifest(
fullname: string,
version: string
) {
if (!this.config.cnpmcore.enableStoreFullPackageVersionManifestsToDatabase)
return;
const [ scope, name ] = getScopeAndName(fullname);
const [scope, name] = getScopeAndName(fullname);
const packageId = await this.packageRepository.findPackageId(scope, name);
if (!packageId) return;
const packageVersion = await this.packageRepository.findPackageVersion(packageId, version);
const packageVersion = await this.packageRepository.findPackageVersion(
packageId,
version
);
if (!packageVersion) return;
const manifest = await this.distRepository.findPackageVersionManifest(packageId, version);
const manifest = await this.distRepository.findPackageVersionManifest(
packageId,
version
);
if (!manifest) return;
const entity = PackageVersionManifestEntity.create({
packageId,
@@ -36,7 +44,7 @@ class StoreManifestEvent {
}
@Event(PACKAGE_VERSION_ADDED)
export class PackageVersionAdded extends StoreManifestEvent {
export class PackageVersionAddedStoreManifestEvent extends StoreManifestEvent {
async handle(fullname: string, version: string) {
await this.savePackageVersionManifest(fullname, version);
}

View File

@@ -0,0 +1,55 @@
// TODO sync event
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { EggAppConfig } from 'egg';
import { Event, Inject } from '@eggjs/tegg';
import {
PACKAGE_UNPUBLISHED,
PACKAGE_VERSION_ADDED,
PACKAGE_VERSION_REMOVED,
PACKAGE_TAG_ADDED,
PACKAGE_TAG_CHANGED,
PACKAGE_TAG_REMOVED,
PACKAGE_MAINTAINER_CHANGED,
PACKAGE_MAINTAINER_REMOVED,
PACKAGE_META_CHANGED,
PACKAGE_BLOCKED,
PACKAGE_UNBLOCKED,
} from './index.js';
import type { PackageSearchService } from '../service/PackageSearchService.js';
class SyncESPackage {
@Inject()
protected readonly packageSearchService: PackageSearchService;
@Inject()
protected readonly config: EggAppConfig;
protected async syncPackage(fullname: string) {
if (!this.config.cnpmcore.enableElasticsearch) return;
await this.packageSearchService.syncPackage(fullname, true);
}
}
@Event(PACKAGE_UNPUBLISHED)
@Event(PACKAGE_BLOCKED)
export class PackageUnpublishedSyncESEvent extends SyncESPackage {
async handle(fullname: string) {
if (!this.config.cnpmcore.enableElasticsearch) return;
await this.packageSearchService.removePackage(fullname);
}
}
@Event(PACKAGE_VERSION_ADDED)
@Event(PACKAGE_META_CHANGED)
@Event(PACKAGE_VERSION_REMOVED)
@Event(PACKAGE_TAG_ADDED)
@Event(PACKAGE_TAG_CHANGED)
@Event(PACKAGE_TAG_REMOVED)
@Event(PACKAGE_MAINTAINER_CHANGED)
@Event(PACKAGE_MAINTAINER_REMOVED)
@Event(PACKAGE_UNBLOCKED)
export class PackageVersionAddedSyncESEvent extends SyncESPackage {
async handle(fullname: string) {
await this.syncPackage(fullname);
}
}

View File

@@ -1,35 +1,91 @@
import { Event, Inject } from '@eggjs/tegg';
import type { EggAppConfig, EggLogger } from 'egg';
import { ForbiddenError } from 'egg-errors';
import {
EggAppConfig,
} from 'egg';
import { PACKAGE_VERSION_ADDED } from './index';
import { getScopeAndName } from '../../common/PackageUtil';
import { PackageManagerService } from '../service/PackageManagerService';
import { PackageVersionFileService } from '../service/PackageVersionFileService';
PACKAGE_VERSION_ADDED,
PACKAGE_TAG_ADDED,
PACKAGE_TAG_CHANGED,
} from './index.js';
import { getScopeAndName } from '../../common/PackageUtil.js';
import type { PackageManagerService } from '../service/PackageManagerService.js';
import type { PackageVersionFileService } from '../service/PackageVersionFileService.js';
class SyncPackageVersionFileEvent {
@Inject()
protected readonly config: EggAppConfig;
@Inject()
protected readonly logger: EggLogger;
@Inject()
private readonly packageManagerService: PackageManagerService;
@Inject()
private readonly packageVersionFileService: PackageVersionFileService;
protected async syncPackageVersionFile(fullname: string, version: string) {
// must set enableUnpkg and enableSyncUnpkgFiles = true both
if (!this.config.cnpmcore.enableUnpkg) return;
if (!this.config.cnpmcore.enableSyncUnpkgFiles) return;
// ignore sync on unittest
if (this.config.env === 'unittest' && fullname !== '@cnpm/unittest-unpkg-demo') return;
const [ scope, name ] = getScopeAndName(fullname);
const { packageVersion } = await this.packageManagerService.showPackageVersionByVersionOrTag(
scope, name, version);
if (
this.config.env === 'unittest' &&
fullname !== '@cnpm/unittest-unpkg-demo'
)
return;
const [scope, name] = getScopeAndName(fullname);
const { packageVersion } =
await this.packageManagerService.showPackageVersionByVersionOrTag(
scope,
name,
version
);
if (!packageVersion) return;
await this.packageVersionFileService.syncPackageVersionFiles(packageVersion);
try {
await this.packageVersionFileService.syncPackageVersionFiles(
packageVersion
);
} catch (err) {
if (err instanceof ForbiddenError) {
this.logger.info(
'[SyncPackageVersionFileEvent.syncPackageVersionFile] ignore sync files, cause: %s',
err.message
);
return;
}
throw err;
}
}
protected async syncPackageReadmeToLatestVersion(fullname: string) {
const [scope, name] = getScopeAndName(fullname);
const { pkg, packageVersion } =
await this.packageManagerService.showPackageVersionByVersionOrTag(
scope,
name,
'latest'
);
if (!pkg || !packageVersion) return;
await this.packageVersionFileService.syncPackageReadme(pkg, packageVersion);
}
}
@Event(PACKAGE_VERSION_ADDED)
export class PackageVersionAdded extends SyncPackageVersionFileEvent {
export class PackageVersionAddedSyncPackageVersionFileEvent extends SyncPackageVersionFileEvent {
async handle(fullname: string, version: string) {
await this.syncPackageVersionFile(fullname, version);
}
}
@Event(PACKAGE_TAG_ADDED)
export class PackageTagAddedSyncPackageVersionFileEvent extends SyncPackageVersionFileEvent {
async handle(fullname: string, tag: string) {
if (tag !== 'latest') return;
await this.syncPackageReadmeToLatestVersion(fullname);
}
}
@Event(PACKAGE_TAG_CHANGED)
export class PackageTagChangedSyncPackageVersionFileEvent extends SyncPackageVersionFileEvent {
async handle(fullname: string, tag: string) {
if (tag !== 'latest') return;
await this.syncPackageReadmeToLatestVersion(fullname);
}
}

View File

@@ -1,5 +1,5 @@
import '@eggjs/tegg';
import { User } from '../entity/User';
import type { User } from '../entity/User.js';
export const PACKAGE_UNPUBLISHED = 'PACKAGE_UNPUBLISHED';
export const PACKAGE_BLOCKED = 'PACKAGE_BLOCKED';
@@ -22,19 +22,35 @@ export interface PackageMetaChange {
deprecateds?: Array<PackageDeprecated>;
}
declare module '@eggjs/tegg' {
interface Events {
[PACKAGE_UNPUBLISHED]: (fullname: string) => Promise<void>;
[PACKAGE_BLOCKED]: (fullname: string) => Promise<void>;
[PACKAGE_UNBLOCKED]: (fullname: string) => Promise<void>;
[PACKAGE_VERSION_ADDED]: (fullname: string, version: string, tag?: string) => Promise<void>;
[PACKAGE_VERSION_REMOVED]: (fullname: string, version: string, tag?: string) => Promise<void>;
[PACKAGE_VERSION_ADDED]: (
fullname: string,
version: string,
tag?: string
) => Promise<void>;
[PACKAGE_VERSION_REMOVED]: (
fullname: string,
version: string,
tag?: string
) => Promise<void>;
[PACKAGE_TAG_ADDED]: (fullname: string, tag: string) => Promise<void>;
[PACKAGE_TAG_CHANGED]: (fullname: string, tag: string) => Promise<void>;
[PACKAGE_TAG_REMOVED]: (fullname: string, tag: string) => Promise<void>;
[PACKAGE_MAINTAINER_CHANGED]: (fullname: string, maintainers: User[]) => Promise<void>;
[PACKAGE_MAINTAINER_REMOVED]: (fullname: string, maintainer: string) => Promise<void>;
[PACKAGE_META_CHANGED]: (fullname: string, meta: PackageMetaChange) => Promise<void>;
[PACKAGE_MAINTAINER_CHANGED]: (
fullname: string,
maintainers: User[]
) => Promise<void>;
[PACKAGE_MAINTAINER_REMOVED]: (
fullname: string,
maintainer: string
) => Promise<void>;
[PACKAGE_META_CHANGED]: (
fullname: string,
meta: PackageMetaChange
) => Promise<void>;
}
}

View File

@@ -2,5 +2,6 @@
"name": "cnpmcore-core",
"eggModule": {
"name": "cnpmcoreCore"
}
},
"type": "module"
}

View File

@@ -1,26 +1,22 @@
import { rm } from 'fs/promises';
import {
AccessLevel,
SingletonProto,
Inject,
EggObjectFactory,
} from '@eggjs/tegg';
import {
EggHttpClient,
} from 'egg';
import fs from 'fs/promises';
import binaries, { BinaryName, CategoryName } from '../../../config/binaries';
import { NFSAdapter } from '../../common/adapter/NFSAdapter';
import { TaskType, TaskState } from '../../common/enum/Task';
import { downloadToTempfile } from '../../common/FileUtil';
import { BinaryRepository } from '../../repository/BinaryRepository';
import { Task } from '../entity/Task';
import { Binary } from '../entity/Binary';
import { TaskService } from './TaskService';
import { AbstractBinary, BinaryItem } from '../../common/adapter/binary/AbstractBinary';
import { AbstractService } from '../../common/AbstractService';
import { TaskRepository } from '../../repository/TaskRepository';
import { BinaryType } from '../../common/enum/Binary';
import type { EggObjectFactory } from '@eggjs/tegg';
import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';
import type { EggHttpClient } from 'egg';
import fs from 'node:fs/promises';
import { sortBy } from 'lodash-es';
import type { BinaryName, CategoryName } from '../../../config/binaries.js';
import binaries from '../../../config/binaries.js';
import type { BinaryRepository } from '../../repository/BinaryRepository.js';
import { Task } from '../entity/Task.js';
import { Binary } from '../entity/Binary.js';
import type { TaskService } from './TaskService.js';
import type { NFSAdapter } from '../../common/adapter/NFSAdapter.js';
import { downloadToTempfile } from '../../common/FileUtil.js';
import { isTimeoutError } from '../../common/ErrorUtil.js';
import type { BinaryItem } from '../../common/adapter/binary/AbstractBinary.js';
import { AbstractBinary } from '../../common/adapter/binary/AbstractBinary.js';
import { AbstractService } from '../../common/AbstractService.js';
import { BinaryType } from '../../common/enum/Binary.js';
import { TaskType, TaskState } from '../../common/enum/Task.js';
function isoNow() {
return new Date().toISOString();
@@ -35,8 +31,6 @@ export class BinarySyncerService extends AbstractService {
@Inject()
private readonly taskService: TaskService;
@Inject()
private readonly taskRepository: TaskRepository;
@Inject()
private readonly httpclient: EggHttpClient;
@Inject()
private readonly nfsAdapter: NFSAdapter;
@@ -46,37 +40,37 @@ export class BinarySyncerService extends AbstractService {
// canvas/v2.6.1/canvas-v2.6.1-node-v57-linux-glibc-x64.tar.gz
// -> node-canvas-prebuilt/v2.6.1/node-canvas-prebuilt-v2.6.1-node-v57-linux-glibc-x64.tar.gz
// canvas 历史版本的 targetName 可能是 category 需要兼容
public async findBinary(targetName: BinaryName | CategoryName, parent: string, name: string) {
public async findBinary(
targetName: BinaryName | CategoryName,
parent: string,
name: string
) {
return await this.binaryRepository.findBinary(targetName, parent, name);
}
public async listDirBinaries(binary: Binary) {
return await this.binaryRepository.listBinaries(binary.category, `${binary.parent}${binary.name}`);
return await this.binaryRepository.listBinaries(
binary.category,
`${binary.parent}${binary.name}`
);
}
public async listRootBinaries(binaryName: BinaryName) {
// 通常 binaryName 和 category 是一样的,但是有些特殊的 binaryName 会有多个 category比如 canvas
// 所以查询 canvas 的时候,需要将 binaryName 和 category 的数据都查出来
const {
category,
} = binaries[binaryName];
const reqs = [
this.binaryRepository.listBinaries(binaryName, '/'),
];
const { category } = binaries[binaryName];
const reqs = [this.binaryRepository.listBinaries(binaryName, '/')];
if (category && category !== binaryName) {
reqs.push(this.binaryRepository.listBinaries(category, '/'));
}
const [
rootBinary,
categoryBinary,
] = await Promise.all(reqs);
const [rootBinary, categoryBinary] = await Promise.all(reqs);
const versions = rootBinary.map(b => b.name);
const versions = new Set(rootBinary.map(b => b.name));
categoryBinary?.forEach(b => {
const version = b.name;
// 只将没有的版本添加进去
if (!versions.includes(version)) {
if (!versions.has(version)) {
rootBinary.push(b);
}
});
@@ -88,17 +82,18 @@ export class BinarySyncerService extends AbstractService {
return await this.nfsAdapter.getDownloadUrlOrStream(binary.storePath);
}
// SyncBinary 由定时任务每台单机定时触发,手动去重
// 添加 bizId 在 db 防止重复,记录 id 错误
public async createTask(binaryName: BinaryName, lastData?: any) {
const existsTask = await this.taskRepository.findTaskByTargetName(binaryName, TaskType.SyncBinary);
if (existsTask) {
return existsTask;
}
try {
return await this.taskService.createTask(Task.createSyncBinary(binaryName, lastData), false);
return await this.taskService.createTask(
Task.createSyncBinary(binaryName, lastData),
false
);
} catch (e) {
this.logger.error('[BinarySyncerService.createTask] binaryName: %s, error: %s', binaryName, e);
this.logger.error(
'[BinarySyncerService.createTask] binaryName: %s, error: %s',
binaryName,
e
);
}
}
@@ -119,40 +114,86 @@ export class BinarySyncerService extends AbstractService {
const binaryAdapter = await this.getBinaryAdapter(binaryName);
const logUrl = `${this.config.cnpmcore.registry}/-/binary/${binaryName}/syncs/${task.taskId}/log`;
let logs: string[] = [];
logs.push(`[${isoNow()}] 🚧🚧🚧🚧🚧 Start sync binary "${binaryName}" 🚧🚧🚧🚧🚧`);
logs.push(
`[${isoNow()}] 🚧🚧🚧🚧🚧 Start sync binary "${binaryName}" 🚧🚧🚧🚧🚧`
);
if (!binaryAdapter) {
task.error = 'unknow binaryName';
logs.push(`[${isoNow()}] ❌ Synced "${binaryName}" fail, ${task.error}, log: ${logUrl}`);
logs.push(
`[${isoNow()}] ❌ Synced "${binaryName}" fail, ${task.error}, log: ${logUrl}`
);
logs.push(`[${isoNow()}] ❌❌❌❌❌ "${binaryName}" ❌❌❌❌❌`);
this.logger.error('[BinarySyncerService.executeTask:fail] taskId: %s, targetName: %s, %s',
task.taskId, task.targetName, task.error);
this.logger.error(
'[BinarySyncerService.executeTask:fail] taskId: %s, targetName: %s, %s',
task.taskId,
task.targetName,
task.error
);
await this.taskService.finishTask(task, TaskState.Fail, logs.join('\n'));
return;
}
await this.taskService.appendTaskLog(task, logs.join('\n'));
logs = [];
this.logger.info('[BinarySyncerService.executeTask:start] taskId: %s, targetName: %s, log: %s',
task.taskId, task.targetName, logUrl);
this.logger.info(
'[BinarySyncerService.executeTask:start] taskId: %s, targetName: %s, log: %s',
task.taskId,
task.targetName,
logUrl
);
try {
await this.syncDir(binaryAdapter, task, '/');
const [hasDownloadError] = await this.syncDir(binaryAdapter, task, '/');
logs.push(`[${isoNow()}] 🟢 log: ${logUrl}`);
logs.push(`[${isoNow()}] 🟢🟢🟢🟢🟢 "${binaryName}" 🟢🟢🟢🟢🟢`);
await this.taskService.finishTask(task, TaskState.Success, logs.join('\n'));
this.logger.info('[BinarySyncerService.executeTask:success] taskId: %s, targetName: %s, log: %s',
task.taskId, task.targetName, logUrl);
await this.taskService.finishTask(
task,
TaskState.Success,
logs.join('\n')
);
// 确保没有下载异常才算 success
await binaryAdapter.finishFetch(!hasDownloadError, binaryName);
this.logger.info(
'[BinarySyncerService.executeTask:success] taskId: %s, targetName: %s, log: %s, hasDownloadError: %s',
task.taskId,
task.targetName,
logUrl,
hasDownloadError
);
} catch (err: any) {
task.error = err.message;
logs.push(`[${isoNow()}] ❌ Synced "${binaryName}" fail, ${task.error}, log: ${logUrl}`);
task.error = `${err.name}: ${err.message}`;
logs.push(
`[${isoNow()}] ❌ Synced "${binaryName}" fail, ${task.error}, log: ${logUrl}`
);
logs.push(`[${isoNow()}] ❌❌❌❌❌ "${binaryName}" ❌❌❌❌❌`);
this.logger.error('[BinarySyncerService.executeTask:fail] taskId: %s, targetName: %s, %s',
task.taskId, task.targetName, task.error);
this.logger.error(err);
if (isTimeoutError(err)) {
this.logger.warn(
'[BinarySyncerService.executeTask:fail] taskId: %s, targetName: %s, %s',
task.taskId,
task.targetName,
task.error
);
this.logger.warn(err);
} else {
this.logger.error(
'[BinarySyncerService.executeTask:fail] taskId: %s, targetName: %s, %s',
task.taskId,
task.targetName,
task.error
);
this.logger.error(err);
}
await binaryAdapter.finishFetch(false, binaryName);
await this.taskService.finishTask(task, TaskState.Fail, logs.join('\n'));
}
}
private async syncDir(binaryAdapter: AbstractBinary, task: Task, dir: string, parentIndex = '') {
private async syncDir(
binaryAdapter: AbstractBinary,
task: Task,
dir: string,
parentIndex = '',
latestVersionParent = '/'
) {
const binaryName = task.targetName as BinaryName;
const result = await binaryAdapter.fetch(dir, binaryName);
let hasDownloadError = false;
@@ -160,14 +201,30 @@ export class BinarySyncerService extends AbstractService {
if (result && result.items.length > 0) {
hasItems = true;
let logs: string[] = [];
const newItems = await this.diff(binaryName, dir, result.items);
logs.push(`[${isoNow()}][${dir}] 🚧 Syncing diff: ${result.items.length} => ${newItems.length}, Binary class: ${binaryAdapter.constructor.name}`);
for (const [ index, { item, reason }] of newItems.entries()) {
const { newItems, latestVersionDir } = await this.diff(
binaryName,
dir,
result.items,
latestVersionParent
);
logs.push(
`[${isoNow()}][${dir}] 🚧 Syncing diff: ${result.items.length} => ${newItems.length}, Binary class: ${binaryAdapter.constructor.name}`
);
// re-check latest version
for (const [index, { item, reason }] of newItems.entries()) {
if (item.isDir) {
logs.push(`[${isoNow()}][${dir}] 🚧 [${parentIndex}${index}] Start sync dir ${JSON.stringify(item)}, reason: ${reason}`);
logs.push(
`[${isoNow()}][${dir}] 🚧 [${parentIndex}${index}] Start sync dir ${JSON.stringify(item)}, reason: ${reason}`
);
await this.taskService.appendTaskLog(task, logs.join('\n'));
logs = [];
const [ hasError, hasSubItems ] = await this.syncDir(binaryAdapter, task, `${dir}${item.name}`, `${parentIndex}${index}.`);
const [hasError, hasSubItems] = await this.syncDir(
binaryAdapter,
task,
`${dir}${item.name}`,
`${parentIndex}${index}.`,
latestVersionDir
);
if (hasError) {
hasDownloadError = true;
} else {
@@ -179,42 +236,71 @@ export class BinarySyncerService extends AbstractService {
}
} else {
// download to nfs
logs.push(`[${isoNow()}][${dir}] 🚧 [${parentIndex}${index}] Downloading ${JSON.stringify(item)}, reason: ${reason}`);
logs.push(
`[${isoNow()}][${dir}] 🚧 [${parentIndex}${index}] Downloading ${JSON.stringify(item)}, reason: ${reason}`
);
// skip exists binary file
const existsBinary = await this.binaryRepository.findBinary(item.category, item.parent, item.name);
const existsBinary = await this.binaryRepository.findBinary(
item.category,
item.parent,
item.name
);
if (existsBinary && existsBinary.date === item.date) {
logs.push(`[${isoNow()}][${dir}] 🟢 [${parentIndex}${index}] binary file exists, skip download, binaryId: ${existsBinary.binaryId}`);
this.logger.info('[BinarySyncerService.syncDir:skipDownload] binaryId: %s exists, storePath: %s',
existsBinary.binaryId, existsBinary.storePath);
logs.push(
`[${isoNow()}][${dir}] 🟢 [${parentIndex}${index}] binary file exists, skip download, binaryId: ${existsBinary.binaryId}`
);
this.logger.info(
'[BinarySyncerService.syncDir:skipDownload] binaryId: %s exists, storePath: %s',
existsBinary.binaryId,
existsBinary.storePath
);
continue;
}
await this.taskService.appendTaskLog(task, logs.join('\n'));
logs = [];
let localFile = '';
try {
const { tmpfile, headers, timing } =
await downloadToTempfile(
this.httpclient, this.config.dataDir, item.sourceUrl!, { ignoreDownloadStatuses: item.ignoreDownloadStatuses });
logs.push(`[${isoNow()}][${dir}] 🟢 [${parentIndex}${index}] HTTP content-length: ${headers['content-length']}, timing: ${JSON.stringify(timing)}, ${item.sourceUrl} => ${tmpfile}`);
const { tmpfile, headers, timing } = await downloadToTempfile(
this.httpclient,
this.config.dataDir,
item.sourceUrl!,
{ ignoreDownloadStatuses: item.ignoreDownloadStatuses }
);
const log = `[${isoNow()}][${dir}] 🟢 [${parentIndex}${index}] HTTP content-length: ${headers['content-length']}, timing: ${JSON.stringify(timing)}, ${item.sourceUrl} => ${tmpfile}`;
logs.push(log);
this.logger.info(
'[BinarySyncerService.syncDir:downloadToTempfile] %s',
log
);
localFile = tmpfile;
const binary = await this.saveBinaryItem(item, tmpfile);
logs.push(`[${isoNow()}][${dir}] 🟢 [${parentIndex}${index}] Synced file success, binaryId: ${binary.binaryId}`);
logs.push(
`[${isoNow()}][${dir}] 🟢 [${parentIndex}${index}] Synced file success, binaryId: ${binary.binaryId}`
);
await this.taskService.appendTaskLog(task, logs.join('\n'));
logs = [];
} catch (err: any) {
if (err.name === 'DownloadNotFoundError') {
this.logger.info('Not found %s, skip it', item.sourceUrl);
logs.push(`[${isoNow()}][${dir}] 🧪️ [${parentIndex}${index}] Download ${item.sourceUrl} not found, skip it`);
logs.push(
`[${isoNow()}][${dir}] 🧪️ [${parentIndex}${index}] Download ${item.sourceUrl} not found, skip it`
);
} else {
this.logger.error('Download binary %s %s', item.sourceUrl, err);
if (err.name === 'DownloadStatusInvalidError') {
this.logger.warn('Download binary %s %s', item.sourceUrl, err);
} else {
this.logger.error('Download binary %s %s', item.sourceUrl, err);
}
hasDownloadError = true;
logs.push(`[${isoNow()}][${dir}] ❌ [${parentIndex}${index}] Download ${item.sourceUrl} error: ${err}`);
logs.push(
`[${isoNow()}][${dir}] ❌ [${parentIndex}${index}] Download ${item.sourceUrl} error: ${err}`
);
}
await this.taskService.appendTaskLog(task, logs.join('\n'));
logs = [];
} finally {
if (localFile) {
await rm(localFile, { force: true });
await fs.rm(localFile, { force: true });
}
}
}
@@ -222,20 +308,35 @@ export class BinarySyncerService extends AbstractService {
if (hasDownloadError) {
logs.push(`[${isoNow()}][${dir}] ❌ Synced dir fail`);
} else {
logs.push(`[${isoNow()}][${dir}] 🟢 Synced dir success`);
logs.push(
`[${isoNow()}][${dir}] 🟢 Synced dir success, hasItems: ${hasItems}`
);
}
await this.taskService.appendTaskLog(task, logs.join('\n'));
}
return [ hasDownloadError, hasItems ];
return [hasDownloadError, hasItems];
}
private async diff(binaryName: BinaryName, dir: string, fetchItems: BinaryItem[]) {
const existsItems = await this.binaryRepository.listBinaries(binaryName, dir);
// see https://github.com/cnpm/cnpmcore/issues/556
// 上游可能正在发布新版本、同步流程中断,导致同步的时候,文件列表不一致
// 如果的当前目录命中 latestVersionParent 父目录,那么就再校验一下当前目录
// 如果 existsItems 为空或者经过修改,那么就不需要 revalidate 了
private async diff(
binaryName: BinaryName,
dir: string,
fetchItems: BinaryItem[],
latestVersionParent = '/'
) {
const existsItems = await this.binaryRepository.listBinaries(
binaryName,
dir
);
const existsMap = new Map<string, Binary>();
for (const item of existsItems) {
existsMap.set(item.name, item);
}
const diffItems: { item: Binary; reason: string }[] = [];
let latestItem: BinaryItem | undefined;
for (const item of fetchItems) {
const existsItem = existsMap.get(item.name);
if (!existsItem) {
@@ -260,9 +361,25 @@ export class BinarySyncerService extends AbstractService {
existsItem.sourceUrl = item.url;
existsItem.ignoreDownloadStatuses = item.ignoreDownloadStatuses;
existsItem.date = item.date;
} else if (dir.endsWith(latestVersionParent)) {
if (!latestItem) {
latestItem = sortBy(fetchItems, ['date']).pop();
}
const isLatestItem = latestItem?.name === item.name;
if (isLatestItem && existsItem.isDir) {
diffItems.push({
item: existsItem,
reason: `revalidate latest version, latest parent dir is ${latestVersionParent}, current dir is ${dir}, current name is ${existsItem.name}`,
});
latestVersionParent = `${latestVersionParent}${existsItem.name}`;
}
}
}
return diffItems;
return {
newItems: diffItems,
latestVersionDir: latestVersionParent,
};
}
private async saveBinaryItem(binary: Binary, tmpfile?: string) {
@@ -270,24 +387,37 @@ export class BinarySyncerService extends AbstractService {
const stat = await fs.stat(tmpfile);
binary.size = stat.size;
await this.nfsAdapter.uploadFile(binary.storePath, tmpfile);
this.logger.info('[BinarySyncerService.saveBinaryItem:uploadFile] binaryId: %s, size: %d, %s => %s',
binary.binaryId, stat.size, tmpfile, binary.storePath);
this.logger.info(
'[BinarySyncerService.saveBinaryItem:uploadFile] binaryId: %s, size: %d, %s => %s',
binary.binaryId,
stat.size,
tmpfile,
binary.storePath
);
}
await this.binaryRepository.saveBinary(binary);
return binary;
}
private async getBinaryAdapter(binaryName: BinaryName): Promise<AbstractBinary | undefined> {
private async getBinaryAdapter(
binaryName: BinaryName
): Promise<AbstractBinary | undefined> {
const config = this.config.cnpmcore;
const binaryConfig = binaries[binaryName];
let binaryAdapter: AbstractBinary;
if (config.sourceRegistryIsCNpm) {
binaryAdapter = await this.eggObjectFactory.getEggObject(AbstractBinary, BinaryType.Api);
binaryAdapter = await this.eggObjectFactory.getEggObject(
AbstractBinary,
BinaryType.Api
);
} else {
binaryAdapter = await this.eggObjectFactory.getEggObject(AbstractBinary, binaryConfig.type);
binaryAdapter = await this.eggObjectFactory.getEggObject(
AbstractBinary,
binaryConfig.type
);
}
await binaryAdapter.init(binaryName);
await binaryAdapter.initFetch(binaryName);
return binaryAdapter;
}
}

View File

@@ -1,11 +1,16 @@
import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';
import { EggLogger } from 'egg';
import type { EggLogger } from 'egg';
import pMap from 'p-map';
import { BugVersion } from '../entity/BugVersion';
import { PackageRepository } from '../../repository/PackageRepository';
import { DistRepository } from '../../repository/DistRepository';
import { getScopeAndName } from '../../common/PackageUtil';
import { CacheService } from './CacheService';
import { BugVersion } from '../entity/BugVersion.js';
import type {
PackageJSONType,
PackageRepository,
} from '../../repository/PackageRepository.js';
import type { DistRepository } from '../../repository/DistRepository.js';
import { getScopeAndName } from '../../common/PackageUtil.js';
import type { CacheService } from './CacheService.js';
import { BUG_VERSIONS, LATEST_TAG } from '../../common/constants.js';
import type { BugVersionStore } from '../../common/adapter/BugVersionStore.js';
@SingletonProto({
accessLevel: AccessLevel.PUBLIC,
@@ -23,53 +28,113 @@ export class BugVersionService {
@Inject()
private readonly cacheService: CacheService;
async cleanBugVersionPackageCaches(bugVersion: BugVersion) {
const fullnames = bugVersion.listAllPackagesHasBugs();
await pMap(fullnames, async fullname => {
await this.cacheService.removeCache(fullname);
}, {
concurrency: 50,
stopOnError: false,
});
@Inject()
private readonly bugVersionStore: BugVersionStore;
async getBugVersion(): Promise<BugVersion | undefined> {
// TODO performance problem, cache bugVersion and update with schedule
const pkg = await this.packageRepository.findPackage('', BUG_VERSIONS);
if (!pkg) return;
/* c8 ignore next 10 */
const tag = await this.packageRepository.findPackageTag(
pkg!.packageId,
LATEST_TAG
);
if (!tag) return;
let bugVersion = this.bugVersionStore.getBugVersion(tag!.version);
if (!bugVersion) {
const packageVersionJson =
(await this.distRepository.findPackageVersionManifest(
pkg!.packageId,
tag!.version
)) as PackageJSONType;
if (!packageVersionJson) return;
const data = packageVersionJson.config?.['bug-versions'];
bugVersion = new BugVersion(data || {});
this.bugVersionStore.setBugVersion(bugVersion, tag!.version);
}
return bugVersion;
}
async fixPackageBugVersions(bugVersion: BugVersion, fullname: string, manifests: Record<string, any>) {
async cleanBugVersionPackageCaches(bugVersion: BugVersion) {
const fullnames = bugVersion.listAllPackagesHasBugs();
await pMap(
fullnames,
async fullname => {
await this.cacheService.removeCache(fullname);
},
{
concurrency: 50,
stopOnError: false,
}
);
}
async fixPackageBugVersions(
bugVersion: BugVersion,
fullname: string,
manifests: Record<string, any>
) {
// If package all version unpublished(like pinyin-tool), versions is undefined
if (!manifests) return;
for (const manifest of Object.values(manifests)) {
this.fixPackageBugVersionWithAllVersions(fullname, bugVersion, manifest, manifests);
this.fixPackageBugVersionWithAllVersions(
fullname,
bugVersion,
manifest,
manifests
);
}
}
async fixPackageBugVersion(bugVersion: BugVersion, fullname: string, manifest: any) {
async fixPackageBugVersion(
bugVersion: BugVersion,
fullname: string,
manifest: any
) {
const advice = bugVersion.fixVersion(fullname, manifest.version);
if (!advice) {
return manifest;
}
const [ scope, name ] = getScopeAndName(fullname);
const [scope, name] = getScopeAndName(fullname);
const pkg = await this.packageRepository.findPackage(scope, name);
if (!pkg) {
return manifest;
}
const packageVersion = await this.packageRepository.findPackageVersion(pkg.packageId, advice.version);
const packageVersion = await this.packageRepository.findPackageVersion(
pkg.packageId,
advice.version
);
if (!packageVersion) {
return manifest;
}
const fixedManifest = await this.distRepository.findPackageVersionManifest(packageVersion.packageId, advice.version);
const fixedManifest = await this.distRepository.findPackageVersionManifest(
packageVersion.packageId,
advice.version
);
if (!fixedManifest) {
return manifest;
}
return bugVersion.fixManifest(manifest, fixedManifest);
}
private fixPackageBugVersionWithAllVersions(fullname: string, bugVersion: BugVersion, manifest: any, manifests: Record<string, any>) {
private fixPackageBugVersionWithAllVersions(
fullname: string,
bugVersion: BugVersion,
manifest: any,
manifests: Record<string, any>
) {
const advice = bugVersion.fixVersion(fullname, manifest.version);
if (!advice) {
return;
}
const fixedManifest = manifests[advice.version];
if (!fixedManifest) {
this.logger.warn('[BugVersionService] not found pkg for %s@%s manifest', fullname, advice.version);
this.logger.warn(
'[BugVersionService] not found pkg for %s@%s manifest',
fullname,
advice.version
);
return;
}
const newManifest = bugVersion.fixManifest(manifest, fixedManifest);

View File

@@ -1,13 +1,9 @@
import {
AccessLevel,
SingletonProto,
Inject,
} from '@eggjs/tegg';
import { CacheAdapter } from '../../common/adapter/CacheAdapter';
import { AbstractService } from '../../common/AbstractService';
import { ChangesStreamTaskData } from '../entity/Task';
import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';
import type { CacheAdapter } from '../../common/adapter/CacheAdapter.js';
import { AbstractService } from '../../common/AbstractService.js';
import type { ChangesStreamTaskData } from '../entity/Task.js';
type PackageCacheAttribe = 'etag' | 'manifests';
type PackageCacheAttribute = 'etag' | 'manifests';
export type UpstreamRegistryInfo = {
registry_name: string;
@@ -57,52 +53,72 @@ export class CacheService extends AbstractService {
return await this.cacheAdapter.getBytes(key);
}
public async savePackageEtagAndManifests(fullname: string, isFullManifests: boolean, etag: string, manifests: Buffer) {
public async savePackageEtagAndManifests(
fullname: string,
isFullManifests: boolean,
etag: string,
manifests: Buffer
) {
await Promise.all([
await this.cacheAdapter.set(this.cacheKey(fullname, isFullManifests, 'etag'), etag),
await this.cacheAdapter.setBytes(this.cacheKey(fullname, isFullManifests, 'manifests'), manifests),
this.cacheAdapter.set(
this.cacheKey(fullname, isFullManifests, 'etag'),
etag
),
this.cacheAdapter.setBytes(
this.cacheKey(fullname, isFullManifests, 'manifests'),
manifests
),
]);
}
public async getTotalData() {
const value = await this.cacheAdapter.get(TOTAL_DATA_KEY);
const totalData: TotalData = value ? JSON.parse(value) : {
packageCount: 0,
packageVersionCount: 0,
lastPackage: '',
lastPackageVersion: '',
download: {
today: 0,
thisweek: 0,
thismonth: 0,
thisyear: 0,
lastday: 0,
lastweek: 0,
lastmonth: 0,
lastyear: 0,
},
changesStream: {},
upstreamRegistries: [],
lastChangeId: 0,
cacheTime: '',
};
const totalData: TotalData = value
? JSON.parse(value)
: {
packageCount: 0,
packageVersionCount: 0,
lastPackage: '',
lastPackageVersion: '',
download: {
today: 0,
thisweek: 0,
thismonth: 0,
thisyear: 0,
lastday: 0,
lastweek: 0,
lastmonth: 0,
lastyear: 0,
},
changesStream: {},
upstreamRegistries: [],
lastChangeId: 0,
cacheTime: '',
};
return totalData;
}
public async saveTotalData(totalData: TotalData) {
return await this.cacheAdapter.set(TOTAL_DATA_KEY, JSON.stringify(totalData));
return await this.cacheAdapter.set(
TOTAL_DATA_KEY,
JSON.stringify(totalData)
);
}
public async removeCache(fullname: string) {
await Promise.all([
await this.cacheAdapter.delete(this.cacheKey(fullname, true, 'etag')),
await this.cacheAdapter.delete(this.cacheKey(fullname, true, 'manifests')),
await this.cacheAdapter.delete(this.cacheKey(fullname, false, 'etag')),
await this.cacheAdapter.delete(this.cacheKey(fullname, false, 'manifests')),
this.cacheAdapter.delete(this.cacheKey(fullname, true, 'etag')),
this.cacheAdapter.delete(this.cacheKey(fullname, true, 'manifests')),
this.cacheAdapter.delete(this.cacheKey(fullname, false, 'etag')),
this.cacheAdapter.delete(this.cacheKey(fullname, false, 'manifests')),
]);
}
private cacheKey(fullname: string, isFullManifests: boolean, attribute: PackageCacheAttribe) {
private cacheKey(
fullname: string,
isFullManifests: boolean,
attribute: PackageCacheAttribute
) {
return `${fullname}|${isFullManifests ? 'full' : 'abbr'}:${attribute}`;
}
}

View File

@@ -1,25 +1,24 @@
import os from 'os';
import { setTimeout } from 'timers/promises';
import {
AccessLevel,
SingletonProto,
EggObjectFactory,
Inject,
} from '@eggjs/tegg';
import { TaskState, TaskType } from '../../common/enum/Task';
import { AbstractService } from '../../common/AbstractService';
import { TaskRepository } from '../../repository/TaskRepository';
import { HOST_NAME, ChangesStreamTask, Task } from '../entity/Task';
import { PackageSyncerService, RegistryNotMatchError } from './PackageSyncerService';
import { TaskService } from './TaskService';
import { RegistryManagerService } from './RegistryManagerService';
import os from 'node:os';
import { setTimeout } from 'node:timers/promises';
import type { EggObjectFactory } from '@eggjs/tegg';
import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';
import { E500 } from 'egg-errors';
import { Registry } from '../entity/Registry';
import { AbstractChangeStream } from '../../common/adapter/changesStream/AbstractChangesStream';
import { getScopeAndName } from '../../common/PackageUtil';
import { GLOBAL_WORKER } from '../../common/constants';
import { ScopeManagerService } from './ScopeManagerService';
import { PackageRepository } from '../../repository/PackageRepository';
import type { PackageSyncerService } from './PackageSyncerService.js';
import { RegistryNotMatchError } from './PackageSyncerService.js';
import type { TaskService } from './TaskService.js';
import type { RegistryManagerService } from './RegistryManagerService.js';
import type { ScopeManagerService } from './ScopeManagerService.js';
import type { PackageRepository } from '../../repository/PackageRepository.js';
import type { TaskRepository } from '../../repository/TaskRepository.js';
import type { ChangesStreamTask } from '../entity/Task.js';
import { HOST_NAME, Task } from '../entity/Task.js';
import type { Registry } from '../entity/Registry.js';
import { AbstractChangeStream } from '../../common/adapter/changesStream/AbstractChangesStream.js';
import { getScopeAndName } from '../../common/PackageUtil.js';
import { isTimeoutError } from '../../common/ErrorUtil.js';
import { GLOBAL_WORKER } from '../../common/constants.js';
import { TaskState, TaskType } from '../../common/enum/Task.js';
import { AbstractService } from '../../common/AbstractService.js';
@SingletonProto({
accessLevel: AccessLevel.PUBLIC,
@@ -32,9 +31,9 @@ export class ChangesStreamService extends AbstractService {
@Inject()
private readonly taskService: TaskService;
@Inject()
private readonly registryManagerService : RegistryManagerService;
private readonly registryManagerService: RegistryManagerService;
@Inject()
private readonly scopeManagerService : ScopeManagerService;
private readonly scopeManagerService: ScopeManagerService;
@Inject()
private readonly eggObjectFactory: EggObjectFactory;
@Inject()
@@ -45,14 +44,22 @@ export class ChangesStreamService extends AbstractService {
// `{registryName}_WORKER`: 自定义 scope 的同步源
public async findExecuteTask(): Promise<ChangesStreamTask | null> {
const targetName = GLOBAL_WORKER;
const globalRegistryTask = await this.taskRepository.findTaskByTargetName(targetName, TaskType.ChangesStream);
const globalRegistryTask = await this.taskRepository.findTaskByTargetName(
targetName,
TaskType.ChangesStream
);
// 如果没有配置默认同步源,先进行初始化
if (!globalRegistryTask) {
await this.taskService.createTask(Task.createChangesStream(targetName), false);
await this.taskService.createTask(
Task.createChangesStream(targetName),
false
);
}
// 自定义 scope 由 admin 手动创建
// 根据 TaskType.ChangesStream 从队列中获取
return await this.taskService.findExecuteTask(TaskType.ChangesStream) as ChangesStreamTask;
return (await this.taskService.findExecuteTask(
TaskType.ChangesStream
)) as ChangesStreamTask;
}
public async suspendSync(exit = false) {
@@ -64,10 +71,16 @@ export class ChangesStreamService extends AbstractService {
}
const authorIp = os.hostname();
// 暂停当前机器所有的 changesStream 任务
const tasks = await this.taskRepository.findTaskByAuthorIpAndType(authorIp, TaskType.ChangesStream);
const tasks = await this.taskRepository.findTaskByAuthorIpAndType(
authorIp,
TaskType.ChangesStream
);
for (const task of tasks) {
if (task.state === TaskState.Processing) {
this.logger.info('[ChangesStreamService.suspendSync:suspend] taskId: %s', task.taskId);
this.logger.info(
'[ChangesStreamService.suspendSync:suspend] taskId: %s',
task.taskId
);
// 1. 更新任务状态为 waiting
// 2. 重新推入任务队列供其他机器执行
await this.taskService.retryTask(task);
@@ -92,8 +105,14 @@ export class ChangesStreamService extends AbstractService {
// allow disable changesStream dynamic
while (since && this.config.cnpmcore.enableChangesStream) {
const { lastSince, taskCount } = await this.executeSync(since, task);
this.logger.info('[ChangesStreamService.executeTask:changes] since: %s => %s, %d new tasks, taskId: %s, updatedAt: %j',
since, lastSince, taskCount, task.taskId, task.updatedAt);
this.logger.info(
'[ChangesStreamService.executeTask:changes] since: %s => %s, %d new tasks, taskId: %s, updatedAt: %j',
since,
lastSince,
taskCount,
task.taskId,
task.updatedAt
);
since = lastSince;
if (taskCount === 0 && this.config.env === 'unittest') {
break;
@@ -101,8 +120,15 @@ export class ChangesStreamService extends AbstractService {
await setTimeout(this.config.cnpmcore.checkChangesStreamInterval);
}
} catch (err) {
this.logger.error('[ChangesStreamService.executeTask:error] %s, exit now', err);
this.logger.error(err);
this.logger.warn(
'[ChangesStreamService.executeTask:error] %s, exit now',
err.message
);
if (isTimeoutError(err)) {
this.logger.warn(err);
} else {
this.logger.error(err);
}
task.error = `${err}`;
await this.taskRepository.saveTask(task);
await this.suspendSync();
@@ -114,9 +140,13 @@ export class ChangesStreamService extends AbstractService {
const { registryId } = task.data || {};
// 如果已有 registryId, 查询 DB 直接获取
if (registryId) {
const registry = await this.registryManagerService.findByRegistryId(registryId);
const registry =
await this.registryManagerService.findByRegistryId(registryId);
if (!registry) {
this.logger.error('[ChangesStreamService.getRegistry:error] registryId %s not found', registryId);
this.logger.error(
'[ChangesStreamService.getRegistry:error] registryId %s not found',
registryId
);
throw new E500(`invalid change stream registry: ${registryId}`);
}
return registry;
@@ -124,7 +154,7 @@ export class ChangesStreamService extends AbstractService {
const registry = await this.registryManagerService.ensureDefaultRegistry();
task.data = {
...(task.data || {}),
...task.data,
registryId: registry.registryId,
};
await this.taskRepository.saveTask(task);
@@ -136,9 +166,15 @@ export class ChangesStreamService extends AbstractService {
// 1. 如果该包已经指定了 registryId 则以 registryId 为准
// 1. 该包的 scope 在当前 registry 下
// 2. 如果 registry 下没有配置 scope (认为是通用 registry 地址) ,且该包的 scope 不在其他 registry 下
public async needSync(registry: Registry, fullname: string): Promise<boolean> {
const [ scopeName, name ] = getScopeAndName(fullname);
const packageEntity = await this.packageRepository.findPackage(scopeName, name);
public async needSync(
registry: Registry,
fullname: string
): Promise<boolean> {
const [scopeName, name] = getScopeAndName(fullname);
const packageEntity = await this.packageRepository.findPackage(
scopeName,
name
);
// 如果包不存在,且处在 exist 模式下,则不同步
if (this.config.cnpmcore.syncMode === 'exist' && !packageEntity) {
@@ -150,18 +186,24 @@ export class ChangesStreamService extends AbstractService {
}
const scope = await this.scopeManagerService.findByName(scopeName);
const inCurrentRegistry = scope && scope?.registryId === registry.registryId;
const inCurrentRegistry =
scope && scope?.registryId === registry.registryId;
if (inCurrentRegistry) {
return true;
}
const registryScopeCount = await this.scopeManagerService.countByRegistryId(registry.registryId);
const registryScopeCount = await this.scopeManagerService.countByRegistryId(
registry.registryId
);
// 当前包没有 scope 信息,且当前 registry 下没有 scope是通用 registry需要同步
return !scope && !registryScopeCount;
}
public async getInitialSince(task: ChangesStreamTask): Promise<string> {
const registry = await this.prepareRegistry(task);
const changesStreamAdapter = await this.eggObjectFactory.getEggObject(AbstractChangeStream, registry.type) as AbstractChangeStream;
const changesStreamAdapter = (await this.eggObjectFactory.getEggObject(
AbstractChangeStream,
registry.type
)) as AbstractChangeStream;
const since = await changesStreamAdapter.getInitialSince(registry);
return since;
}
@@ -170,7 +212,10 @@ export class ChangesStreamService extends AbstractService {
// 更新任务的 since 和 taskCount 相关字段
public async executeSync(since: string, task: ChangesStreamTask) {
const registry = await this.prepareRegistry(task);
const changesStreamAdapter = await this.eggObjectFactory.getEggObject(AbstractChangeStream, registry.type) as AbstractChangeStream;
const changesStreamAdapter = (await this.eggObjectFactory.getEggObject(
AbstractChangeStream,
registry.type
)) as AbstractChangeStream;
let taskCount = 0;
let lastSince = since;
@@ -196,17 +241,29 @@ export class ChangesStreamService extends AbstractService {
skipDependencies: true,
tips,
});
this.logger.info('[ChangesStreamService.createTask:success] fullname: %s, task: %s, tips: %s',
fullname, task.id, tips);
this.logger.info(
'[ChangesStreamService.createTask:success] fullname: %s, task: %s, tips: %s',
fullname,
task.id,
tips
);
} catch (err) {
if (err instanceof RegistryNotMatchError) {
this.logger.warn('[ChangesStreamService.executeSync:skip] fullname: %s, error: %s, tips: %s',
fullname, err, tips);
this.logger.warn(
'[ChangesStreamService.executeSync:skip] fullname: %s, error: %s, tips: %s',
fullname,
err,
tips
);
continue;
}
// only log error, make sure changes still reading
this.logger.error('[ChangesStreamService.executeSync:error] fullname: %s, error: %s, tips: %s',
fullname, err, tips);
this.logger.error(
'[ChangesStreamService.executeSync:error] fullname: %s, error: %s, tips: %s',
fullname,
err,
tips
);
this.logger.error(err);
continue;
}

View File

@@ -1,16 +1,17 @@
import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';
import { AbstractService } from '../../common/AbstractService';
import { HookType } from '../../common/enum/Hook';
import { TaskState } from '../../common/enum/Task';
import { HookEvent } from '../entity/HookEvent';
import { CreateHookTask, Task } from '../entity/Task';
import { HookRepository } from '../../repository/HookRepository';
import { PackageRepository } from '../../repository/PackageRepository';
import pMap from 'p-map';
import { Hook } from '../entity/Hook';
import { TaskService } from './TaskService';
import { isoNow } from '../../common/LogUtil';
import { getScopeAndName } from '../../common/PackageUtil';
import { AbstractService } from '../../common/AbstractService.js';
import { HookType } from '../../common/enum/Hook.js';
import { TaskState } from '../../common/enum/Task.js';
import type { HookEvent } from '../entity/HookEvent.js';
import type { CreateHookTask } from '../entity/Task.js';
import { Task } from '../entity/Task.js';
import type { HookRepository } from '../../repository/HookRepository.js';
import type { PackageRepository } from '../../repository/PackageRepository.js';
import type { Hook } from '../entity/Hook.js';
import type { TaskService } from './TaskService.js';
import { isoNow } from '../../common/LogUtil.js';
import { getScopeAndName } from '../../common/PackageUtil.js';
@SingletonProto({
accessLevel: AccessLevel.PUBLIC,
@@ -27,10 +28,14 @@ export class CreateHookTriggerService extends AbstractService {
async executeTask(task: CreateHookTask): Promise<void> {
const { hookEvent } = task.data;
const [ scope, name ] = getScopeAndName(hookEvent.fullname);
const [scope, name] = getScopeAndName(hookEvent.fullname);
const pkg = await this.packageRepository.findPackage(scope, name);
if (!pkg) {
await this.taskService.finishTask(task, TaskState.Success, `[${isoNow()}][Hooks] package ${hookEvent.fullname} not exits`);
await this.taskService.finishTask(
task,
TaskState.Success,
`[${isoNow()}][Hooks] package ${hookEvent.fullname} not exits`
);
return;
}
@@ -38,41 +43,97 @@ export class CreateHookTriggerService extends AbstractService {
`[${isoNow()}][Hooks] Start Create Trigger for ${pkg.fullname} ${task.data.hookEvent.changeId}`,
`[${isoNow()}][Hooks] change content ${JSON.stringify(task.data.hookEvent.change)}`,
];
await this.taskService.finishTask(task, TaskState.Processing, startLog.join('\n'));
await this.taskService.finishTask(
task,
TaskState.Processing,
startLog.join('\n')
);
try {
await this.taskService.appendTaskLog(task, `[${isoNow()}][Hooks] PushHooks to ${HookType.Package} ${pkg.fullname}\n`);
await this.createTriggerByMethod(task, HookType.Package, pkg.fullname, hookEvent);
await this.taskService.appendTaskLog(task, `[${isoNow()}][Hooks] PushHooks to ${HookType.Scope} ${pkg.scope}\n`);
await this.createTriggerByMethod(task, HookType.Scope, pkg.scope, hookEvent);
await this.taskService.appendTaskLog(
task,
`[${isoNow()}][Hooks] PushHooks to ${HookType.Package} ${pkg.fullname}\n`
);
await this.createTriggerByMethod(
task,
HookType.Package,
pkg.fullname,
hookEvent
);
await this.taskService.appendTaskLog(
task,
`[${isoNow()}][Hooks] PushHooks to ${HookType.Scope} ${pkg.scope}\n`
);
await this.createTriggerByMethod(
task,
HookType.Scope,
pkg.scope,
hookEvent
);
const maintainers = await this.packageRepository.listPackageMaintainers(pkg.packageId);
const maintainers = await this.packageRepository.listPackageMaintainers(
pkg.packageId
);
for (const maintainer of maintainers) {
await this.taskService.appendTaskLog(task, `[${isoNow()}][Hooks] PushHooks to ${HookType.Owner} ${maintainer.name}\n`);
await this.createTriggerByMethod(task, HookType.Owner, maintainer.name, hookEvent);
await this.taskService.appendTaskLog(
task,
`[${isoNow()}][Hooks] PushHooks to ${HookType.Owner} ${maintainer.name}\n`
);
await this.createTriggerByMethod(
task,
HookType.Owner,
maintainer.name,
hookEvent
);
}
await this.taskService.finishTask(task, TaskState.Success, `[${isoNow()}][Hooks] create trigger succeed \n`);
await this.taskService.finishTask(
task,
TaskState.Success,
`[${isoNow()}][Hooks] create trigger succeed \n`
);
} catch (e) {
e.message = 'create trigger failed: ' + e.message;
await this.taskService.finishTask(task, TaskState.Fail, `[${isoNow()}][Hooks] ${e.stack} \n`);
await this.taskService.finishTask(
task,
TaskState.Fail,
`[${isoNow()}][Hooks] ${e.stack} \n`
);
return;
}
}
private async createTriggerByMethod(task: Task, type: HookType, name: string, hookEvent: HookEvent) {
private async createTriggerByMethod(
task: Task,
type: HookType,
name: string,
hookEvent: HookEvent
) {
let hooks = await this.hookRepository.listHooksByTypeAndName(type, name);
while (hooks.length) {
await this.createTriggerTasks(hooks, hookEvent);
hooks = await this.hookRepository.listHooksByTypeAndName(type, name, hooks[hooks.length - 1].id);
await this.taskService.appendTaskLog(task,
`[${isoNow()}][Hooks] PushHooks to ${type} ${name} ${hooks.length} \n`);
hooks = await this.hookRepository.listHooksByTypeAndName(
type,
name,
hooks[hooks.length - 1].id
);
await this.taskService.appendTaskLog(
task,
`[${isoNow()}][Hooks] PushHooks to ${type} ${name} ${hooks.length} \n`
);
}
}
private async createTriggerTasks(hooks: Array<Hook>, hookEvent: HookEvent) {
await pMap(hooks, async hook => {
const triggerHookTask = Task.createTriggerHookTask(hookEvent, hook.hookId);
await this.taskService.createTask(triggerHookTask, true);
}, { concurrency: 5 });
await pMap(
hooks,
async hook => {
const triggerHookTask = Task.createTriggerHookTask(
hookEvent,
hook.hookId
);
await this.taskService.createTask(triggerHookTask, true);
},
{ concurrency: 5 }
);
}
}

View File

@@ -1,7 +1,11 @@
import { ContextEventBus, Inject } from '@eggjs/tegg';
import { Advice, IAdvice } from '@eggjs/tegg/aop';
import type { ContextEventBus } from '@eggjs/tegg';
import { Inject, ObjectInitType } from '@eggjs/tegg';
import type { IAdvice } from '@eggjs/tegg/aop';
import { Advice } from '@eggjs/tegg/aop';
@Advice()
@Advice({
initType: ObjectInitType.CONTEXT,
})
export class EventCorkAdvice implements IAdvice {
@Inject()
private eventBus: ContextEventBus;

View File

@@ -0,0 +1,46 @@
import { SingletonProto, AccessLevel, Inject } from '@eggjs/tegg';
import type { EggLogger } from 'egg';
import pMap from 'p-map';
import type { PackageVersionRepository } from '../../repository/PackageVersionRepository.js';
import { PaddingSemVer } from '../entity/PaddingSemVer.js';
@SingletonProto({
accessLevel: AccessLevel.PUBLIC,
})
export class FixNoPaddingVersionService {
@Inject()
private readonly packageVersionRepository: PackageVersionRepository;
@Inject()
private readonly logger: EggLogger;
async fixPaddingVersion(id?: number): Promise<void> {
// eslint-disable-next-line no-constant-condition
while (true) {
const packageVersions =
await this.packageVersionRepository.findHaveNotPaddingVersion(id);
if (packageVersions.length === 0) {
break;
}
id =
(packageVersions[packageVersions.length - 1].id as unknown as number) +
1;
this.logger.info(
'[FixNoPaddingVersionService] fix padding version ids %j',
packageVersions.map(t => t.id)
);
await pMap(
packageVersions,
async packageVersion => {
const paddingSemver = new PaddingSemVer(packageVersion.version);
await this.packageVersionRepository.fixPaddingVersion(
packageVersion.packageVersionId,
paddingSemver
);
},
{ concurrency: 30 }
);
}
}
}

View File

@@ -0,0 +1,19 @@
import {
AccessLevel,
SingletonProto,
} from '@eggjs/tegg';
import { NotFoundError, NotImplementedError } from 'egg-errors';
import { AbstractService } from '../../common/AbstractService.js';
import { NOT_IMPLEMENTED_PATH } from '../../common/constants.js';
@SingletonProto({
accessLevel: AccessLevel.PUBLIC,
})
export class HomeService extends AbstractService {
async misc(path: string) {
if (NOT_IMPLEMENTED_PATH.includes(path)) {
throw new NotImplementedError(`${path} not implemented yet`);
}
throw new NotFoundError(`${path} not found`);
}
}

View File

@@ -1,12 +1,9 @@
import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';
import { Hook } from '../entity/Hook';
import { HookType } from '../../common/enum/Hook';
import {
ForbiddenError,
NotFoundError,
} from 'egg-errors';
import { HookRepository } from '../../repository/HookRepository';
import { EggAppConfig } from 'egg';
import { ForbiddenError, NotFoundError } from 'egg-errors';
import type { EggAppConfig } from 'egg';
import type { HookRepository } from '../../repository/HookRepository.js';
import { Hook } from '../entity/Hook.js';
import type { HookType } from '../../common/enum/Hook.js';
export interface CreateHookCommand {
type: HookType;
@@ -59,7 +56,9 @@ export class HookManageService {
throw new NotFoundError(`hook ${cmd.hookId} not found`);
}
if (hook.ownerId !== cmd.operatorId) {
throw new ForbiddenError(`hook ${cmd.hookId} not belong to ${cmd.operatorId}`);
throw new ForbiddenError(
`hook ${cmd.hookId} not belong to ${cmd.operatorId}`
);
}
hook.endpoint = cmd.endpoint;
hook.secret = cmd.secret;
@@ -73,7 +72,9 @@ export class HookManageService {
throw new NotFoundError(`hook ${cmd.hookId} not found`);
}
if (hook.ownerId !== cmd.operatorId) {
throw new ForbiddenError(`hook ${cmd.hookId} not belong to ${cmd.operatorId}`);
throw new ForbiddenError(
`hook ${cmd.hookId} not belong to ${cmd.operatorId}`
);
}
await this.hookRepository.removeHook(cmd.hookId);
return hook;

View File

@@ -1,16 +1,16 @@
import { AccessLevel, SingletonProto, Inject } from '@eggjs/tegg';
import { TriggerHookTask } from '../entity/Task';
import { HookEvent } from '../entity/HookEvent';
import { HookRepository } from '../../repository/HookRepository';
import { PackageRepository } from '../../repository/PackageRepository';
import { DistRepository } from '../../repository/DistRepository';
import { UserRepository } from '../../repository/UserRepository';
import { Hook } from '../entity/Hook';
import { EggContextHttpClient } from 'egg';
import { isoNow } from '../../common/LogUtil';
import { TaskState } from '../../common/enum/Task';
import { TaskService } from './TaskService';
import { getScopeAndName } from '../../common/PackageUtil';
import type { EggContextHttpClient } from 'egg';
import type { TriggerHookTask } from '../entity/Task.js';
import type { HookEvent } from '../entity/HookEvent.js';
import type { HookRepository } from '../../repository/HookRepository.js';
import type { PackageRepository } from '../../repository/PackageRepository.js';
import type { DistRepository } from '../../repository/DistRepository.js';
import type { UserRepository } from '../../repository/UserRepository.js';
import type { Hook } from '../entity/Hook.js';
import { isoNow } from '../../common/LogUtil.js';
import { TaskState } from '../../common/enum/Task.js';
import type { TaskService } from './TaskService.js';
import { getScopeAndName } from '../../common/PackageUtil.js';
@SingletonProto({
accessLevel: AccessLevel.PUBLIC,
@@ -38,24 +38,40 @@ export class HookTriggerService {
const { hookId, hookEvent } = task.data;
const hook = await this.hookRepository.findHookById(hookId);
if (!hook) {
await this.taskService.finishTask(task, TaskState.Success, `[${isoNow()}][TriggerHooks] hook ${hookId} not exits`);
await this.taskService.finishTask(
task,
TaskState.Success,
`[${isoNow()}][TriggerHooks] hook ${hookId} not exits`
);
return;
}
try {
const payload = await this.createTriggerPayload(task, hookEvent, hook);
if (!payload) {
await this.taskService.finishTask(task, TaskState.Success, `[${isoNow()}][TriggerHooks] generate payload failed \n`);
await this.taskService.finishTask(
task,
TaskState.Success,
`[${isoNow()}][TriggerHooks] generate payload failed \n`
);
return;
}
const status = await this.doExecuteTrigger(hook, payload);
hook.latestTaskId = task.taskId;
task.data.responseStatus = status;
await this.hookRepository.saveHook(hook);
await this.taskService.finishTask(task, TaskState.Success, `[${isoNow()}][TriggerHooks] trigger hook succeed ${status} \n`);
await this.taskService.finishTask(
task,
TaskState.Success,
`[${isoNow()}][TriggerHooks] trigger hook succeed ${status} \n`
);
} catch (e) {
e.message = 'trigger hook failed: ' + e.message;
task.error = e.message;
await this.taskService.finishTask(task, TaskState.Fail, `[${isoNow()}][TriggerHooks] ${e.stack} \n`);
await this.taskService.finishTask(
task,
TaskState.Fail,
`[${isoNow()}][TriggerHooks] ${e.stack} \n`
);
return;
}
}
@@ -82,19 +98,33 @@ export class HookTriggerService {
throw new Error(`hook response with ${res.status}`);
}
async createTriggerPayload(task: TriggerHookTask, hookEvent: HookEvent, hook: Hook): Promise<object | undefined> {
const [ scope, name ] = getScopeAndName(hookEvent.fullname);
async createTriggerPayload(
task: TriggerHookTask,
hookEvent: HookEvent,
hook: Hook
): Promise<object | undefined> {
const [scope, name] = getScopeAndName(hookEvent.fullname);
const pkg = await this.packageRepository.findPackage(scope, name);
if (!pkg) {
await this.taskService.finishTask(task, TaskState.Success, `[${isoNow()}][TriggerHooks] can not found pkg for ${hookEvent.fullname} \n`);
await this.taskService.finishTask(
task,
TaskState.Success,
`[${isoNow()}][TriggerHooks] can not found pkg for ${hookEvent.fullname} \n`
);
return;
}
const user = await this.userRepository.findUserByUserId(hook.ownerId);
if (!user) {
await this.taskService.finishTask(task, TaskState.Success, `[${isoNow()}][TriggerHooks] can not found user for ${hook.ownerId} \n`);
await this.taskService.finishTask(
task,
TaskState.Success,
`[${isoNow()}][TriggerHooks] can not found user for ${hook.ownerId} \n`
);
return;
}
const manifest = await this.distRepository.readDistBytesToJSON(pkg!.manifestsDist!);
const manifest = await this.distRepository.readDistBytesToJSON(
pkg!.manifestsDist!
);
return {
event: hookEvent.event,
name: pkg.fullname,

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