Compare commits

...

585 Commits

Author SHA1 Message Date
semantic-release-bot
8b341d2299 Release 4.7.0
[skip ci]

## [4.7.0](https://github.com/cnpm/cnpmcore/compare/v4.6.3...v4.7.0) (2025-06-10)

### Features

* add shouldNotMerge for task data for skip task merge ([#807](https://github.com/cnpm/cnpmcore/issues/807)) ([490dce3](490dce3ad2))

### Bug Fixes

* content type check ([#809](https://github.com/cnpm/cnpmcore/issues/809)) ([b8c7c06](b8c7c06536)), closes [#693](https://github.com/cnpm/cnpmcore/issues/693)
* order binary by date ([#808](https://github.com/cnpm/cnpmcore/issues/808)) ([12aa425](12aa425c26))
2025-06-10 00:52:26 +00:00
fengmk2
87b6cbedef chore: use oxlint 0.18 (#810)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Chores**
  - Updated the development dependency "oxlint" to the latest version.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-10 08:50:55 +08:00
semantic-release-bot
3e395405c1 Release 4.7.0
[skip ci]

## [4.7.0](https://github.com/cnpm/cnpmcore/compare/v4.6.3...v4.7.0) (2025-06-09)

### Features

* add shouldNotMerge for task data for skip task merge ([#807](https://github.com/cnpm/cnpmcore/issues/807)) ([490dce3](490dce3ad2))

### Bug Fixes

* content type check ([#809](https://github.com/cnpm/cnpmcore/issues/809)) ([b8c7c06](b8c7c06536)), closes [#693](https://github.com/cnpm/cnpmcore/issues/693)
* order binary by date ([#808](https://github.com/cnpm/cnpmcore/issues/808)) ([12aa425](12aa425c26))
2025-06-09 12:50:45 +00:00
elrrrrrrr
b8c7c06536 fix: content type check (#809)
> Fix the content-type display logic of showFileController, close #693 

* 🌐 HTML/XML files now render directly (no forced attachment)
* ⚠️ Note: No changes to actual file storage - only affects presentation
layer
* ♻️ New content automatically converts to standardized formats

-------
> 修复 showFileController 相关接口 content-type 展示逻辑, close #693 

* 对于 html,xml 不再默认返回 attachment,改为直接渲染 
* 增量部分默认进行格式转换
* 存量部分在 controller 查看时统一返回




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

## Summary by CodeRabbit

- **Bug Fixes**
- Files with `.xml` and `.html` extensions are now served with a
`text/plain` content type instead of their previous MIME types,
improving content handling and consistency.
- The download behavior for HTML and XML files has been updated; these
files are no longer forced as attachments in the browser.
- **Tests**
- Updated tests to reflect the new content type handling for `.xml` and
`.html` files.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-09 20:49:29 +08:00
semantic-release-bot
76e3f267c1 Release 4.7.0
[skip ci]

## [4.7.0](https://github.com/cnpm/cnpmcore/compare/v4.6.3...v4.7.0) (2025-05-31)

### Features

* add shouldNotMerge for task data for skip task merge ([#807](https://github.com/cnpm/cnpmcore/issues/807)) ([490dce3](490dce3ad2))

### Bug Fixes

* order binary by date ([#808](https://github.com/cnpm/cnpmcore/issues/808)) ([12aa425](12aa425c26))
2025-05-31 11:39:42 +00:00
killa
12aa425c26 fix: order binary by date (#808)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved accuracy in retrieving the latest binary directory by
updating the sorting criterion.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-31 19:38:07 +08:00
semantic-release-bot
01f393eb94 Release 4.7.0
[skip ci]

## [4.7.0](https://github.com/cnpm/cnpmcore/compare/v4.6.3...v4.7.0) (2025-05-31)

### Features

* add shouldNotMerge for task data for skip task merge ([#807](https://github.com/cnpm/cnpmcore/issues/807)) ([490dce3](490dce3ad2))
2025-05-31 11:05:43 +00:00
killa
490dce3ad2 feat: add shouldNotMerge for task data for skip task merge (#807)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added support for a new task property to prevent certain tasks from
being merged when waiting, providing more granular control over task
handling.
- **Bug Fixes**
- Improved logic for task merging to consider both task type and a new
optional flag, ensuring correct behavior for historical task
compensation scenarios.
- **Tests**
- Introduced new tests to verify the updated task merging logic and the
effect of the new property.
- **Chores**
- Limited the number of items returned per request when fetching
platform-specific binaries to improve data retrieval efficiency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-31 19:04:13 +08:00
semantic-release-bot
cb3768c82f Release 4.6.3
[skip ci]

## [4.6.3](https://github.com/cnpm/cnpmcore/compare/v4.6.2...v4.6.3) (2025-05-29)

### Bug Fixes

* set puppeteer loop only once ([#805](https://github.com/cnpm/cnpmcore/issues/805)) ([54e3335](54e3335abd))
2025-05-29 13:27:20 +00:00
killa
54e3335abd fix: set puppeteer loop only once (#805) 2025-05-29 21:26:00 +08:00
semantic-release-bot
a1a3859d75 Release 4.6.2
[skip ci]

## [4.6.2](https://github.com/cnpm/cnpmcore/compare/v4.6.1...v4.6.2) (2025-05-29)

### Bug Fixes

* only set lastSyncTime if lastData.lastSyncTime is undefined ([#804](https://github.com/cnpm/cnpmcore/issues/804)) ([1239dc9](1239dc9f49))
2025-05-29 08:00:36 +00:00
killa
1239dc9f49 fix: only set lastSyncTime if lastData.lastSyncTime is undefined (#804)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved synchronization logic to prevent overwriting the last sync
time for Chromium browser snapshots if it is already set.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 15:59:12 +08:00
semantic-release-bot
9efd2932e8 Release 4.6.1
[skip ci]

## [4.6.1](https://github.com/cnpm/cnpmcore/compare/v4.6.0...v4.6.1) (2025-05-29)

### Bug Fixes

* lastSyncTime is string should not stringify again ([#803](https://github.com/cnpm/cnpmcore/issues/803)) ([f6f5314](f6f53149a1))
2025-05-29 07:28:28 +00:00
killa
f6f53149a1 fix: lastSyncTime is string should not stringify again (#803)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved handling of the `lastSyncTime` parameter when fetching binary
data to ensure more accurate synchronization.

- **Tests**
- Added a new test case to verify fetching binary data with a provided
`lastSyncTime`.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-29 15:27:05 +08:00
semantic-release-bot
2e90486454 Release 4.6.0
[skip ci]

## [4.6.0](https://github.com/cnpm/cnpmcore/compare/v4.5.3...v4.6.0) (2025-05-29)

### Features

* add since for api sync binary ([#802](https://github.com/cnpm/cnpmcore/issues/802)) ([297bd7a](297bd7a745))
2025-05-29 00:12:33 +00:00
killa
297bd7a745 feat: add since for api sync binary (#802) 2025-05-29 08:11:05 +08:00
fengmk2
b8f2ac5f85 test: run es tests on github action (#796)
<!--- SUMMARY_MARKER --->
## Sweep Summary <sub><a href="https://app.sweep.dev"><img
src="https://raw.githubusercontent.com/sweepai/sweep/main/.assets/sweep-square.png"
width="25" alt="Sweep"></a></sub>

Adds Elasticsearch setup to GitHub Actions workflow to enable running
Elasticsearch-dependent tests in CI.

- Added system configuration steps for Elasticsearch in the GitHub
Actions workflow, including swap and sysctl settings.
- Integrated the official Elastic GitHub Action to run Elasticsearch
8.18.0 during CI tests.
- Implemented a wait mechanism to ensure Elasticsearch is fully ready
before proceeding with tests.

---
[Ask Sweep AI questions about this PR](https://app.sweep.dev)
<!--- SUMMARY_MARKER --->

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

- **Chores**
- Enhanced CI workflow to configure system limits and start an
Elasticsearch service before running tests. The workflow now waits for
Elasticsearch to be fully ready before proceeding.
- Improved test diagnostics by adding detailed response data to
assertion messages for better debugging.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-16 18:15:13 +08:00
semantic-release-bot
c5f1642a1f Release 4.5.3
[skip ci]

## [4.5.3](https://github.com/cnpm/cnpmcore/compare/v4.5.2...v4.5.3) (2025-05-14)

### Bug Fixes

* use new changestream endpoint ([#800](https://github.com/cnpm/cnpmcore/issues/800)) ([2d8bad3](2d8bad3ca2))
2025-05-14 03:52:33 +00:00
fengmk2
2d8bad3ca2 fix: use new changestream endpoint (#800)
should be `https://replicate.npmjs.com/registry` not
`https://replicate.npmjs.com`

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

- **Chores**
  - Updated the changes stream registry endpoint configuration.
- Standardized assertion methods in tests to use `assert.ok()` for
improved clarity and consistency across the codebase.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: fengmk2 <fengmk2@gmail.com>
Co-authored-by: elrrrrrrr <elrrrrrrr@gmail.com>
2025-05-14 11:51:10 +08:00
elrrrrrrr
81620e3ed5 refactor: doc_count & doc_version_count perf (#797)
<!--- SUMMARY_MARKER --->
## Sweep Summary <sub><a href="https://app.sweep.dev"><img
src="https://raw.githubusercontent.com/sweepai/sweep/main/.assets/sweep-square.png"
width="25" alt="Sweep"></a></sub>

Improves database performance by replacing expensive count queries with
a dedicated totals table that's updated asynchronously via events.

- Created a new `totals` table in `app/repository/model/Total.ts` to
store package and version counts instead of running expensive SQL count
queries.
- Implemented `TotalRepository` in `app/repository/TotalRepository.ts`
with methods to increment and retrieve count values.
- Added event handlers in `app/core/event/TotalHandler.ts` that listen
for package and version additions to update counts asynchronously.
- Modified `PackageRepository.queryTotal()` to fetch counts from the
totals table instead of executing direct SQL count queries.
- Added migration scripts in `sql/mysql/4.3.0.sql` and
`sql/postgresql/4.3.0.sql` to create the totals table and populate it
with existing data.

---
[Ask Sweep AI questions about this PR](https://app.sweep.dev)
<!--- SUMMARY_MARKER --->

> Fix database performance issues caused by doc_count and
doc_version_count queries

1. 💽 Add a corresponding totals table to record statistical information
2.  Add a `PACKAGE_ADDED` event and the original
`PACKAGE_VERSION_ADDED` event to asynchronously update records in the
table
3. ♻️ Add a new existing data migration script to migrate the original
statistical information to the totals table

-----------

> 修复 doc_count 和 doc_version_count 查询导致的数据库性能问题

1. 💽 新增对应 totals 表,用来记录统计信息
2.  新增 `PACKAGE_ADDED` 事件,和原有 `PACKAGE_VERSION_ADDED` 事件,异步更新表内记录
3. ♻️ 新增存量数据迁移脚本,迁移原有的统计信息到 totals 表

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

- **New Features**
- Introduced persistent tracking of total package and package version
counts, with real-time updates when new packages or versions are added.
- Added new data models and repository methods to manage and retrieve
these total counts.
- Emitted events upon new package creation to update totals
automatically.

- **Database**
- Added a new "totals" table to both MySQL and PostgreSQL databases for
storing aggregate counts initialized from existing data.

- **Bug Fixes**
- Ensured total counts are always returned as numbers in scheduled data
updates.

- **Tests**
- Added and updated tests to verify correct behavior of total count
tracking, incrementing, resetting, and retrieval.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-24 14:23:03 +08:00
semantic-release-bot
de3a6153b0 Release 4.5.2
[skip ci]

## [4.5.2](https://github.com/cnpm/cnpmcore/compare/v4.5.1...v4.5.2) (2025-04-17)

### Bug Fixes

* init release ([#795](https://github.com/cnpm/cnpmcore/issues/795)) ([3203c64](3203c64c48)), closes [/github.com/cnpm/cnpm/issues/459#issue-2998106947](https://github.com/cnpm//github.com/cnpm/cnpm/issues/459/issues/issue-2998106947)
2025-04-17 06:38:18 +00:00
elrrrrrrr
3203c64c48 fix: init release (#795)
> close https://github.com/cnpm/cnpm/issues/459#issue-2998106947
1. Correct the initialization of initRelease to ensure the fetch process
is triggered as expected.
------

> close https://github.com/cnpm/cnpm/issues/459#issue-2998106947
1. 🐛 修复 initRelease 时,被错误初始化为 [] 空数组,导致不会触发 fetch 

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

## Summary by CodeRabbit

- **Refactor**
- Improved handling of uninitialized or absent release data, ensuring
clearer distinction between missing and empty release lists.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-17 14:37:21 +08:00
semantic-release-bot
d6c243cf6b Release 4.5.1
[skip ci]

## [4.5.1](https://github.com/cnpm/cnpmcore/compare/v4.5.0...v4.5.1) (2025-04-15)

### Bug Fixes

* disable information_schema.tables count ([#794](https://github.com/cnpm/cnpmcore/issues/794)) ([0a6eab3](0a6eab325e))
2025-04-15 04:01:05 +00:00
elrrrrrrr
0a6eab325e fix: disable information_schema.tables count (#794)
> Fix potential performance issues caused by using
`information_schema.tables` in distributed databases

1. 📊 `information_schema.tables` has no index, consumes large amounts of
memory, and requires aggregation after being generated per instance.
2. 🚚 Execution plans involve multi-table joins, distributed operations,
GROUP BY, and similar operations.
3. ♻️ Consistently use `SELECT COUNT` with index.
----------

> 修复在分布式 db 中,使用 `information_schema.tables` 可能导致的性能问题
1. 📊 `information_schema.tables` 内部无索引,占用大量内存,需要根据实例数生成后聚合
2. 🚚 执行计划涉及多表连接、分布式操作、GROUP BY 等操作
3. ♻️ 统一使用 `select count`,通过索引计算

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

- **Refactor**
- Unified the method for retrieving total row counts across all
databases for improved consistency.
- **Chores**
  - Updated linting configuration to allow grouped exports.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-15 11:59:39 +08:00
elrrrrrrr
a17aed8fb1 chore: custom registry (#793) 2025-04-11 23:05:46 +08:00
semantic-release-bot
770fc7fdaf Release 4.5.0
[skip ci]

## [4.5.0](https://github.com/cnpm/cnpmcore/compare/v4.4.0...v4.5.0) (2025-04-11)

### Features

* use npm new replication api ([#792](https://github.com/cnpm/cnpmcore/issues/792)) ([de97428](de97428ffd))
2025-04-11 13:05:02 +00:00
fengmk2
de97428ffd feat: use npm new replication api (#792)
closes https://github.com/cnpm/cnpmcore/issues/791

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

- **New Features**
  - Added an HTTP header to indicate replication opt-in.
- Enhanced logging now shows the results count and last sequence
identifier.

- **Refactor**
- Streamlined the change-fetching process by directly iterating over
response data.

- **Tests**
- Updated test cases with refined assertions and a simplified mocking
strategy to align expectations with the new response format.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-11 21:03:36 +08:00
semantic-release-bot
22da5c7f70 Release 4.4.0
[skip ci]

## [4.4.0](https://github.com/cnpm/cnpmcore/compare/v4.3.1...v4.4.0) (2025-04-09)

### Features

* add onnxruntime binary mirror ([#790](https://github.com/cnpm/cnpmcore/issues/790)) ([f4f09c2](f4f09c2d66))
2025-04-09 09:02:40 +00:00
Feng Yu
f4f09c2d66 feat: add onnxruntime binary mirror (#790) 2025-04-09 17:01:12 +08:00
semantic-release-bot
3dce867d3a Release 4.3.1
[skip ci]

## [4.3.1](https://github.com/cnpm/cnpmcore/compare/v4.3.0...v4.3.1) (2025-04-08)

### Bug Fixes

* execute SyncBinaryTask should with data ([#788](https://github.com/cnpm/cnpmcore/issues/788)) ([af3672e](af3672ebdb))
2025-04-08 05:55:10 +00:00
killa
af3672ebdb fix: execute SyncBinaryTask should with data (#788)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Enhanced the task management system to support binary data
synchronization tasks, enabling more precise handling of binary workflow
processes.
- Improved support for binary task execution and logging to ensure data
accuracy during synchronization.

- **Tests**
- Added new test cases to validate the proper execution and data
handling for binary synchronization tasks.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-08 13:53:45 +08:00
semantic-release-bot
e89d8528df Release 4.3.0
[skip ci]

## [4.3.0](https://github.com/cnpm/cnpmcore/compare/v4.2.0...v4.3.0) (2025-04-07)

### Features

* mirror astral-sh/python-build-standalone ([#787](https://github.com/cnpm/cnpmcore/issues/787)) ([58a1a9c](58a1a9c2af))
2025-04-07 06:28:18 +00:00
fengmk2
58a1a9c2af feat: mirror astral-sh/python-build-standalone (#787)
closes https://github.com/cnpm/cnpmcore/issues/784

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

## Summary by CodeRabbit

- **New Features**
- Introduced a new option for obtaining redistributable Python builds,
making it easier for users to access pre-packaged releases via GitHub.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-07 14:26:54 +08:00
semantic-release-bot
ce0fd9d9c2 Release 4.2.0
[skip ci]

## [4.2.0](https://github.com/cnpm/cnpmcore/compare/v4.1.6...v4.2.0) (2025-04-03)

### Features

* **sql:** add gmt_create for binaries index idx_category_parent_gmt_create ([#786](https://github.com/cnpm/cnpmcore/issues/786)) ([080035f](080035f7bc))
2025-04-03 06:48:31 +00:00
killa
080035f7bc feat(sql): add gmt_create for binaries index idx_category_parent_gmt_create (#786)
Include sql change.

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

- **New Features**
- Enhanced data processing with additional safeguards to limit
iterations during data retrieval, improving stability and preventing
resource overuse.

- **Chores**
- Refined database indexing by removing an old index and adding a new
one, optimizing query performance for faster data access and a smoother
overall experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-03 14:47:12 +08:00
semantic-release-bot
ffd90473f7 Release 4.1.6
[skip ci]

## [4.1.6](https://github.com/cnpm/cnpmcore/compare/v4.1.5...v4.1.6) (2025-04-02)

### Bug Fixes

* iterator chromium bucket to get all versions ([#785](https://github.com/cnpm/cnpmcore/issues/785)) ([facb26d](facb26d192))
2025-04-02 14:46:56 +00:00
killa
facb26d192 fix: iterator chromium bucket to get all versions (#785)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced an admin-protected endpoint for binary synchronization with
support for additional data.
- Added enhanced logic to dynamically retrieve and update the latest
binary versions for multiple platforms.
  
- **Improvements**
- Optimized binary data fetching by migrating to a more dynamic,
cloud-based data source.
  
- **Dependency Management**
- Updated project dependencies to improve XML parsing capabilities while
removing outdated tools.
  
- **Tests**
- Revised test workflows to verify the new synchronization parameters
and data structures.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-02 22:45:18 +08:00
zhangyuantao
53060c817a test: vscode test config error (#783) 2025-03-29 19:40:59 +08:00
semantic-release-bot
9f4b8ebb00 Release 4.1.5
[skip ci]

## [4.1.5](https://github.com/cnpm/cnpmcore/compare/v4.1.4...v4.1.5) (2025-03-27)

### Bug Fixes

* 🐛 oss config assert ([#780](https://github.com/cnpm/cnpmcore/issues/780)) ([5c132b8](5c132b882b)), closes [#779](https://github.com/cnpm/cnpmcore/issues/779)
2025-03-27 11:23:53 +00:00
AN Meng
5c132b882b fix: 🐛 oss config assert (#780)
 Closes: #779

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

## Summary by CodeRabbit

- **Chores**
- Updated configuration validation to require the OSS bucket setting
instead of a CDN URL, with no changes to public interfaces.

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

Co-authored-by: ANMeng <littleliar.im@qq.com>
2025-03-27 19:22:26 +08:00
fengmk2
a1e1072d1a test: enable more oxlint rules (#778)
- unicorn/prefer-number-properties
- typescript/no-import-type-side-effects
- unicorn/no-array-for-each
- typescript/no-dynamic-delete
- no-empty-function
- import/unambiguous
- max-depth 6
- unicorn/prefer-array-some
- unicorn/prefer-string-slice
- no-lonely-if
- max-nested-callbacks 5
- unicorn/explicit-length-check
- unicorn/no-lonely-if
- no-negated-condition
- no-else-return
- unicorn/prefer-date-now
- typescript/prefer-ts-expect-error
- typescript/ban-ts-comment
- no-throw-literal
- typescript/prefer-enum-initializers
- unicorn/no-typeof-undefined

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

- **Chores**  
- Upgraded dependency version of `oxlint` to `^0.16.0` for improved code
quality.

- **Refactor**  
- Streamlined internal logic for error handling, string manipulation,
and data parsing, enhancing maintainability and performance.
- Simplified conditional logic and iteration methods in various services
and controllers for improved clarity.
- Updated import statements for type clarity and consistency across the
codebase.

- **Tests**  
- Improved test assertions and iteration methods to provide clearer
verification of system reliability.
- Enhanced logging functionality in test cases for better visibility of
output and error handling.
- Modified assertions to ensure more precise requirements in test cases.

These behind-the-scenes enhancements contribute to a more robust and
stable application, ensuring a smoother experience for end-users.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-03-18 23:48:00 +08:00
fengmk2
5d5f40ba26 chore: more oxlint rules (#774) 2025-03-18 00:38:32 +08:00
semantic-release-bot
b322f2c9ec Release 4.1.4
[skip ci]

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

### Bug Fixes

* use tegg beta ([#776](https://github.com/cnpm/cnpmcore/issues/776)) ([dd5ee4f](dd5ee4ff30))
2025-03-16 02:50:34 +00:00
fengmk2
dd5ee4ff30 fix: use tegg beta (#776)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Chores**
- Updated dependency versioning where specific beta numbers were
replaced with a broader beta designation. This change streamlines
dependency management and supports smoother integrations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-03-16 10:49:07 +08:00
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
semantic-release-bot
0a5b0c78d5 Release 3.20.3
[skip ci]

## [3.20.3](https://github.com/cnpm/npmcore/compare/v3.20.2...v3.20.3) (2023-05-18)

### Bug Fixes

* unpublish idempotent ([#466](https://github.com/cnpm/npmcore/issues/466)) ([2a7eacf](2a7eacf27c))
2023-05-18 02:33:25 +00:00
elrrrrrrr
2a7eacf27c fix: unpublish idempotent (#466)
> Fixed the idempotent issue during unpublish pkg, which caused repeated
triggering of change events and endless sync loops for downstream
registries.
* 🐞 Add idempotent check during unpublish; skip when the package has
already been unpublished.
-------------
> 修复 unpublish 时未做幂等控制,导致删包时,不断触发 change 事件,下游 registry 不断 sync 导致任务循环
* 🐞 统一在 unpublish 进行幂等判断,如果该包已 unpublish,则跳过
2023-05-18 10:32:18 +08:00
fengmk2
33628ccacf test: use TypeScript v5 (#425) 2023-05-07 14:54:04 +08:00
semantic-release-bot
e7f5e24b34 Release 3.20.2
[skip ci]

## [3.20.2](https://github.com/cnpm/npmcore/compare/v3.20.1...v3.20.2) (2023-05-06)

### Bug Fixes

* set cache-control default value to "public, max-age=300" ([#462](https://github.com/cnpm/npmcore/issues/462)) ([adda725](adda72566d))
2023-05-06 10:51:15 +00:00
fengmk2
adda72566d fix: set cache-control default value to "public, max-age=300" (#462)
follow registry.npmjs.org default value
2023-05-06 18:50:04 +08:00
semantic-release-bot
42939e99d7 Release 3.20.1
[skip ci]

## [3.20.1](https://github.com/cnpm/npmcore/compare/v3.20.0...v3.20.1) (2023-05-06)

### Bug Fixes

* use nfs download api ([#461](https://github.com/cnpm/npmcore/issues/461)) ([bb16957](bb169577e2))
2023-05-06 08:32:39 +00:00
fengmk2
bb169577e2 fix: use nfs download api (#461) 2023-05-06 16:31:25 +08:00
semantic-release-bot
0858efbc11 Release 3.20.0
[skip ci]

## [3.20.0](https://github.com/cnpm/npmcore/compare/v3.19.3...v3.20.0) (2023-05-06)

### Features

* enable sql logger ([#460](https://github.com/cnpm/npmcore/issues/460)) ([51cd044](51cd044742))
2023-05-06 06:42:49 +00:00
fengmk2
51cd044742 feat: enable sql logger (#460)
And fix registry error response cause data return null bug
2023-05-06 14:41:20 +08:00
fengmk2
a647317c2c chore: update contributors
[skip ci]
2023-05-06 12:40:48 +08:00
semantic-release-bot
098277e274 Release 3.19.3
[skip ci]

## [3.19.3](https://github.com/cnpm/npmcore/compare/v3.19.2...v3.19.3) (2023-05-06)

### Bug Fixes

* ignore hidden dir files ([#459](https://github.com/cnpm/npmcore/issues/459)) ([637e8ad](637e8ad9a0))
2023-05-06 04:38:37 +00:00
fengmk2
637e8ad9a0 fix: ignore hidden dir files (#459)
avoid oss upload fail

> [SignatureDoesNotMatchError]: The request signature we calculated does
not match the signature you provided. Check your key and signing method.
2023-05-06 12:37:23 +08:00
semantic-release-bot
5223e8ca40 Release 3.19.2
[skip ci]

## [3.19.2](https://github.com/cnpm/npmcore/compare/v3.19.1...v3.19.2) (2023-05-05)

### Bug Fixes

* ignore non-file on tar entry filter ([#458](https://github.com/cnpm/npmcore/issues/458)) ([7e63e7f](7e63e7f0eb))
2023-05-05 17:54:58 +00:00
fengmk2
7e63e7f0eb fix: ignore non-file on tar entry filter (#458)
> ENOENT: no such file or directory, stat
'/root/.cnpmcore/downloads/2023/05/06/unpkg_@iov_wallet-providers@1.0.0_0f152162-9cce-4a80-bacc-41271b7aac3f/package'
2023-05-06 01:53:25 +08:00
semantic-release-bot
39b73b18bf Release 3.19.1
[skip ci]

## [3.19.1](https://github.com/cnpm/npmcore/compare/v3.19.0...v3.19.1) (2023-05-05)

### Bug Fixes

* download tgz file to local file before untar it ([#457](https://github.com/cnpm/npmcore/issues/457)) ([90d5046](90d504622a))
2023-05-05 16:25:26 +00:00
fengmk2
90d504622a fix: download tgz file to local file before untar it (#457)
avoid "zlib: unexpected end of file"
2023-05-06 00:24:23 +08:00
semantic-release-bot
5f9a7a8be2 Release 3.19.0
[skip ci]

## [3.19.0](https://github.com/cnpm/npmcore/compare/v3.18.0...v3.19.0) (2023-05-05)

### Features

* support unpkg features ([#456](https://github.com/cnpm/npmcore/issues/456)) ([8ec081a](8ec081acd6))
2023-05-05 15:23:16 +00:00
fengmk2
8ec081acd6 feat: support unpkg features (#456)
WARN: include sql change

😄 Follow unpkg router
😄 Auto sync files after package version add

closes https://github.com/cnpm/cnpmcore/issues/452
2023-05-05 23:22:08 +08:00
semantic-release-bot
23607d9497 Release 3.18.0
[skip ci]

## [3.18.0](https://github.com/cnpm/npmcore/compare/v3.17.1...v3.18.0) (2023-05-05)

### Features

* sync chrome-for-testing binary ([#455](https://github.com/cnpm/npmcore/issues/455)) ([dd7d73e](dd7d73e871))
2023-05-05 08:11:58 +00:00
elrrrrrrr
dd7d73e871 feat: sync chrome-for-testing binary (#455)
> https://github.com/puppeteer/puppeteer/issues/10131 Puppeteer has
updated the default browser to Chrome and added the corresponding
implementation as follows:

🧶 Added a new category `/-/binary/` for Chrome , exp:
`/-/binary/chrome-for-testing/113.0.5672.63/mac-arm64/chrome-mac-arm64.zip`

-----------

> https://github.com/puppeteer/puppeteer/issues/10131 puppeteer
更新了默认浏览器为 chrome,新增对应实现

🧶 `/-/binary/` 新增 chrome binary 分类,
示例链接`/-/binary/chrome-for-testing/113.0.5672.63/mac-arm64/chrome-mac-arm64.zip`
2023-05-05 16:10:50 +08:00
semantic-release-bot
c1fc1a58d4 Release 3.17.1
[skip ci]

## [3.17.1](https://github.com/cnpm/npmcore/compare/v3.17.0...v3.17.1) (2023-05-04)

### Bug Fixes

* calculate _hasShrinkwrap on server-side if not present ([#450](https://github.com/cnpm/npmcore/issues/450)) ([db59bd6](db59bd6cd9))
2023-05-04 08:21:08 +00:00
飞超
db59bd6cd9 fix: calculate _hasShrinkwrap on server-side if not present (#450) 2023-05-04 16:19:55 +08:00
semantic-release-bot
1f592f4b2f Release 3.17.0
[skip ci]

## [3.17.0](https://github.com/cnpm/npmcore/compare/v3.16.0...v3.17.0) (2023-04-25)

### Features

* add source registry name in manifest ([#448](https://github.com/cnpm/npmcore/issues/448)) ([f891aed](f891aedea8))
2023-04-25 08:41:50 +00:00
elrrrrrrr
f891aedea8 feat: add source registry name in manifest (#448)
> Add a private field, _source_registry_name in the version manifest.
* 🧶 Add related types for PackageManifestType and adjust relevant unit
tests.
* 🤖 Update the workflow trigger.
* ♻️ No compensation will be made for the _source_registry_name field in
the existing packageVersion.
-------

> 在 version manifest 中新增私有字段,_source_registry_name 用于标记
* 🧶 新增 PackageManifestType 相关类型,并调整相关单测
* 🤖 调整 workflow 触发时机,不限制 target 分支
* ♻️ 存量 packageVersion 内 _source_registry_name 不做补偿
2023-04-25 16:40:37 +08:00
semantic-release-bot
ae191f3283 Release 3.16.0
[skip ci]

## [3.16.0](https://github.com/cnpm/npmcore/compare/v3.15.0...v3.16.0) (2023-04-21)

### Features

* add health checker for slb ([#445](https://github.com/cnpm/npmcore/issues/445)) ([4dcfe89](4dcfe89575))
2023-04-21 07:49:28 +00:00
fengmk2
4dcfe89575 feat: add health checker for slb (#445)
closes https://github.com/cnpm/cnpmcore/issues/444
2023-04-21 15:48:13 +08:00
semantic-release-bot
e26299a768 Release 3.15.0
[skip ci]

## [3.15.0](https://github.com/cnpm/npmcore/compare/v3.14.0...v3.15.0) (2023-04-21)

### Features

* create sync task with auth header ([#442](https://github.com/cnpm/npmcore/issues/442)) ([d95c58b](d95c58b5ce))
2023-04-21 01:49:29 +00:00
hezhengxu2018
d95c58b5ce feat: create sync task with auth header (#442)
The upstream repository carries authentication header information via
task parameters when alwaysAuth is enabled

---

上游仓库开启 alwaysAuth 时通过任务参数携带认证头信息
2023-04-21 09:48:15 +08:00
semantic-release-bot
59706ab97e Release 3.14.0
[skip ci]

## [3.14.0](https://github.com/cnpm/npmcore/compare/v3.13.2...v3.14.0) (2023-04-20)

### Features

* support granular token ([#443](https://github.com/cnpm/npmcore/issues/443)) ([92ddf2c](92ddf2c8c3))
2023-04-20 07:28:37 +00:00
elrrrrrrr
92ddf2c8c3 feat: support granular token (#443)
> 🚀 Added implementation related to
[granularToken](https://docs.npmjs.com/about-access-tokens#about-granular-access-tokens),
mainly used for web authorization scenarios.

* 📝 Added `1.14.0.sql` to add fields and `token_packages` for
granularToken.
* 🛣️ Added gat related routes, including `create`, `query`, and `delete`
api.
* 🌟 Added `tokenService` to check granularToken access.
* 🔄 Modified Token to perform options and data attribute conversions
internally in the model.
-----------

> 🚀 新增
[granularToken](https://docs.npmjs.com/about-access-tokens#about-granular-access-tokens)
相关实现,主要用于 web 端授权场景
* 📝 新增 `1.14.0.sql` 添加 granularToken 相关字段及 `token_packages` 中间表
* 🛣️ 新增 gat 相关路由,包括`创建`、`查询`、`删除`接口
* 🌟 新增 `tokenService` ,处理 granularToken 鉴权
* 🔄 修改 Token ,在 model 内部进行 options 和 data 属性转换
2023-04-20 15:27:26 +08:00
semantic-release-bot
6961ffb92d Release 3.13.2
[skip ci]

## [3.13.2](https://github.com/cnpm/npmcore/compare/v3.13.1...v3.13.2) (2023-04-10)

### Bug Fixes

* skip tag name out of utf8mb3 ([#440](https://github.com/cnpm/npmcore/issues/440)) ([a64c90b](a64c90b28d))
2023-04-10 08:25:41 +00:00
hezhengxu2018
a64c90b28d fix: skip tag name out of utf8mb3 (#440)
closes https://github.com/cnpm/cnpmcore/issues/438
2023-04-10 16:24:32 +08:00
semantic-release-bot
6177856c9e Release 3.13.1
[skip ci]

## [3.13.1](https://github.com/cnpm/npmcore/compare/v3.13.0...v3.13.1) (2023-04-10)

### Bug Fixes

* update webauth default URL to registry ([#432](https://github.com/cnpm/npmcore/issues/432)) ([cf95d7d](cf95d7dce4))
2023-04-10 06:37:31 +00:00
zhangyuantao
cf95d7dce4 fix: update webauth default URL to registry (#432) 2023-04-10 14:36:05 +08:00
semantic-release-bot
79a5937b74 Release 3.13.0
[skip ci]

## [3.13.0](https://github.com/cnpm/npmcore/compare/v3.12.1...v3.13.0) (2023-04-07)

### Features

* support npm access command ([#436](https://github.com/cnpm/npmcore/issues/436)) ([0ffb614](0ffb61484e))
2023-04-07 04:14:50 +00:00
elrrrrrrr
0ffb61484e feat: support npm access command (#436)
> Supports partial npm access query commands.
https://github.com/cnpm/cnpmcore/issues/64

* The following commands are supported:
  * `npm access list packages [<user>|<scope>|<scope:team> [<package>]`
  * `npm access list collaborators [<package> [<user>]]`
* Added `/-/package/:fullname/collaborators` and
`/-/org/:username/package` interfaces.
* Error code logic is consistent with the npm registry.

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

> 支持部分 npm access 查询命令 https://github.com/cnpm/cnpmcore/issues/64
* 支持如下命令:
  * `npm access list packages [<user>|<scope>|<scope:team> [<package>]`
  * `npm access list collaborators [<package> [<user>]]`
* 新增 `/-/package/:fullname/collaborators` 及 `/-/org/:username/package`
接口
* 错误码逻辑和 npm registry 保持一致
2023-04-07 12:13:27 +08:00
semantic-release-bot
eaf88bdf40 Release 3.12.1
[skip ci]

## [3.12.1](https://github.com/cnpm/npmcore/compare/v3.12.0...v3.12.1) (2023-04-07)

### Bug Fixes

* allow to remove the package entity ([#437](https://github.com/cnpm/npmcore/issues/437)) ([613e0a1](613e0a11db))
2023-04-07 02:35:02 +00:00
fengmk2
613e0a11db fix: allow to remove the package entity (#437)
closes https://github.com/cnpm/cnpmcore/issues/435
2023-04-07 10:33:44 +08:00
semantic-release-bot
570d346657 Release 3.12.0
[skip ci]

## [3.12.0](https://github.com/cnpm/npmcore/compare/v3.11.2...v3.12.0) (2023-04-06)

### Features

* allow admin to sync package only ([#434](https://github.com/cnpm/npmcore/issues/434)) ([c5ac715](c5ac715b2b)), closes [#412](https://github.com/cnpm/npmcore/issues/412)
2023-04-06 04:26:12 +00:00
hezhengxu2018
c5ac715b2b feat: allow admin to sync package only (#434)
closes #412
2023-04-06 12:24:41 +08:00
semantic-release-bot
52a60ca6dd Release 3.11.2
[skip ci]

## [3.11.2](https://github.com/cnpm/npmcore/compare/v3.11.1...v3.11.2) (2023-04-03)

### Bug Fixes

* init sync spec registry ([#433](https://github.com/cnpm/npmcore/issues/433)) ([eedfb2b](eedfb2bb86))
2023-04-03 03:43:55 +00:00
elrrrrrrr
eedfb2bb86 fix: init sync spec registry (#433)
> Fixed the issue where the registry was not correctly matched when
synchronizing scoped packages for the first time
* Add scope params in initSpecRegistry
------------
> 修复初次同步 scope 包,未正确匹配 registry 的问题
* 修改 initSpecRegistry 方法,统一传入 scope 参数
2023-04-03 11:42:35 +08:00
semantic-release-bot
30e9140d6c Release 3.11.1
[skip ci]

## [3.11.1](https://github.com/cnpm/npmcore/compare/v3.11.0...v3.11.1) (2023-03-30)

### Bug Fixes

* timeout handler not work ([#430](https://github.com/cnpm/npmcore/issues/430)) ([3f83808](3f838080ca))
* update login assets cdn url ([#429](https://github.com/cnpm/npmcore/issues/429)) ([4ee410a](4ee410a62e))
2023-03-30 05:12:17 +00:00
elrrrrrrr
3f838080ca fix: timeout handler not work (#430)
> 💥 TaskTimeoutHandler did not have try-catch, the redis lock will cause
all queues to fail when a single task update failed.

* 🛡️ Added try-catch statements in TaskTimeoutHandler.
* 🚧 Restricted updates to the primary key when updating the model in
ModelConvertor.

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

> 💥 TaskTimeoutHandler 未添加 try-catch,且有同步锁,导致单个任务更新异常时,所有队列不生效

* 🛡️ TaskTimeoutHandler 统一添加 try-catch
* 🚧 ModelConvertor 更新模型时,统一限制不允许更新主键
2023-03-30 13:11:01 +08:00
semantic-release-bot
8c6ce1b5b9 Release 3.11.1
[skip ci]

## [3.11.1](https://github.com/cnpm/npmcore/compare/v3.11.0...v3.11.1) (2023-03-28)

### Bug Fixes

* update login assets cdn url ([#429](https://github.com/cnpm/npmcore/issues/429)) ([4ee410a](4ee410a62e))
2023-03-28 09:26:57 +00:00
LiWanglin
4ee410a62e fix: update login assets cdn url (#429)
使用 gw.alipayobjects.com 的CDN源替换 bootcdn
-------------------------------
Update the CDN of JS resource to gw.alipayobjects.com

Co-authored-by: lanxiu.lwl <lanxiu.lwl@alipay.com>
2023-03-28 17:25:40 +08:00
semantic-release-bot
9d66d35a41 Release 3.11.0
[skip ci]

## [3.11.0](https://github.com/cnpm/npmcore/compare/v3.10.0...v3.11.0) (2023-03-27)

### Features

* support webauthn ([#422](https://github.com/cnpm/npmcore/issues/422)) ([1b8512b](1b8512b321))
2023-03-27 10:28:33 +00:00
LiWanglin
1b8512b321 feat: support webauthn (#422)
1. webauth 由 authentication 改造为 session,并增加 web 登录页面,更安全
2. 支持 webauthn 的登录方式,可通过配置控制(默认关闭),更高效

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

1. use session instead http authentication on webauth
2. support [webauthn](https://webauthn.guide/), you should set
`enableWebAuthn: true` in the configuration

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

---------

Co-authored-by: lanxiu.lwl <lanxiu.lwl@alipay.com>
Co-authored-by: elrrrrrrr <elrrrrrrr@gmail.com>
2023-03-27 18:27:07 +08:00
semantic-release-bot
f973c016bc Release 3.10.0
[skip ci]

## [3.10.0](https://github.com/cnpm/npmcore/compare/v3.9.0...v3.10.0) (2023-03-27)

### Features

* redirect not found can be false when syncMode='none' ([#428](https://github.com/cnpm/npmcore/issues/428)) ([91ebd19](91ebd195ce))
2023-03-27 10:14:38 +00:00
hezhengxu2018
91ebd195ce feat: redirect not found can be false when syncMode='none' (#428)
> 在禁止自动创建同步任务时也可以关闭 redirectNotFound,实现在私有化部署时用户仅能使用当前仓库内已有的依赖

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

> Allow to turn off redirectNotFound when disabling the automatic
creation of sync tasks, enabling users to use only existing dependencies
in the current repository when deploying privately.

---------

Co-authored-by: fengmk2 <fengmk2@gmail.com>
2023-03-27 18:13:16 +08:00
semantic-release-bot
069ac68c5e Release 3.9.0
[skip ci]

## [3.9.0](https://github.com/cnpm/npmcore/compare/v3.8.0...v3.9.0) (2023-03-20)

### Features

* redis weak ([#426](https://github.com/cnpm/npmcore/issues/426)) ([300f0e4](300f0e4fd9))
2023-03-20 03:42:14 +00:00
elrrrrrrr
300f0e4fd9 feat: redis weak (#426)
> manifest 读取场景弱依赖 redis
* 添加 try-catch 防止 redis 请求失败导致读取失败
* 读取完成后异步设置缓存
----
> Redis dependency from manifest with weak scene dependence:

* Adds try-catch to prevent reading failure from redis interruption.
* Asynchronously sets cache after reading is complete.
2023-03-20 11:40:56 +08:00
elrrrrrrr
5877f71527 chore: remove ts-node (#418)
> Remove ts-node dependency and do DB initialization via npm scripts
hook and bash script.

* Added prepare-database.sh
* Remove swc & ts-node, since swc `useDefineForClassFields` will cause
leoric create error
[ref](fd438213ad/src/transpilers/swc.ts (L229)),
which can't customize
--------------
> 删除 ts-node 依赖,通过 npm scripts hook 和 bash 来进行 DB 初始化工作

* 新增 prepare-database.sh 处理 db 初始化工作
* 删除 ts-node 及 swc,swc 新版开启 `useDefineForClassFields` 会导致 leoric
创建对象失败,且无法自定义,
[ref](fd438213ad/src/transpilers/swc.ts (L229))一并去除
2023-03-16 17:47:37 +08:00
fengmk2
7ec53b1796 test: use @cnpmjs/npm-cli-login instead of npm-cli-login (#423)
skip snyk download

```
[09:07:45] [4/4] scripts.postinstall npm-cli-login@1.0.0 › snyk@^1.124.1 run "node wrapper_dist/bootstrap.js exec", root: "/root/workspace/npmmirror-registry_main/node_modules/_snyk@1.1117.0@snyk"
[09:07:45] Downloading from 'https://static.snyk.io/cli/v1.1117.0/snyk-linux' to '/root/workspace/npmmirror-registry_main/node_modules/_snyk@1.1117.0@snyk/wrapper_dist/snyk-linux'
```
2023-03-13 09:36:08 +08:00
semantic-release-bot
ae6b2f0d64 Release 3.8.0
[skip ci]

## [3.8.0](https://github.com/cnpm/npmcore/compare/v3.7.0...v3.8.0) (2023-03-08)

### Features

* Support for migrating packages into current registry ([#417](https://github.com/cnpm/npmcore/issues/417)) ([e5f905b](e5f905bd48))
2023-03-08 11:03:09 +00:00
elrrrrrrr
e5f905bd48 feat: Support for migrating packages into current registry (#417)
> Support for migrating packages into current registry
1. 🆕 Add `ensureSelfRegistry` method to initialize the current
configuration to the DB
2. 🧹 Add displayName to hide userPrefix info
3. 🧶 Uniformly determine publish access with `checkPublishAccess` and
`ensurePublishAccess`
--------------
> 支持将包迁移至当前 registry,避免不再进行包同步
1. 🆕  `ensureSelfRegistry` 方法,将当前配置初始化至 DB
2. 🧹 添加 displayName,外部不再展示 userPrefix 信息
3. 🧶 通过 checkPublishAccess 及 ensurePublishAccess 统一判断发布权限
2023-03-08 19:01:43 +08:00
semantic-release-bot
9fa6c961c4 Release 3.7.0
[skip ci]

## [3.7.0](https://github.com/cnpm/npmcore/compare/v3.6.0...v3.7.0) (2023-03-01)

### Features

* retry changes task when current work error ([#414](https://github.com/cnpm/npmcore/issues/414)) ([d7ae7aa](d7ae7aaaf2))
2023-03-01 07:39:42 +00:00
elrrrrrrr
d7ae7aaaf2 feat: retry changes task when current work error (#414)
> 当前请求 changesStream 失败时,需等待 15 分钟超时调度。
* 原 suspendTaskWhenExit 重构为 suspendSync ,支持传入 exit 参数,控制是否继续等待
* 请求 changesStream 失败时,主动挂起任务

------

> Wait 15 minutes for timeout scheduling if the current request
changesStream fails
* `suspendTaskWhenExit` is refactored to `suspendSync`, add exit
parameter to control whether to exiting the queue
* Suspend task when request changesStream fails
2023-03-01 15:38:35 +08:00
semantic-release-bot
74ab0eb908 Release 3.6.0
[skip ci]

## [3.6.0](https://github.com/cnpm/npmcore/compare/v3.5.0...v3.6.0) (2023-02-27)

### Features

* add integrate doc ([#413](https://github.com/cnpm/npmcore/issues/413)) ([a02f8b4](a02f8b45d3))
2023-02-27 01:14:08 +00:00
elrrrrrrr
a02f8b45d3 feat: add integrate doc (#413)
> 新增集成文档,完善 https://github.com/cnpm/cnpmcore/pull/411 SSORequest 文档说明
* 新增 INTEGRATE.md 文档
* README.md 增加对应链接

-----------

> New integration documentation for
https://github.com/cnpm/cnpmcore/pull/411
* Add INTEGRATE.md
* Add link in README.md
2023-02-27 09:12:58 +08:00
semantic-release-bot
ea3a8aa649 Release 3.5.0
[skip ci]

## [3.5.0](https://github.com/cnpm/npmcore/compare/v3.4.3...v3.5.0) (2023-02-21)

### Features

* support webauth infra ([#411](https://github.com/cnpm/npmcore/issues/411)) ([583437a](583437a83e))
2023-02-21 09:45:19 +00:00
elrrrrrrr
583437a83e feat: support webauth infra (#411)
> 基于 https://github.com/cnpm/cnpmcore/pull/380 ,新增 infra 层,允许自定义 authUrl
、新增 SSO 登录方法

* 从 `app/webauth` 移动至 `app/port`,取消独立 module
* 新增 SSORequest 方法,作为 SSO 内置方法
* 新增 authAdapter,因为 npm cli 请求地址是固定的
* 单测补全

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

> New infra layer based on https://github.com/cnpm/cnpmcore/pull/380 ,
allowing custom the authUrl and SSO.

* Moved from `app/webauth` to `app/port`, normlize the controller
* New SSORequest method as SSO preset login
* New authAdapter, since npm cli request addresses are fixed
* TestCase updated

![image](https://user-images.githubusercontent.com/5574625/220271869-0b4d96c6-0d89-499e-9c74-eff2727749cb.png)

---------

Co-authored-by: fengmk2 <fengmk2@gmail.com>
2023-02-21 17:43:31 +08:00
fengmk2
4f1555a7f2 test: improve coverage (#410) 2023-02-16 11:20:16 +08:00
fengmk2
9b3352847c deps: use ioredis v5 typings (#409) 2023-02-15 22:17:50 +08:00
semantic-release-bot
5e95781a0c Release 3.4.3
[skip ci]

## [3.4.3](https://github.com/cnpm/npmcore/compare/v3.4.2...v3.4.3) (2023-02-15)

### Bug Fixes

* changesStream suspend ([#408](https://github.com/cnpm/npmcore/issues/408)) ([2c821ea](2c821eaa64))
2023-02-15 05:38:23 +00:00
elrrrrrrr
2c821eaa64 fix: changesStream suspend (#408)
> https://github.com/cnpm/cnpmcore/pull/367 tegg-v3 升级后,service 改为
Singleton,无法通过 ctx 上下文获取
* 修改 app 内 Service 获取方式
* 移除废弃的 typing.ts 声明

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

> https://github.com/cnpm/cnpmcore/pull/367 Service has been refactored
to Singleton in tegg-v3 , which can't be inited with context anymore.
* Update Service init logic in appHook
* Remove the deprecated typing.ts


![image](https://user-images.githubusercontent.com/5574625/218919092-4a6b7353-7234-47f4-af99-9bf16846c2f1.png)
2023-02-15 13:37:12 +08:00
fengmk2
8964d7074d test: use ts-node with swc (#407)
https://typestrong.org/ts-node/docs/swc/
2023-02-14 22:37:14 +08:00
semantic-release-bot
18ed51e88e Release 3.4.2
[skip ci]

## [3.4.2](https://github.com/cnpm/npmcore/compare/v3.4.1...v3.4.2) (2023-02-14)

### Bug Fixes

* distinct processing task ([#406](https://github.com/cnpm/npmcore/issues/406)) ([c43c067](c43c067211))
2023-02-14 08:19:33 +00:00
elrrrrrrr
c43c067211 fix: distinct processing task (#406)
> 相同任务并发执行时,如果上游有 changesStream 事件,可能会导致版本被错误删除。
* 撤销 https://github.com/cnpm/cnpmcore/pull/352 的变更,我们已在
https://github.com/cnpm/cnpmcore/pull/361 中解决了事件实时性问题

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

> Concurrent execution of the same task with changesStream events may
cause versions to be deleted incorrectly.
* revert https://github.com/cnpm/cnpmcore/pull/352, since we have fixed
in https://github.com/cnpm/cnpmcore/pull/361
2023-02-14 16:18:17 +08:00
semantic-release-bot
97ca612bf1 Release 3.4.1
[skip ci]

## [3.4.1](https://github.com/cnpm/npmcore/compare/v3.4.0...v3.4.1) (2023-02-13)

### Bug Fixes

* forbidden non-ascii binary subpath ([#405](https://github.com/cnpm/npmcore/issues/405)) ([7b52f6f](7b52f6f303))
2023-02-13 13:44:15 +00:00
fengmk2
7b52f6f303 fix: forbidden non-ascii binary subpath (#405)
closes https://github.com/cnpm/cnpmcore/issues/395
2023-02-13 21:43:07 +08:00
fengmk2
f7344eb90f test: use 127.0.0.1 instead of localhost to connect local db (#404) 2023-02-13 09:54:09 +08:00
fengmk2
1c24c49c0b test: use egg-bin v6 (#403)
https://github.com/eggjs/egg-bin/pull/217
2023-02-12 23:58:38 +08:00
fengmk2
6b1a92dbf6 test: add vscode debug config (#402) 2023-02-12 09:16:09 +08:00
semantic-release-bot
045615d25d Release 3.4.0
[skip ci]

## [3.4.0](https://github.com/cnpm/npmcore/compare/v3.3.2...v3.4.0) (2023-02-10)

### Features

* sync delete mode ([#398](https://github.com/cnpm/npmcore/issues/398)) ([27af0be](27af0beaad))
2023-02-10 13:32:47 +00:00
elrrrrrrr
27af0beaad feat: sync delete mode (#398)
> 为了避免部分 npm 包误封、误删,导致生产环境影响,新增 syncDeleteMode 配置,允许自定义同步策略

* 新增 `syncDeleteMode` : 'ignore' | 'block' | 'delete'
  * delete: 目前默认值,同步删包事件
  * ignore: 忽略 upstream 所有删包事件
  * block: 不做物理删除,只新增 block 记录,不允许访问,除非管理员手动恢复并更新 `syncPackageBlockList`
* `npm-security-holder` 场景也判断为删包事件
* 更新原有删包流程,统一处理,调整部分日志输出

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

> New `syncDeleteMode` to allow custom syncing policy to avoid some npm
packages being blocked or deleted by mistake.

* Add `syncDeleteMode` : 'ignore' | 'block' | 'delete'
  * delete: by default, sync delete events
  * ignore: ignore all upstream delete events
* block: only add block records, cant access unless the administrator
manually restores and update `syncPackageBlockList`.
* `npm-security-holder` event is also determined to be a delete event
* Update the original packet deletion process, update log output by the
way
2023-02-10 21:31:23 +08:00
semantic-release-bot
18cfb0d35a Release 3.3.2
[skip ci]

## [3.3.2](https://github.com/cnpm/npmcore/compare/v3.3.1...v3.3.2) (2023-02-10)

### Reverts

* Revert "fix: should sync package deps by default (#400)" (#401) ([b021e1e](b021e1ebc3)), closes [#400](https://github.com/cnpm/npmcore/issues/400) [#401](https://github.com/cnpm/npmcore/issues/401)
2023-02-10 01:07:56 +00:00
fengmk2
b021e1ebc3 Revert "fix: should sync package deps by default (#400)" (#401)
This reverts commit 282abf6920.
2023-02-10 09:06:43 +08:00
semantic-release-bot
2c679bec5c Release 3.3.1
[skip ci]

## [3.3.1](https://github.com/cnpm/npmcore/compare/v3.3.0...v3.3.1) (2023-02-10)

### Bug Fixes

* should sync package deps by default ([#400](https://github.com/cnpm/npmcore/issues/400)) ([282abf6](282abf6920))
2023-02-10 00:51:53 +00:00
fengmk2
282abf6920 fix: should sync package deps by default (#400) 2023-02-10 08:50:38 +08:00
semantic-release-bot
144f1b3a40 Release 3.3.0
[skip ci]

## [3.3.0](https://github.com/cnpm/npmcore/compare/v3.2.6...v3.3.0) (2023-02-09)

### Features

* auto sync package's optionalDependencies ([#399](https://github.com/cnpm/npmcore/issues/399)) ([07a19cf](07a19cfd1d))
2023-02-09 15:40:09 +00:00
fengmk2
07a19cfd1d feat: auto sync package's optionalDependencies (#399)
closes https://github.com/cnpm/cnpmcore/issues/397
2023-02-09 23:38:52 +08:00
fengmk2
db8995a2ab refactor: use Symbol instead of const on decorator attribute (#396) 2023-02-06 13:15:16 +08:00
fengmk2
cfc373c87a refactor: keep ignoreDownloadStatuses as number[] (#394) 2023-02-05 21:52:42 +08:00
semantic-release-bot
baa01835b3 Release 3.2.6
[skip ci]

## [3.2.6](https://github.com/cnpm/npmcore/compare/v3.2.5...v3.2.6) (2023-02-05)

### Bug Fixes

* should init binary adapter before reuse it ([#393](https://github.com/cnpm/npmcore/issues/393)) ([b9985ab](b9985ab166))
2023-02-05 03:35:31 +00:00
fengmk2
b9985ab166 fix: should init binary adapter before reuse it (#393) 2023-02-05 11:34:10 +08:00
semantic-release-bot
1c7feb7d11 Release 3.2.5
[skip ci]

## [3.2.5](https://github.com/cnpm/npmcore/compare/v3.2.4...v3.2.5) (2023-02-03)

### Bug Fixes

* allow publish 10mb tarball package by default ([#391](https://github.com/cnpm/npmcore/issues/391)) ([f873b8d](f873b8d3e4))
2023-02-03 14:18:19 +00:00
fengmk2
f873b8d3e4 fix: allow publish 10mb tarball package by default (#391)
closes https://github.com/cnpm/cnpmcore/issues/388
2023-02-03 22:17:07 +08:00
semantic-release-bot
09a66d1d07 Release 3.2.4
[skip ci]

## [3.2.4](https://github.com/cnpm/npmcore/compare/v3.2.3...v3.2.4) (2023-02-02)

### Bug Fixes

* skip download exists binary file ([#389](https://github.com/cnpm/npmcore/issues/389)) ([f4f40ed](f4f40edf43))
2023-02-02 07:55:11 +00:00
fengmk2
f4f40edf43 fix: skip download exists binary file (#389) 2023-02-02 15:54:07 +08:00
elrrrrrrr
84eff97870 refactor: Restrict binaryName types (#387)
> restrict binaryName type , the single source is the `config/binary.js`
file.

* export `BinaryName` & `CategoryName` type
* use `BinaryNameRule` typebox validator in controller
* `binaryName: string` => `binaryName: BinaryName`
2023-02-01 16:44:51 +08:00
semantic-release-bot
1bcc169e93 Release 3.2.3
[skip ci]

## [3.2.3](https://github.com/cnpm/npmcore/compare/v3.2.2...v3.2.3) (2023-01-30)

### Bug Fixes

* config path ([#385](https://github.com/cnpm/npmcore/issues/385)) ([ab72a3b](ab72a3bb8e))
2023-01-30 02:18:18 +00:00
elrrrrrrr
ab72a3bb8e fix: config path (#385)
Fix partial `config/binaries` file path in binary.
Prevent js parsing issues when cnpmcore is required as an npm module.
This is the part that was missed in the previous pr .
https://github.com/cnpm/cnpmcore/pull/384
2023-01-30 10:17:02 +08:00
semantic-release-bot
aff453ad8b Release 3.2.2
[skip ci]

## [3.2.2](https://github.com/cnpm/npmcore/compare/v3.2.1...v3.2.2) (2023-01-29)

### Bug Fixes

* import path ([#384](https://github.com/cnpm/npmcore/issues/384)) ([750ef60](750ef6092e))
2023-01-29 12:24:38 +00:00
elrrrrrrr
750ef6092e fix: import path (#384) 2023-01-29 20:23:33 +08:00
semantic-release-bot
17df8ecab5 Release 3.2.1
[skip ci]

## [3.2.1](https://github.com/cnpm/npmcore/compare/v3.2.0...v3.2.1) (2023-01-29)

### Bug Fixes

* api binary host config ([#383](https://github.com/cnpm/npmcore/issues/383)) ([8a2415f](8a2415f5a7))
2023-01-29 03:15:57 +00:00
elrrrrrrr
8a2415f5a7 fix: api binary host config (#383) 2023-01-29 11:14:39 +08:00
semantic-release-bot
4884e9f50a Release 3.2.0
[skip ci]

## [3.2.0](https://github.com/cnpm/npmcore/compare/v3.1.2...v3.2.0) (2023-01-28)

### Features

* update index json ([#379](https://github.com/cnpm/npmcore/issues/379)) ([bce6e79](bce6e7971f))
2023-01-28 07:00:51 +00:00
elrrrrrrr
bce6e7971f feat: update index json (#379)
> 多同步源方案之后,原有 srouce_registry 配置仅初始化时消费, 更新 / 状态信息相关字段

1. 使用 `information_schema` 替换 id 计算,解决部分 db id 自增不连续的问题
2. 添加 upstream_registries 列表,返回对应 changesStreamTaskData 以及 registry 信息
3. ~~source_registry~~ , ~~changes_stream_registry~~,
~~sync_changes_steam~~ 标记为 Legacy 字段,暂不移除
4. 新增 rawQueryUtil 处理 getCount 类型查询逻辑
2023-01-28 14:59:40 +08:00
semantic-release-bot
68edfb500d Release 3.1.2
[skip ci]

## [3.1.2](https://github.com/cnpm/npmcore/compare/v3.1.1...v3.1.2) (2023-01-28)

### Bug Fixes

* binary path ([#381](https://github.com/cnpm/npmcore/issues/381)) ([790621b](790621b4b9))
2023-01-28 03:27:38 +00:00
elrrrrrrr
790621b4b9 fix: binary path (#381)
> 目前逻辑会解析出 `
https://skia-canvas.s3.us-east-1.amazonaws.com/v0.9.24/linux-arm64-{node_napi_label}.tar.gz`
会导致任务多次失败重试

* 添加 if/else 判断兼容 `{platform}-{arch}-{node_napi_label}` 
* 临时兼容,后续通过变量替换实现
2023-01-28 11:25:57 +08:00
elrrrrrrr
dd4fe23419 refactor: re-org binary apdater (#378)
> 使用单例动态注入的方式重构 BinaryAdapter,实现类不再依赖上下文参数

1. Adapter 定义统一通过 `@BinaryAdapter(BinaryType.xx)` 定义,去除构造函数
2. 统一 `fetch(dir: string, bianryName?: string)` 接口定义,涉及 config 及
binaryTaskConfig 逻辑由实现类内部实现
3. 新增 `BinarySyncerService#getBinaryAdapter` 根据 binaryName 实例化对应
binaryAdapter
2023-01-21 14:30:06 +08:00
semantic-release-bot
56fa53c566 Release 3.1.1
[skip ci]

## [3.1.1](https://github.com/cnpm/npmcore/compare/v3.1.0...v3.1.1) (2023-01-18)

### Bug Fixes

* not exists binary should return 404 ([#377](https://github.com/cnpm/npmcore/issues/377)) ([0cc348d](0cc348dd6e))
2023-01-18 14:16:31 +00:00
fengmk2
0cc348dd6e fix: not exists binary should return 404 (#377)
closes https://github.com/cnpm/cnpmcore/issues/376
2023-01-18 22:15:22 +08:00
fengmk2
7952e33152 chore: update contributors
[skip ci]
2023-01-18 21:41:56 +08:00
semantic-release-bot
b0878e4107 Release 3.1.0
[skip ci]

## [3.1.0](https://github.com/cnpm/npmcore/compare/v3.0.1...v3.1.0) (2023-01-18)

### Features

* support auto sync when package not found ([#337](https://github.com/cnpm/npmcore/issues/337)) ([8734413](873441374f)), closes [#335](https://github.com/cnpm/npmcore/issues/335) [/github.com/cnpm/cnpmcore/pull/50/files#diff-97cbafa75ed0bae6a1f0a2df0676c00f56b9cf8944b04ddb82d6dd0ab141961](https://github.com/cnpm//github.com/cnpm/cnpmcore/pull/50/files/issues/diff-97cbafa75ed0bae6a1f0a2df0676c00f56b9cf8944b04ddb82d6dd0ab141961)
2023-01-18 01:59:35 +00:00
laoboxie
873441374f feat: support auto sync when package not found (#337)
1、修复 `syncMode = exist` 同步包定时任务不执行的问题
2、支持包不存在时自动同步和重定向到 sourceRegistry 的功能 close #335 ,通过配置 `syncNotFound = true` 开启

参考:

1、https://github.com/cnpm/cnpmcore/pull/50/files#diff-97cbafa75ed0bae6a1f0a2df0676c00f56b9cf8944b04ddb82d6dd0ab141961f
2、https://github.com/cnpm/cnpmjs.org/blob/master/middleware/sync_by_install.js

Co-authored-by: fengmk2 <fengmk2@gmail.com>
2023-01-18 09:58:24 +08:00
semantic-release-bot
23bc3b20f6 Release 3.0.1
[skip ci]

## [3.0.1](https://github.com/cnpm/npmcore/compare/v3.0.0...v3.0.1) (2023-01-18)

### Bug Fixes

* try to show latest version on sync log ([#375](https://github.com/cnpm/npmcore/issues/375)) ([1c64a57](1c64a57dbe))
2023-01-18 01:36:28 +00:00
fengmk2
1c64a57dbe fix: try to show latest version on sync log (#375) 2023-01-18 09:35:10 +08:00
semantic-release-bot
d6b35caa0e Release 3.0.0
[skip ci]

## [3.0.0](https://github.com/cnpm/npmcore/compare/v2.10.1...v3.0.0) (2023-01-17)

### ⚠ BREAKING CHANGES

* use SingletonProto instead of ContextProto

Co-authored-by: killagu <killa123@126.com>

### Code Refactoring

* use tegg v3 ([#370](https://github.com/cnpm/npmcore/issues/370)) ([8e3acae](8e3acaead9))
2023-01-17 15:38:50 +00:00
fengmk2
8e3acaead9 refactor: use tegg v3 (#370)
BREAKING CHANGE: use SingletonProto instead of ContextProto

Co-authored-by: killagu <killa123@126.com>
2023-01-17 23:37:38 +08:00
semantic-release-bot
fff032b1e8 Release 2.10.1
[skip ci]

## [2.10.1](https://github.com/cnpm/npmcore/compare/v2.10.0...v2.10.1) (2023-01-08)

### Bug Fixes

* export _cnpmcore_publish_time on abbreviated manifests ([#374](https://github.com/cnpm/npmcore/issues/374)) ([4bceac5](4bceac5a4c))
2023-01-08 10:41:00 +00:00
fengmk2
4bceac5a4c fix: export _cnpmcore_publish_time on abbreviated manifests (#374) 2023-01-08 18:39:36 +08:00
fengmk2
e09cdad6ec test: run tsc prod on ci (#373) 2023-01-05 22:27:11 +08:00
semantic-release-bot
6384229a53 Release 2.10.0
[skip ci]

## [2.10.0](https://github.com/cnpm/npmcore/compare/v2.9.1...v2.10.0) (2023-01-05)

### Features

* unpublish pkg when upstream block ([#372](https://github.com/cnpm/npmcore/issues/372)) ([7e419c1](7e419c1fb4))
2023-01-05 12:36:22 +00:00
elrrrrrrr
7e419c1fb4 feat: unpublish pkg when upstream block (#372)
> https://registry.npmmirror.com/chalk-next 

如果上游 cnpmcore registry block 包时,下游同步时目前会同步失败,导致包无法自动废弃

* 上游包 unblock 时,下游包应当 unpublish 处理,follow upstream 操作
2023-01-05 20:34:57 +08:00
fengmk2
bda3f1caf4 test: remove tsconfig-paths/register (#369)
https://github.com/eggjs/egg-bin/pull/199
2022-12-19 02:24:59 +08:00
fengmk2
e76885847c test: use redis image (#368) 2022-12-19 00:49:54 +08:00
semantic-release-bot
32d5084fdc Release 2.9.1
[skip ci]

## [2.9.1](https://github.com/cnpm/npmcore/compare/v2.9.0...v2.9.1) (2022-12-17)

### Bug Fixes

* Auto enable npm publish on github action ([3d366dd](3d366dd996))
* fix tsc:prod ([ca78d00](ca78d00f28))
2022-12-17 10:43:53 +00:00
fengmk2
f2055a355f chore: fix branches config 2022-12-17 18:08:57 +08:00
fengmk2
3d366dd996 fix: Auto enable npm publish on github action 2022-12-17 18:04:49 +08:00
fengmk2
6fcc5c6dab Update release.yml 2022-12-16 23:35:58 +08:00
fengmk2
b761a8f4eb Update release.yml 2022-12-16 23:32:38 +08:00
fengmk2
65a8d1d324 Create release.yml 2022-12-16 23:29:46 +08:00
fengmk2
57515de719 🐛 FIX: Use runInAnonymousContextScope instead (#367) 2022-12-16 21:58:55 +08:00
killagu
ca78d00f28 fix: fix tsc:prod 2022-12-15 22:32:11 +08:00
killagu
ea84da989f Release 2.9.0 2022-12-15 22:24:46 +08:00
elrrrrrrr
c562645db7 feat: suspend task before app close (#365)
> 机器计划内重启时,需要等待超时后再重新 retry 恢复 changesStream
同步,新增任务挂起机器,应用退出前,挂起当前机器正在执行的同步任务

* 新增 taskRepository#findTaskByAuthorIpAndType 方法,查找所有当前机器所有 worker 同步的任务
* 新增 module.d.ts 定义,目前仅消费 cnpmcoreCore module 内的 changesStream 方法
* app.ts 内调用 changesStreamService#suspendTaskWhenExit 在应用退出前触发

Co-authored-by: killa <killa123@126.com>
2022-12-15 21:34:24 +08:00
killagu
eb04533714 Release 2.8.1 2022-12-05 14:07:01 +08:00
fengmk2
7bc0fccaca 🤖 TEST: Fix async function mock 2022-12-03 15:03:19 +08:00
fengmk2
84ae9bcfa0 📖 DOC: Update contributors 2022-12-03 15:00:06 +08:00
laibao101
fad30adc56 feat: npm command support npm v6 (#356)
有很多比较久远的包,对 node 版本有限制。最高只能用 node14 npm6。 会导致 npm owner
操作的时候报错。原因是npm6的请求里面 header 没有 npm-command 参数。 增加兼容性。

Co-authored-by: Nice ZHOU 周华 <nice.zhou@nio.com>
2022-12-03 14:59:08 +08:00
elrrrrrrr
f961219dbe fix: Sync save ignore ER_DUP_ENTRY error (#364)
> 2个同步任务并发执行时,可能出现查询时未写入版本,写入时冲突,导致未正常更新 pkg.manifests

* 识别写入异常场景,由于 version 写入已添加 nfs 和 关联数据事务,可以直接放入 updatedVersions 数组进行
manifests 更新
* dist-tag 添加校验逻辑,对于 dist-tag 的更新,必须确保存量 manifests 或本次同步增量的 versions
包含对应版本,否则跳过
2022-12-03 14:49:29 +08:00
fengmk2
c02010f2e5 Release 2.8.0 2022-11-29 00:07:21 +08:00
elrrrrrrr
d55c680ef9 Event cork (#361)
> syncPackage 同步时,由于任务并发,可能会导致同步过程中 versions 表记录已经创建,pkg.manifests 还没有同步
> 针对这种场景做补偿逻辑,防止 tag 打在一个 pkg.manifests 没有的版本里

* 修改 pkg.manifests 补偿逻辑,兼容有 versions 没 pkg.manifests 的情况
* 添加 eventCork 的 advice,在 syncPackage 任务结束后,再统一触发 changes,依赖
[ref](https://github.com/eggjs/tegg/pull/60)

在同步和被同步的场景,确保 changes 发出时,pkg.manifests 已经更新
统一 ctx 内不同 changes 时序可能影响,不影响重新读取 manifests 一致性
2022-11-28 23:59:03 +08:00
fengmk2
c1eb0978ba Release 2.7.1 2022-11-25 21:35:53 +08:00
Ke Wu
c6b8aecfd0 fix: request binary error (#360)
Co-authored-by: 天玎 <tianding.wk@antgroup.com>
2022-11-25 21:34:53 +08:00
fengmk2
32e842e882 Release 2.7.0 2022-11-25 18:26:14 +08:00
Ke Wu
5738d569ea refactor: binary sync task use binaryName by default (#358)
1. 默认使用 config/binaries 的 binaryName 创建同步任务;
2. config/binaries 中的 category 为组合不同 binary 数据的配置。默认跟 binaryName 保持一致;
3. 当 category 跟 binaryName 不一致时,合并 binaryName 和 category 两个二进制数据信息。

以 canvas 和 node-canvas-prebuilt 为例:
1. 创建同步任务时,分别同步各自的数据;
2. 查询二进制数据时,由于 canvas 中的 category 配置为 node-canvas-prebuilt,这时候会合并 canvas
和 node-canvas-prebuilt 两个 binary(binaryName)的数据并返回。

这个重构删除 mergeCategory 字段,使得配置数据更加精简。

Co-authored-by: 天玎 <tianding.wk@antgroup.com>
2022-11-25 18:25:23 +08:00
fengmk2
9dd2d4bbe4 Release 2.6.1 2022-11-23 18:34:56 +08:00
fengmk2
0b35ead2a0 🐛 FIX: typo for canvas 2022-11-23 18:34:22 +08:00
fengmk2
a64ebd80f3 Release 2.6.0 2022-11-23 18:16:50 +08:00
Ke Wu
be8387dfa4 feat: Support canvas sync from different binary (#357)
Co-authored-by: 天玎 <tianding.wk@antgroup.com>
2022-11-23 18:15:51 +08:00
elrrrrrrr
d6c4cf5029 fix: duplicate binary task (#354)
> syncBinary 目前会通过定时任务单机每天创建,导致多实例冲突
> 其他任务类型均通过事件触发不受影响
* 创建 syncBinary 任务时,手动去重
* 添加 bizId 参数 进行兜底
2022-11-12 22:33:42 +08:00
fengmk2
0ada89b2fc Release 2.5.2 2022-11-11 18:32:40 +08:00
elrrrrrrr
7eb209de13 fix: create task when waiting (#352)
> 下游同步 cnpmcore 项目时,cnpmcore 会同时发送 [version, tag] 两个独立的事件
有可能下游在处理 version change 时,tag 尚未创建完成
但由于 task targetName 幂等,导致 tag 的 change 事件未响应,造成 tag 异常

* 创建同步任务时进行判断,如果任务还未开始才放弃创建
2022-11-11 18:31:53 +08:00
killagu
5965dbddbc Release 2.5.1 2022-11-07 10:09:07 +08:00
fengmk2
e40c5021bb 🐛 FIX: Mirror cypress arm64 binary (#351) 2022-11-06 18:46:17 +08:00
killagu
65a3df891d Release 2.5.0 2022-11-04 16:02:50 +08:00
elrrrrrrr
43d77ee91e feat: long description (#349)
目前 db 限制 pkg.description 长度为 10k,cnpmjs.org 为 longtext,可能导致不兼容。

* 长 description 场景,截断字符保存,防止创建 pkg 失败
2022-11-04 14:12:58 +08:00
fengmk2
3e7a434d19 Release 2.4.1 2022-10-28 00:12:00 +08:00
elrrrrrrr
28eeeafd98 fix: registry host config (#346)
closes #343
2022-10-28 00:03:10 +08:00
fengmk2
92350a8643 👌 IMPROVE: Show changes stream create task log (#347) 2022-10-27 22:11:37 +08:00
fengmk2
cd5bd923b8 🐛 FIX: Catch all error on changes stream handler (#344) 2022-10-25 23:26:40 +08:00
fengmk2
b787f36a75 Release 2.4.0 2022-10-25 10:08:45 +08:00
elrrrrrrr
b19b0a0496 fix: Lazy set registryId when executeTask (#341)
> packageSyncer#executeTask 时,根据 scope 和 data.registryId 更新
pkg.registryId 配置

解决问题:
1. 例如 @cnpm 配置特定 registry,当 @cnpm/a 依赖了 @cnpm/b 时,@cnpm/b 同步任务执行异常
2. 存量 registryId 为 null 的 package 无时机更新 registryId
3. 存量 registryId 为 null ,手动触发 sync 任务时,不支持传入 registryName: default
2022-10-25 10:07:50 +08:00
fengmk2
6aa302d074 📦 NEW: Use oss-cnpm v4 (#340)
https://github.com/cnpm/oss-cnpm/pull/28
2022-10-23 20:48:12 +08:00
fengmk2
305175ab5f 🤖 TEST: Use enum define on unittest (#333) 2022-10-16 20:22:23 +08:00
fengmk2
07f2eba137 🤖 TEST: Mock all binary http requests (#328) 2022-10-07 23:59:05 +08:00
fengmk2
4b0c7dc619 🤖 TEST: Mock all httpclient request (#327) 2022-10-07 01:14:56 +08:00
fengmk2
a217fd07cc 👌 IMPROVE: Reduce warning log (#326) 2022-10-06 19:08:43 +08:00
fengmk2
8b5ece2ba9 Release 2.3.1 2022-10-06 12:48:04 +08:00
fengmk2
d79634eea7 🐛 FIX: Should sync package when registry id is null (#324) 2022-10-06 12:46:53 +08:00
fengmk2
24f920d65b 🐛 FIX: Should run sync package on all worker (#323)
0d8a667b3f/app/schedule/SyncPackageWorker.ts (L10)
2022-10-02 22:25:06 +08:00
fengmk2
bbc08fd268 👌 IMPROVE: syncPackageWorkerMaxConcurrentTasks up to 20 (#322) 2022-10-02 21:38:54 +08:00
zhangyuantao
5852f22023 feat: support sync exist mode (#275)
添加`syncMode:
exist`的支持,参考:https://github.com/cnpm/cnpmjs.org/blob/master/sync/sync_exist.js
的实现

注意事项:建议企业内部使用时,关闭 `enableChangesStream`,同时也关闭
`enableCheckRecentlyUpdated`。手工同步一些常见的包后,再启用 `syncMode:
exist`,这样包的数量可控,也能保障包的状态是最新的
2022-09-30 14:57:01 +08:00
fengmk2
c2acd3b6cc Release 2.3.0 2022-09-24 19:37:17 +08:00
fengmk2
bd83a19eca 👌 IMPROVE: use urllib3 instead (#302)
https://github.com/eggjs/egg/issues/4847
2022-09-24 19:36:09 +08:00
fengmk2
35e7d3ad3c 👌 IMPROVE: Enable phpmyadmin and DEBUG_LOCAL_SQL by default (#320)
```
2022-09-24 09:06:04,812 INFO 1793 [-/127.0.0.1/10567ea0-3ba5-11ed-a8c6-6f52da8076ba/2.946ms GET /foo] [Tracing] auth: 0, npm-command: -, referer: -, user-agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
[sql-debug] [20.193ms] SELECT * FROM `packages` WHERE `scope` = '' AND `name` = 'foo' LIMIT 1
```

![image](https://user-images.githubusercontent.com/156269/192073800-dc5dd934-6f81-4868-a0c6-778e6111d402.png)
2022-09-24 10:08:35 +08:00
fengmk2
b91a550644 Release 2.2.0 2022-09-22 12:11:02 +08:00
fengmk2
e72ce3576f 🤖 TEST: Skip unstable tests (#318) 2022-09-22 12:10:09 +08:00
killa
7e9beead57 fix: only append / if path is not empty and not ends with / (#316) 2022-09-22 09:44:36 +08:00
killa
bca0fb3c37 feat: only allow pkg sync from registry it belong (#317) 2022-09-22 09:44:16 +08:00
fengmk2
171b11f7bb Revert "fix: fix directory path (#313)"
This reverts commit 4fe68cbf38.
2022-09-10 15:06:45 +08:00
killa
4fe68cbf38 fix: fix directory path (#313) 2022-09-08 15:40:37 +08:00
killagu
6e7573c8b3 Release 2.1.1 2022-09-08 13:29:09 +08:00
killa
8fb9dd8cf4 fix: findExecuteTask only return waiting task (#312)
If multi instance access queue may return
same task id, update task attemp idempotent
for safe concurrent.
2022-09-08 13:19:37 +08:00
killagu
d8a27e23d7 Release 2.1.0 2022-09-05 20:53:46 +08:00
elrrrrrrr
c5d2b49ab3 feat: auto get next valid task (#311)
* 🚗 findExecuteTask 自动获取下一个满足条件的 task
2022-09-05 20:52:54 +08:00
killagu
9de3f0996c Release 2.0.0 2022-09-05 11:33:54 +08:00
killa
fc4baff226 chore: refactor schedule with @Schedule (#309) 2022-09-05 11:30:32 +08:00
fengmk2
959e292be9 Release 1.11.6 2022-09-04 23:20:53 +08:00
elrrrrrrr
768f951b6f fix: cnpmjsorg changesstream limit (#310)
* 🐞 修复 cnpmjs.org 类型 changesStream 返回最新 changes 时,计数异常
2022-09-04 23:19:44 +08:00
killagu
0d8a667b3f Release 1.11.5 2022-09-02 18:49:51 +08:00
elrrrrrrr
f673ab8ba1 fix: execute state check (#308)
taskService.findExecuteTask 添加判断,只处理状态仍为 waiting 状态的 task
2022-09-02 18:48:36 +08:00
fengmk2
091420ae26 🤖 TEST: Add SQL Review Action (#307)
https://github.com/marketplace/actions/sql-review
2022-09-01 23:26:15 +08:00
killagu
eb32254379 Release 1.11.4 2022-08-30 17:20:02 +08:00
elrrrrrrr
f9210ca7e1 fix: changes stream empty (#306)
* 🐞 修复 changesStream 返回空列表,task 状态不更新的问题
2022-08-30 16:54:08 +08:00
killagu
47c9630cf5 Release 1.11.3 2022-08-29 21:05:32 +08:00
elrrrrrrr
48f228da44 fix: changes stream updatedAt (#304) 2022-08-29 20:57:21 +08:00
elrrrrrrr
87045ba8b0 fix: task updatedAt save (#305)
* 🧶 ModelConvertor 新增 autoUpdatedAt 参数,taskRepository.saveTask 默认开启。

目前 saveTask 前会先从 db 内查询 entity.id 对应的 model,如果 updatedAt 不一致则更新。
如果单个任务有多个 worker 在执行,会导致 updatedAt 和 entity 不一致,导致 updaptedAt 被错误覆盖。
后续会触发 timeout Retry,放大异常。
2022-08-29 20:22:30 +08:00
fengmk2
a58916a3b9 Release 1.11.2 2022-08-28 22:20:04 +08:00
fengmk2
e06c841537 🐛 FIX: Should sync public package when registryName not exists (#303) 2022-08-28 22:19:01 +08:00
fengmk2
f139444213 📖 DOC: Update contributors 2022-08-28 19:01:38 +08:00
fengmk2
c4a9de598d Create SECURITY.md
Follow https://raw.githubusercontent.com/atomist/samples/master/SECURITY.md
2022-08-28 09:56:20 +08:00
fengmk2
709d65bd04 🤖 TEST: Use diff bucket on OSS test (#301) 2022-08-28 09:21:07 +08:00
killa
95766990fa chore: use AsyncGenerator insteadof Transform stream (#300)
Signed-off-by: killagu <killa123@126.com>
2022-08-27 10:08:21 +08:00
killa
4e8700c4f7 fix: only create createHookTask if hook enable (#299) 2022-08-26 20:36:06 +08:00
fengmk2
3ed5269f1d 📦 NEW: Mirror better-sqlite3 binary (#296) 2022-08-24 22:43:45 +08:00
killagu
997295b3fc Release 1.11.1 2022-08-24 16:28:57 +08:00
elrrrrrrr
359a150eb4 fix: changes stream (#297)
1. 修复 registry/sync 初始化时没有传递 registryId 问题
2. 修复 changesStream 获取时,如果没有需要同步的任务会导致 task#since 无法更新
2022-08-24 15:54:04 +08:00
killagu
304014c300 Release 1.11.0 2022-08-23 10:32:05 +08:00
elrrrrrrr
a91c8ac4d0 feat: sync package from spec regsitry (#293) 2022-08-22 21:12:41 +08:00
elrrrrrrr
de37008261 feat: changesStream adapter & needSync() method (#292) 2022-08-22 20:07:25 +08:00
elrrrrrrr
4b506c8371 feat: init registry & scope (#286) 2022-08-17 17:38:55 +08:00
killa
41c6e24c84 feat: impl trigger Hooks (#289)
Refs:
- https://github.com/cnpm/cnpmcore/issues/282
2022-08-17 00:04:08 +08:00
killa
79cb82615f feat: impl migration sql (#290) 2022-08-16 23:51:36 +08:00
killa
4cfa8ed9d6 feat: impl hooks api (#287)
* feat: impl hooks api

Refs:
- https://github.com/cnpm/cnpmcore/issues/282
- https://github.com/npm/registry/blob/master/docs/hooks/endpoints.md
2022-08-16 16:56:26 +08:00
killa
47d53d22ad feat: add bizId for task (#285)
* feat: add bizId for task

impl idempotent save for task

Refs:
- https://github.com/cnpm/cnpmcore/issues/282
2022-08-16 16:54:45 +08:00
fengmk2
710680742a 🐛 FIX: Should show queue size on logging (#280) 2022-08-08 23:15:04 +08:00
fengmk2
3a41b2161c 🐛 FIX: Handle binary configuration value (#278)
close https://github.com/cnpm/cnpmcore/pull/274
2022-08-06 11:28:52 +08:00
Opportunity
3b1536b070 feat: add node-webrtc mirror (#274) 2022-08-05 09:16:38 +08:00
fengmk2
3a37f4b6f7 Release 1.10.0 2022-08-04 19:28:16 +08:00
killa
c2b7d5aa98 feat: use sort set to impl queue (#277)
Use sort set to keep queue in order and keep same value only insert once
2022-08-04 19:21:21 +08:00
killagu
269cbf1185 Release 1.9.1 2022-07-29 14:28:25 +08:00
killa
c54aa2165c fix: check executingCount after task is done (#276)
app.config.cnpmcore.syncPackageWorkerMaxConcurrentTasks may dynamic
modify, should check executingCount in every loop.
2022-07-29 14:05:49 +08:00
fengmk2
3268d030b6 🤖 TEST: show package not use cache if isSync (#273)
tests for #268
2022-07-26 01:24:30 +08:00
killagu
86e7fc6d4b Release 1.9.0 2022-07-25 14:36:02 +08:00
killa
af6a75af32 feat: add forceSyncHistory options (#271) 2022-07-25 14:34:56 +08:00
killagu
4303c8aa25 Release 1.8.0 2022-07-21 09:29:36 +08:00
killa
b49a38c77e feat: use Model with inject (#269) 2022-07-21 09:20:41 +08:00
fengmk2
f322f28a5c Release 1.7.1 2022-07-20 13:46:17 +08:00
killa
52fca55aa8 fix: show package not use cache if isSync (#268) 2022-07-20 13:45:19 +08:00
killagu
b78ac80093 Release 1.7.0 2022-07-12 11:49:36 +08:00
killa
4f7ce8b4b2 deps: upgrade leoric to 2.x (#262) 2022-07-12 11:48:43 +08:00
471 changed files with 647410 additions and 8280 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,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

@@ -5,17 +5,96 @@ name: Node.js CI
on:
push:
branches:
- main
- master
branches: [master]
pull_request:
branches:
- main
- master
schedule:
- cron: '0 2 * * *'
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, 22, 24]
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
# https://github.com/elastic/elastic-github-actions/blob/master/elasticsearch/README.md
- name: Configure sysctl limits
run: |
sudo swapoff -a
sudo sysctl -w vm.swappiness=1
sudo sysctl -w fs.file-max=262144
sudo sysctl -w vm.max_map_count=262144
- name: Runs Elasticsearch
uses: elastic/elastic-github-actions/elasticsearch@master
with:
stack-version: 8.18.0
security-enabled: false
- name: Wait for Elasticsearch to be ready
run: |
curl -v http://localhost:9200
while ! curl -s http://localhost:9200 | grep -q "elasticsearch"; do
echo "Waiting for Elasticsearch to be ready..."
sleep 1
done
- 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
CNPMCORE_CONFIG_ENABLE_ES: true
CNPMCORE_CONFIG_ES_CLIENT_NODES: http://localhost:9200
- name: Code Coverage
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
test-mysql57-fs-nfs:
runs-on: ${{ matrix.os }}
@@ -28,47 +107,41 @@ jobs:
ports:
- 3306:3306
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]
node-version: [20, 22, 24]
os: [ubuntu-latest]
steps:
- name: Checkout Git Source
uses: actions/checkout@v2
- name: Checkout Git Source
uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
# https://github.com/marketplace/actions/redis-server-in-github-actions#usage
- name: Start Redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Install Dependencies
run: npm i -g npminstall && npminstall
- name: Install Dependencies
run: npm i
- name: Continuous Integration
run: npm run ci
- name: Continuous Integration
run: npm run ci
- name: Code Coverage
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Code Coverage
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
test-mysql57-oss-nfs:
test-mysql57-s3-nfs:
runs-on: ${{ matrix.os }}
if:
contains('
refs/heads/main
refs/heads/master
refs/heads/dev
', github.ref)
services:
mysql:
@@ -80,40 +153,42 @@ jobs:
- 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: [16, 18]
node-version: [20, 22]
os: [ubuntu-latest]
steps:
- name: Checkout Git Source
uses: actions/checkout@v2
- name: Checkout Git Source
uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
# https://github.com/marketplace/actions/redis-server-in-github-actions#usage
- name: Start Redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Install Dependencies
run: npm i
- name: Install Dependencies
run: npm i
- 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: Continuous Integration
run: npm run ci
env:
CNPMCORE_NFS_TYPE: oss
CNPMCORE_NFS_OSS_BUCKET: cnpmcore-unittest-github
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@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Code Coverage
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}

12
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: Release
on:
push:
branches: [ master ]
jobs:
release:
name: Node.js
uses: cnpm/github-actions/.github/workflows/node-release.yml@master
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}

View File

@@ -1,27 +0,0 @@
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: '45 15 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Stale issue message'
stale-pr-message: 'Stale pull request message'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'

7
.gitignore vendored
View File

@@ -13,6 +13,7 @@ config/config.prod.ts
config/**/*.js
app/**/*.js
test/**/*.js
app.js
.cnpmcore
.cnpmcore_unittest
@@ -75,7 +76,7 @@ typings/
# Output of 'npm pack'
*.tgz
!test/fixtures/*.tgz
!test/fixtures/**/*.tgz
# Yarn Integrity file
.yarn-integrity
@@ -116,4 +117,8 @@ dist
.tern-port
.idea
.DS_Store
run
!test/ctx_register.js
.egg/

1
.husky/pre-commit Normal file
View File

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

149
.oxlintrc.json Normal file
View File

@@ -0,0 +1,149 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"env": {
"node": true,
"mocha": true
},
"categories": {
"correctness": "error",
"perf": "error",
"nursery": "error",
"restriction": "error",
"style": "error",
"pedantic": "error",
"suspicious": "error"
},
"plugins": [
"import",
"typescript",
"unicorn",
"jsdoc",
"node",
"promise",
"oxc"
],
"rules": {
// eslint
"constructor-super": "error",
"group-exports": "allow",
"getter-return": "error",
"no-undef": "error",
"no-unreachable": "error",
"no-var": "error",
"no-eq-null": "error",
"no-await-in-loop": "allow",
"eqeqeq": ["error", "smart"],
"init-declarations": "allow",
"curly": "allow",
"no-ternary": "allow",
"max-params": ["error", 6],
"no-await-expression-member": "error",
"no-continue": "allow",
"guard-for-in": "allow",
"func-style": "allow",
"sort-imports": "allow",
"yoda": "allow",
"sort-keys": "allow",
"no-magic-numbers": "allow",
"no-duplicate-imports": "error",
"no-multi-assign": "error",
"func-names": "error",
"default-param-last": "error",
"prefer-object-spread": "error",
"no-undefined": "allow",
"no-plusplus": "allow",
// maybe warn
"no-console": "warn",
"no-extraneous-class": "allow",
"no-empty-function": "error",
"max-depth": ["error", 6],
"max-lines-per-function": "allow",
"no-lonely-if": "error",
"max-lines": "allow",
"require-await": "allow",
"max-nested-callbacks": ["error", 5],
"max-classes-per-file": "allow",
"radix": "allow",
"no-negated-condition": "error",
"no-else-return": "error",
"no-throw-literal": "error",
// import
"import/exports-last": "allow",
"import/max-dependencies": "allow",
"import/no-cycle": "error",
"import/no-anonymous-default-export": "error",
"import/no-namespace": "error",
"import/named": "error",
"import/export": "error",
"import/no-default-export": "allow",
"import/unambiguous": "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",
"promise/avoid-new": "error",
// unicorn
"unicorn/error-message": "error",
"unicorn/no-null": "allow",
"unicorn/filename-case": "allow",
"unicorn/prefer-structured-clone": "error",
"unicorn/prefer-logical-operator-over-ternary": "error",
"unicorn/prefer-number-properties": "error",
"unicorn/prefer-array-some": "error",
"unicorn/prefer-string-slice": "error",
// "unicorn/no-null": "error",
"unicorn/throw-new-error": "error",
"unicorn/catch-error-name": "allow",
"unicorn/prefer-spread": "allow",
"unicorn/numeric-separators-style": "error",
"unicorn/prefer-string-raw": "error",
"unicorn/text-encoding-identifier-case": "error",
"unicorn/no-array-for-each": "error",
"unicorn/explicit-length-check": "error",
"unicorn/no-lonely-if": "error",
"unicorn/no-useless-undefined": "allow",
"unicorn/prefer-date-now": "error",
"unicorn/no-static-only-class": "allow",
"unicorn/no-typeof-undefined": "error",
"unicorn/prefer-negative-index": "error",
// oxc
"oxc/no-map-spread": "error",
"oxc/no-rest-spread-properties": "allow",
"oxc/no-optional-chaining": "allow",
"oxc/no-async-await": "allow",
// typescript
"typescript/explicit-function-return-type": "allow",
"typescript/consistent-type-imports": "error",
"typescript/consistent-type-definitions": "error",
"typescript/consistent-indexed-object-style": "allow",
"typescript/no-inferrable-types": "error",
"typescript/array-type": "error",
"typescript/no-non-null-assertion": "error",
"typescript/no-explicit-any": "error",
"typescript/no-import-type-side-effects": "error",
"typescript/no-dynamic-delete": "error",
"typescript/prefer-ts-expect-error": "error",
"typescript/ban-ts-comment": "error",
"typescript/prefer-enum-initializers": "error",
// jsdoc
"jsdoc/require-returns": "allow",
"jsdoc/require-param": "allow",
// import
"consistent-type-specifier-style": "allow",
"no-unassigned-import": "allow",
// for-loop
"no-for-loop": "allow"
},
"ignorePatterns": ["index.d.ts"]
}

6
.prettierrc Normal file
View File

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

26
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,26 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Egg Debug",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev", "--", "--inspect-brk"],
"console": "integratedTerminal",
"restart": true,
"autoAttachChildProcesses": true
},
{
"type": "node",
"request": "launch",
"name": "Egg Test",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "test:local", "--", "--inspect-brk"],
"autoAttachChildProcesses": true
}
]
}

1644
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,64 +2,109 @@
## 环境初始化
本项目的外部服务依赖有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)
## 本地开发
### 安装依赖
```bash
$ npm install
npm install
```
### 开发运行
### 开发运行 - MySQL
```bash
# 初始化数据库
$ MYSQL_DATABASE=cnpmcore npm run prepare-database
CNPMCORE_DATABASE_NAME=cnpmcore bash ./prepare-database-mysql.sh
# 启动 Web 服务
$ DEBUG_LOCAL_SQL=true npm run dev
npm run dev
# 访问
curl -v http://127.0.0.1:7001
```
### 单元测试
### 开发运行 - PostgreSQL
```bash
$ npm run test
# 初始化数据库
CNPMCORE_DATABASE_NAME=cnpmcore bash ./prepare-database-postgresql.sh
# 启动 Web 服务
npm run dev:postgresql
# 访问
curl -v http://127.0.0.1:7001
```
编写单测规范:
### 登录和测试发包
- assert 断言库必须使用 require 引入
> cnpmcore 默认不开放注册,可以通过 `config.default.ts` 中的 `allowPublicRegistration` 配置开启,否则只有管理员可以登录
```ts
import assert = require('assert');
注册 cnpmcore_admin 管理员
```bash
npm login --registry=http://127.0.0.1:7001
# 验证登录
npm whoami --registry=http://127.0.0.1:7001
```
> CAUTION: don't use `import assert from 'assert'`
> Just use old style import assert = require('assert') for assert module. This is limitation.
> See https://github.com/power-assert-js/espower-typescript#caution-dont-use-import-assert-from-assert
发包
```bash
npm publish --registry=http://127.0.0.1:7001
```
### 单元测试
MySQL
```bash
npm run test
```
PostgreSQL
```bash
npm run test:postgresql
```
## 项目结构
```
```txt
app
├── common
│ └── adapter
@@ -72,6 +117,8 @@ app
│ └── controller
├── repository
│ └── model
├── infra
│ └── NFSClientAdapter.ts
└── test
├── control
│ └── response_time.test.js
@@ -80,31 +127,69 @@ app
```
common
- util全局工具类
- adapter外部服务调用
core
- entity核心模型实现业务行为
- event异步事件定义以及消费串联业务
- service核心业务
- util服务 core 内部,不对外暴露
repository
- modelORM 模型,数据定义
- XXXRepository: 仓储接口,存储、查询过程
port
- controllerHTTP controller
infra
基于 PaaS 基础设置实现各种 adapter 真实适配实现cnpmcore 会内置一种实现,企业自定义的 cnpmcore 应该自行基于自身的
PaaS 环境实现自己的 infra module。
- NFSClientAdapter.ts
- QueueAdapter.ts
- AuthAdapter.ts
## 架构分层依赖图
```txt
+--------------------------------+ +--------+ +----------+
| Controller | | | | |
+----^-------------^-------------+ | | | |
| | | | | |
| inject | inject | | | |
| | | | | |
| +----------+-------------+ | | | |
| | Service | | Entity | | |
| +-----------^------------+ | | | |
| | | | | Common |
| | inject | | | |
| | | | | |
+----+--------------+------------+ | | | |
| Repository | | | | |
+-------------------^------------+ +---^----| | |
| | | |
| inject ORM | | |
| | | |
+-----------+------------+ | | |
| Model +<-----+ | |
+------------------------+ +----------+
```
## Controller 开发指南
目前只支持 HTTP 协议的 Controller代码在 `app/port/controller` 目录下。
基于类继承的模式来实现,类关系大致如下:
```
```txt
+----------------------+ +----------------------+ +---------------+
| PackageController.ts | | PackageTagController | | XxxController |
| PackageController | | PackageTagController | | XxxController |
+---------------+------+ +---+------------------+ +--+------------+
| | |
| extends | extends | extends
@@ -130,15 +215,15 @@ port
例如会封装 PackageEntity、PackageVersionEntity 等查询方法。
```ts
// try to get package entity, throw NotFoundError when package not exists
private async getPackageEntity(scope: string, name: string) {
const packageEntity = await this.packageRepository.findPackage(scope, name);
if (!packageEntity) {
const fullname = getFullname(scope, name);
throw new NotFoundError(`${fullname} not found`);
}
return packageEntity;
// try to get package entity, throw NotFoundError when package not exists
private async getPackageEntity(scope: string, name: string) {
const packageEntity = await this.packageRepository.findPackage(scope, name);
if (!packageEntity) {
const fullname = getFullname(scope, name);
throw new NotFoundError(`${fullname} not found`);
}
return packageEntity;
}
```
### 请求合法性校验三部曲
@@ -192,13 +277,39 @@ await this.userRoleManager.requiredPackageMaintainer(pkg, authorizedUser);
当然,大部分对包进行写操作的请求下,我们在 AbstractController 里面抽取了一个更加简便的方法,一次性将数据获取和权限校验包含在一起:
```ts
const pkg = await this.getPackageEntityAndRequiredMaintainer(ctx, fullname);
const { pkg } = await this.ensurePublishAccess(ctx, fullname);
```
## Service 开发指南
Service 依赖 Repository然后被 Controller 依赖
```txt
+---------------------------+ +----------------------+ +-------------+
| PackageVersionFileService | | PackageSyncerService | | XxxService |
+---------------^-----------+ +---^------------------+ +--^----------+
| | |
| inject | inject | inject
| | |
+---+-------------------+-------------------------+--+
| PackageManagerService |
+-----------------------^----------------------------+
|
| inject
|
+---------+--------+
| XxxRepository |
+------------------+
```
### PackageManagerService 管理所有包以及版本信息
它会被其他 Service 依赖
## Repository 开发指南
Repository 依赖 Model然后被 Service 和 Controller 依赖
### Repository 类方法命名规则
- `findSomething` 查询一个模型数据
@@ -216,9 +327,9 @@ const pkg = await this.getPackageEntityAndRequiredMaintainer(ctx, fullname);
可能需要涉及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,68 +0,0 @@
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.

240
INTEGRATE.md Normal file
View File

@@ -0,0 +1,240 @@
# 🥚 如何在 [tegg](https://github.com/eggjs/tegg) 中集成 cnpmcore
> 文档中的示例项目可以在 [这里](https://github.com/eggjs/examples/commit/bed580fe053ae573f8b63f6788002ff9c6e7a142) 查看,在开始前请确保已阅读 [DEVELOPER.md](DEVELOPER.md) 中的相关文档,完成本地开发环境搭建。
在生产环境中,我们也可以直接部署 cnpmcore 系统,实现完整的 Registry 镜像功能。
但通常,在企业内部会有一些内部的中间件服务或限制,例如文件存储、缓存服务、登录鉴权流程等。
除了源码部署、二次开发的方式,我们还提供了 npm 包的方式,便于 [tegg](https://github.com/eggjs/tegg) 应用集成。
这样既可以享受到丰富的自定义扩展能力,又可以享受到 cnpmcore 持续迭代的功能演进。
下面,让我们以 [tegg](https://github.com/eggjs/tegg) 初始化的应用为例,以 npm 包的方式集成 cnpmcore并扩展登录功能以支持企业内 [SSO](https://en.wikipedia.org/wiki/Single_sign-on) 登录。
## 🚀 快速开始
### 🆕 新建一个 tegg 应用
> 我们以 <https://github.com/eggjs/examples/tree/master/hello-tegg> 为例
```shell
.
├── app
│   ├── biz
│   ├── controller
│   └── middleware
├── config
│   ├── config.default.ts
│   └── plugin.ts
├── package.json
├── test
│   ├── biz
│   └── controller
└── tsconfig.json
```
### 📦︎ 安装 cnpmcore 修改对应配置
```shell
npm i cnpmcore -S
```
1. 修改 `ts-config.json` 配置,这是因为 cnpmcore 使用了 [subPath](https://nodejs.org/api/packages.html#subpath-exports)
```json
{
"extends": "@eggjs/tsconfig",
"compilerOptions": {
"baseUrl": "./",
"target": "ES2021"
}
}
```
2. 修改 `config/plugin.ts` 文件,开启 cnpmcore 依赖的一些插件
```typescript
// 开启如下插件
{
redis: {
enable: true,
package: '@eggjs/redis',
},
teggOrm: {
enable: true,
package: '@eggjs/tegg-orm-plugin',
},
eventbusModule: {
enable: true,
package: '@eggjs/tegg-eventbus-plugin',
},
tracer: {
enable: true,
package: '@eggjs/tracer',
},
typeboxValidate: {
enable: true,
package: 'egg-typebox-validate',
},
}
```
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
1. 创建文件夹,用于存放自定义的 infra module这里以 app/infra 为例
```shell
├── infra
│   ├── AuthAdapter.ts
│   ├── NFSAdapter.ts
│   ├── QueueAdapter.ts
│   └── package.json
```
* 添加 `package.json` ,声明 infra 作为一个 eggModule 单元
```JSON
{
"name": "infra",
"eggModule": {
"name": "infra"
}
}
```
* 添加 `XXXAdapter.ts` 在对应的 Adapter 中继承 cnpmcore 默认的 Adapter以 AuthAdapter 为例
```typescript
import { AccessLevel, SingletonProto } from '@eggjs/tegg';
import { AuthAdapter } from 'cnpmcore/infra/AuthAdapter';
@SingletonProto({
name: 'authAdapter',
accessLevel: AccessLevel.PUBLIC,
})
export class MyAuthAdapter extends AuthAdapter {
}
```
2. 添加 `config/module.json`,将 cnpmcore 作为一个 module 集成进我们新增的 tegg 应用中
```json
[
{
"path": "../app/biz"
},
{
"path": "../app/infra"
},
{
"package": "cnpmcore/common"
},
{
"package": "cnpmcore/core"
},
{
"package": "cnpmcore/port"
},
{
"package": "cnpmcore/repository"
}
]
```
### ✍🏻 重载 AuthAdapter 实现
我们以 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';
import { randomUUID } from 'crypto';
import { AuthUrlResult, userResult } from 'node_modules/cnpmcore/dist/app/common/typing';
const ONE_DAY = 3600 * 24;
@SingletonProto({
name: 'authAdapter',
accessLevel: AccessLevel.PUBLIC,
})
export class MyAuthAdapter extends AuthAdapter {
async getAuthUrl(ctx: EggContext): Promise<AuthUrlResult> {
const sessionId = randomUUID();
await this.redis.setex(sessionId, ONE_DAY, '');
return {
// 替换实际企业内的登录中心地址,这里我们以系统内默认的 hello 路由为例
loginUrl: `${ctx.origin}/hello?name=${sessionId}`,
doneUrl: `${ctx.href}/done/session/${sessionId}`,
};
}
async ensureCurrentUser(): Promise<userResult | null> {
return {
name: 'hello',
email: 'hello@cnpmjs.org',
};
}
}
```
修改 HelloController 的实现,实际也可以通过登录中心回调、页面确认等方式实现
```typescript
// 触发回调接口,会自动完成用户创建
await this.httpclient.request(`${ctx.origin}/-/v1/login/sso/${name}`, { method: 'POST' });
```
## 🎉 功能验证
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...
```
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
```

View File

@@ -1,36 +1,34 @@
# 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
See [DEVELOPER.md](DEVELOPER.md)
## How to integrate
See [INTEGRATE.md](INTEGRATE.md)
## License
[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/26033663?v=4" width="100px;"/><br/><sub><b>Zian502</b></sub>](https://github.com/Zian502)<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/227713?v=4" width="100px;"/><br/><sub><b>atian25</b></sub>](https://github.com/atian25)<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/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/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/3478550?v=4" width="100px;"/><br/><sub><b>coolyuantao</b></sub>](https://github.com/coolyuantao)<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 Jul 09 2022 08:59:28 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)

41
SECURITY.md Normal file
View File

@@ -0,0 +1,41 @@
# Security Policy
## Supported Versions
Currently being supported with security updates.
| Version | Supported |
| -------- | ------------------ |
| >= 3.0.0 | :white_check_mark: |
## Reporting a Vulnerability
The cnpmcore OSS team and community take all security vulnerabilities seriously.
Thank you for improving the security of our open source software.
We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.
Report security vulnerabilities by emailing the cnpmcore security team at:
```
fengmk2+cnpmcoresecurity@gmail.com
killa123@126.com
```
The lead maintainer will acknowledge your email within 48 hours,
and will send a more detailed response within 72 hours indicating the next steps in handling your report.
After the initial reply to your report,
the security team will endeavor to keep you informed of the progress towards a fix and full announcement,
and may ask for additional information or guidance.
Report security vulnerabilities in third-party modules to the person or team maintaining the module.
## Disclosure Policy
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
will be released as fast as possible to NPM.

36
app.ts
View File

@@ -1,6 +1,7 @@
import path from 'path';
import { readFile } from 'fs/promises';
import { Application } from 'egg';
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 {
@@ -8,7 +9,7 @@ declare module 'egg' {
}
}
export default class CnpmcoreAppHook {
export default class CnpmcoreAppHook implements ILifecycleBoot {
private readonly app: Application;
constructor(app: Application) {
@@ -16,11 +17,34 @@ export default class CnpmcoreAppHook {
this.app.binaryHTML = '';
}
configWillLoad() {
const app = this.app;
// https://github.com/eggjs/tegg/blob/master/plugin/orm/app.ts#L37
// store query sql to log
app.config.orm.logger = {
...app.config.orm.logger,
logQuery(sql: string, duration: number) {
app.getLogger('sqlLogger').info('[%s] %s', duration, sql);
},
};
}
// https://eggjs.org/zh-cn/basics/app-start.html
async didReady() {
// 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);
const text = await readFile(filepath, 'utf8');
this.app.binaryHTML = text.replace(
'{{registry}}',
this.app.config.cnpmcore.registry
);
}
// 应用退出时执行
// 需要暂停当前执行的 changesStream task
async beforeClose() {
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()

36
app/common/CryptoUtil.ts Normal file
View File

@@ -0,0 +1,36 @@
import { generateKeyPairSync } from 'node:crypto';
import NodeRSA from 'node-rsa';
// generate rsa key pair
export function genRSAKeys(): { publicKey: string, privateKey: string } {
const key = generateKeyPairSync('rsa', {
modulusLength: 512,
});
const publicKey = key.publicKey.export({
type: 'pkcs1',
format: 'pem',
}).toString('base64');
const privateKey = key.privateKey.export({
type: 'pkcs1',
format: 'pem',
}).toString('base64');
return { publicKey, privateKey };
}
// encrypt rsa private key
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, encryptedBase64: string): string {
const key = new NodeRSA(privateKey, 'pkcs1-private-pem', {
encryptionScheme: 'pkcs1',
environment: 'browser',
});
return key.decrypt(encryptedBase64, 'utf8');
}

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

@@ -0,0 +1,29 @@
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 &&
TimeoutErrorNames.has(err.cause.name)
) {
return true;
}
return false;
}

View File

@@ -1,56 +1,41 @@
import { mkdir, rm } from 'fs/promises';
import { createWriteStream } from 'fs';
import { setTimeout } from 'timers/promises';
import path from 'path';
import url from 'url';
import { randomBytes } from 'crypto';
import { EggContextHttpClient } from 'egg';
import dayjs from './dayjs';
// oxlint-disable import/exports-last
import { mkdir, rm } from 'node:fs/promises';
import { createWriteStream } from 'node:fs';
import { setTimeout } from 'node:timers/promises';
import path from 'node:path';
import url from 'node:url';
import { randomBytes } from 'node:crypto';
import type { EggContextHttpClient, HttpClientResponse } from 'egg';
import mime from 'mime-types';
import dayjs from './dayjs.js';
export async function createTempfile(dataDir: string, filename: string) {
// will auto clean on CleanTempDir Schedule
const tmpdir = path.join(dataDir, 'downloads', dayjs().format('YYYY/MM/DD'));
await mkdir(tmpdir, { recursive: true });
// 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!)}`);
return tmpfile;
}
export async function downloadToTempfile(httpclient: EggContextHttpClient,
dataDir: string, url: string, ignoreDownloadStatuses?: number[], retries = 3) {
let lastError: any;
while (retries > 0) {
try {
return await _downloadToTempfile(httpclient, dataDir, url, ignoreDownloadStatuses);
} catch (err: any) {
if (err.name === 'DownloadNotFoundError') throw err;
lastError = err;
}
retries--;
if (retries > 0) {
// sleep 1s ~ 4s in random
await setTimeout(1000 + Math.random() * 4000);
}
}
throw lastError;
}
async function _downloadToTempfile(httpclient: EggContextHttpClient,
dataDir: string, url: string, ignoreDownloadStatuses?: number[]) {
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 { status, headers, res } = await httpclient.request(url, {
timeout: 60000 * 10,
const requestHeaders: Record<string, string> = {};
if (optionalConfig?.remoteAuthToken) {
requestHeaders.authorization = `Bearer ${optionalConfig.remoteAuthToken}`;
}
const { status, headers, res } = (await httpclient.request(url, {
timeout: 60_000 * 10,
headers: requestHeaders,
writeStream,
timing: true,
followRedirect: true,
});
if (status === 404 || (ignoreDownloadStatuses && 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;
@@ -70,3 +55,107 @@ async function _downloadToTempfile(httpclient: EggContextHttpClient,
throw err;
}
}
export interface DownloadToTempfileOptionalConfig {
retries?: number;
ignoreDownloadStatuses?: number[];
remoteAuthToken?: string;
}
export async function createTempDir(dataDir: string, dirname?: string) {
// will auto clean on CleanTempDir Schedule
let tmpdir = path.join(dataDir, 'downloads', dayjs().format('YYYY/MM/DD'));
if (dirname) {
tmpdir = path.join(tmpdir, dirname);
}
await mkdir(tmpdir, { recursive: true });
return tmpdir;
}
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,
// oxlint-disable-next-line typescript-eslint/no-non-null-assertion
`${randomBytes(10).toString('hex')}-${path.basename(url.parse(filename).pathname!)}`
);
return tmpfile;
}
export async function downloadToTempfile(
httpclient: EggContextHttpClient,
dataDir: string,
url: string,
optionalConfig?: DownloadToTempfileOptionalConfig
) {
let retries = optionalConfig?.retries || 3;
let lastError: Error | undefined;
while (retries > 0) {
try {
return await _downloadToTempfile(
httpclient,
dataDir,
url,
optionalConfig
);
} catch (err) {
if (err.name === 'DownloadNotFoundError') 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;
await setTimeout(delay);
}
}
// oxlint-disable-next-line no-throw-literal
throw lastError;
}
export interface Tempfile {
tmpfile: string;
headers: HttpClientResponse['res']['headers'];
timing: HttpClientResponse['res']['timing'];
}
const DEFAULT_CONTENT_TYPE = 'application/octet-stream';
const PLAIN_TEXT = 'text/plain';
const WHITE_FILENAME_CONTENT_TYPES = {
license: PLAIN_TEXT,
readme: PLAIN_TEXT,
history: PLAIN_TEXT,
changelog: PLAIN_TEXT,
'.npmignore': PLAIN_TEXT,
'.jshintignore': PLAIN_TEXT,
'.eslintignore': PLAIN_TEXT,
'.jshintrc': 'application/json',
'.eslintrc': 'application/json',
} as const;
const CONTENT_TYPE_BLACKLIST = new Set(['application/xml', 'text/html']);
export function ensureContentType(contentType: string) {
if (CONTENT_TYPE_BLACKLIST.has(contentType)) {
return 'text/plain';
}
return contentType;
}
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;
const defaultContentType = mime.lookup(filename);
// https://github.com/cnpm/cnpmcore/issues/693#issuecomment-2955268229
const contentType =
defaultContentType ||
WHITE_FILENAME_CONTENT_TYPES[
filename as keyof typeof WHITE_FILENAME_CONTENT_TYPES
] ||
DEFAULT_CONTENT_TYPE;
return ensureContentType(contentType);
}

3
app/common/LogUtil.ts Normal file
View File

@@ -0,0 +1,3 @@
export function isoNow() {
return new Date().toISOString();
}

View File

@@ -1,5 +1,15 @@
import { createReadStream } from 'fs';
import * as ssri from 'ssri';
import { createReadStream } from 'node:fs';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import { fromData, fromStream, type HashLike } from 'ssri';
// @ts-expect-error type error
import tar from '@fengmk2/tar';
import type {
AuthorType,
PackageJSONType,
} from '../repository/PackageRepository.js';
// /@cnpm%2ffoo
// /@cnpm%2Ffoo
@@ -7,28 +17,44 @@ import * as ssri from 'ssri';
// /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 {
return scope ? `${scope}/${name}` : name;
}
export async function calculateIntegrity(contentOrFile: Uint8Array | string) {
let integrityObj;
export function cleanUserPrefix(username: string): string {
return username.replace(/^.*:/, '');
}
export function getPrefixedName(prefix: string, username: string): string {
return prefix ? `${prefix}${username}` : username;
}
export interface Integrity {
integrity: string;
shasum: string;
}
export async function calculateIntegrity(
contentOrFile: Uint8Array | string
): Promise<Integrity> {
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;
@@ -36,20 +62,109 @@ 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`;
}
export function detectInstallScript(manifest: any) {
export function detectInstallScript(manifest: {
scripts?: Record<string, string>;
}) {
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-version-object
let hasInstallScript = false;
const scripts = manifest.scripts;
if (scripts) {
// https://www.npmjs.com/package/fix-has-install-script
if (scripts.install || scripts.preinstall || scripts.postinstall) {
hasInstallScript = true;
}
// https://www.npmjs.com/package/fix-has-install-script
if (scripts?.install || scripts?.preinstall || scripts?.postinstall) {
hasInstallScript = true;
}
return hasInstallScript;
}
/** 判断一个版本压缩包中是否包含 npm-shrinkwrap.json */
export async function hasShrinkWrapInTgz(
contentOrFile: Uint8Array | string
): Promise<boolean> {
let readable: Readable;
if (typeof contentOrFile === 'string') {
readable = createReadStream(contentOrFile);
} else {
readable = new Readable({
read() {
this.push(contentOrFile);
this.push(null);
},
});
}
let hasShrinkWrap = false;
const abortController = new AbortController();
const parser = tar.t({
// options.strict 默认为 false会忽略 Recoverable errors例如 tar 解析失败
// 详见 https://github.com/isaacs/node-tar#warnings-and-errors
// oxlint-disable-next-line typescript-eslint/no-explicit-any
onentry(entry: any) {
if (entry.path === 'package/npm-shrinkwrap.json') {
hasShrinkWrap = true;
abortController.abort();
}
},
});
try {
await pipeline(readable, parser, { signal: abortController.signal });
return hasShrinkWrap;
} catch (e) {
if (e.code === 'ABORT_ERR') {
return hasShrinkWrap;
}
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> {
// oxlint-disable-next-line promise/avoid-new
return new Promise((resolve, reject) => {
Readable.from(tarballBytes).pipe(
tar.t({
filter: (name: string) => name === 'package/package.json',
onentry: async (entry: Readable) => {
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
@@ -6,7 +6,7 @@ export function isSyncWorkerRequest(ctx: EggContext) {
if (!isSyncWorkerRequest) {
const ua = ctx.headers['user-agent'] || '';
// old sync client will request with these user-agent
if (ua.indexOf('npm_service.cnpmjs.org/') !== -1) {
if (ua.includes('npm_service.cnpmjs.org/')) {
isSyncWorkerRequest = true;
}
}

View File

@@ -1,7 +1,8 @@
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');
@@ -28,14 +29,28 @@ 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 {
return crypto.createHash('sha512').update(plain).digest('hex');
}
export function getUAInfo(userAgent?: string) {
if (!userAgent) return null;
return new UAParser(userAgent);
}
export function getBrowserTypeForWebauthn(userAgent?: string) {
const ua = getUAInfo(userAgent);
if (!ua) return null;
const os = ua.getOS();
if (os.name === 'iOS' || os.name === 'Android') return 'mobile';
if (os.name === 'Mac OS') return ua.getBrowser().name;
return null;
}

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

@@ -1,18 +1,16 @@
import {
ContextProto,
AccessLevel,
Inject,
} from '@eggjs/tegg';
import { Redis } from 'ioredis';
import { AccessLevel, Inject, SingletonProto } from '@eggjs/tegg';
// FIXME: @eggjs/redis should use ioredis v5
// https://github.com/eggjs/redis/issues/35
import type { Redis } from 'ioredis';
const ONE_DAY = 3600 * 24;
@ContextProto({
@SingletonProto({
accessLevel: AccessLevel.PUBLIC,
})
export class CacheAdapter {
@Inject()
private readonly redis: Redis;
private readonly redis: Redis; // 由 redis 插件引入
async setBytes(key: string, bytes: Buffer) {
await this.redis.setex(key, ONE_DAY, bytes);
@@ -38,7 +36,7 @@ export class CacheAdapter {
const lockName = this.getLockName(key);
const existsTimestamp = await this.redis.get(lockName);
if (existsTimestamp) {
if (Date.now() - parseInt(existsTimestamp) < seconds * 1000) {
if (Date.now() - Number.parseInt(existsTimestamp) < seconds * 1000) {
return null;
}
// lock timeout, delete it
@@ -62,12 +60,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,18 +1,14 @@
import { Readable } from 'stream';
import {
ContextProto,
AccessLevel,
Inject,
} from '@eggjs/tegg';
import type { Readable } from 'node:stream';
import type { IncomingHttpHeaders } from 'node:http';
import { AccessLevel, Inject, SingletonProto } 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';
@ContextProto({
@SingletonProto({
name: INSTANCE_NAME,
accessLevel: AccessLevel.PUBLIC,
})
@@ -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,13 +51,30 @@ 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
);
await this.nfsClient.download(storeKey, file, { timeout });
}
@Pointcut(AsyncTimer)
async remove(storeKey: string) {
this.logger.info('[%s:remove] key: %s, file: %s', INSTANCE_NAME, storeKey);
this.logger.info('[%s:remove] key: %s', INSTANCE_NAME, storeKey);
await this.nfsClient.remove(storeKey);
}
@@ -72,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,18 +1,21 @@
import { setTimeout } from 'timers/promises';
import {
ContextProto,
AccessLevel,
Inject,
} from '@eggjs/tegg';
import {
EggLogger,
EggContextHttpClient,
import { setTimeout } from 'node:timers/promises';
import { AccessLevel, ContextProto, Inject } from '@eggjs/tegg';
import type {
EggAppConfig,
EggContextHttpClient,
EggLogger,
HttpClientRequestOptions,
HttpClientResponse,
} from 'egg';
import { HttpMethod } from 'urllib';
import type { PackageManifestType } from '../../repository/PackageRepository.js';
import { isTimeoutError } from '../ErrorUtil.js';
type HttpMethod = HttpClientRequestOptions['method'];
const INSTANCE_NAME = 'npmRegistry';
export type RegistryResponse = { method: HttpMethod } & HttpClientResponse;
@ContextProto({
name: INSTANCE_NAME,
accessLevel: AccessLevel.PUBLIC,
@@ -24,73 +27,130 @@ export class NPMRegistry {
private readonly httpclient: EggContextHttpClient;
@Inject()
private config: EggAppConfig;
private timeout = 10000;
private timeout = 10_000;
public registryHost: string;
get registry(): string {
return this.config.cnpmcore.sourceRegistry;
return this.registryHost || this.config.cnpmcore.sourceRegistry;
}
public async getFullManifests(fullname: string, retries = 3) {
public setRegistryHost(registryHost = '') {
this.registryHost = registryHost;
}
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
const url = `${this.registry}/${encodeURIComponent(fullname)}?t=${Date.now()}&cache=0`;
let lastError: any;
let lastError: Error | undefined;
while (retries > 0) {
try {
// large package: https://r.cnpmjs.org/%40procore%2Fcore-icons
// https://r.cnpmjs.org/intraactive-sdk-ui 44s
return await this.request('GET', url, undefined, { timeout: 120000 });
} catch (err: any) {
if (err.name === 'ResponseTimeoutError') throw err;
const authorization = this.genAuthorizationHeader(
optionalConfig?.remoteAuthToken
);
return await this.request('GET', url, undefined, {
timeout: 120_000,
headers: { authorization },
});
} catch (err) {
if (isTimeoutError(err)) {
throw err;
}
lastError = err;
}
retries--;
if (retries > 0) {
// sleep 1s ~ 4s in random
await setTimeout(1000 + Math.random() * 4000);
const delay =
process.env.NODE_ENV === 'test' ? 1 : 1000 + Math.random() * 4000;
await setTimeout(delay);
}
}
// oxlint-disable-next-line no-throw-literal
throw lastError;
}
// app.put('/:name/sync', sync.sync);
public async createSyncTask(fullname: string) {
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,
// logId: logId
// };
return await this.request('PUT', url);
return await this.request('PUT', url, undefined, { authorization });
}
// app.get('/:name/sync/log/:id', sync.getSyncLog);
public async getSyncTask(fullname: string, id: string, offset: number) {
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);
return await this.request('GET', url, undefined, { authorization });
}
public async getDownloadRanges(registry: string, fullname: string, start: string, end: string) {
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);
return await this.request('GET', url, undefined, { authorization });
}
private async request(method: HttpMethod, url: string, params?: object, options?: object) {
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,
});
this.logger.info('[NPMRegistry:request] %s %s, status: %s', method, url, res.status);
return {
})) as HttpClientResponse;
this.logger.info(
'[NPMRegistry:request] %s %s, status: %s',
method,
url,
res.status
);
return {
method,
...res,
};
}
public genAuthorizationHeader(remoteAuthToken?: string) {
return remoteAuthToken ? `Bearer ${remoteAuthToken}` : '';
}
}

View File

@@ -1,32 +0,0 @@
import {
AccessLevel,
Inject,
ContextProto,
} from '@eggjs/tegg';
import { Redis } from 'ioredis';
@ContextProto({
accessLevel: AccessLevel.PUBLIC,
})
export class QueueAdapter {
@Inject()
private readonly redis: Redis;
private getQueueName(key: string) {
return `CNPMCORE_Q_${key}`;
}
async push<T>(key: string, item: T) {
return await this.redis.lpush(this.getQueueName(key), JSON.stringify(item));
}
async pop<T>(key: string) {
const json = await this.redis.rpop(this.getQueueName(key));
if (!json) return null;
return JSON.parse(json) as T;
}
async length(key: string) {
return await this.redis.llen(this.getQueueName(key));
}
}

View File

@@ -1,69 +1,106 @@
import { EggContextHttpClient, EggLogger } from 'egg';
import { BinaryTaskConfig } from '../../../../config/binaries';
import {
Inject,
QualifierImplDecoratorUtil,
type ImplDecorator,
} 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 = {
const platforms = ['darwin', 'linux', 'win32'] as const;
export interface BinaryItem {
name: string;
isDir: boolean;
url: string;
size: string | number;
date: string;
ignoreDownloadStatuses?: number[];
};
}
export type FetchResult = {
export interface FetchResult {
items: BinaryItem[];
// oxlint-disable-next-line typescript-eslint/no-explicit-any
nextParams?: any;
};
}
export const BINARY_ADAPTER_ATTRIBUTE = Symbol('BINARY_ADAPTER_ATTRIBUTE');
export abstract class AbstractBinary {
protected httpclient: EggContextHttpClient;
@Inject()
protected logger: EggLogger;
protected binaryConfig: BinaryTaskConfig;
constructor(httpclient: EggContextHttpClient, logger: EggLogger, binaryConfig: BinaryTaskConfig) {
this.httpclient = httpclient;
this.logger = logger;
this.binaryConfig = binaryConfig;
@Inject()
protected httpclient: EggHttpClient;
abstract initFetch(binaryName: BinaryName): Promise<void>;
abstract fetch(
dir: string,
binaryName: BinaryName,
lastData?: Record<string, unknown>
): 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
}
abstract fetch(dir: string, params?: any): Promise<FetchResult | undefined>;
protected async requestXml(url: string) {
const { status, data, headers } = await this.httpclient.request(url, {
timeout: 20000,
timeout: 30_000,
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) {
// oxlint-disable-next-line typescript-eslint/no-explicit-any
protected async requestJSON<T = any>(
url: string,
requestHeaders?: Record<string, string>
): Promise<T> {
const { status, data, headers } = await this.httpclient.request(url, {
timeout: 20000,
timeout: 30_000,
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);
return data;
this.logger.warn(
'[AbstractBinary.requestJSON:non-200-status] url: %s, status: %s, headers: %j',
url,
status,
headers
);
return data as T;
}
return data;
return data as T;
}
// 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
const modulesVersion = Number.parseInt(version.modules);
// node v6.0.0 modules 48 min
if (modulesVersion >= 48 && !nodeABIVersions.includes(modulesVersion)) {
nodeABIVersions.push(modulesVersion);
}
@@ -73,25 +110,31 @@ export abstract class AbstractBinary {
protected listNodePlatforms() {
// https://nodejs.org/api/os.html#osplatform
return [ 'darwin', 'linux', 'win32' ];
return platforms;
}
protected listNodeArchs() {
if (this.binaryConfig.options?.nodeArchs) return this.binaryConfig.options.nodeArchs;
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
);

View File

@@ -1,19 +1,43 @@
import { EggContextHttpClient, EggLogger } from 'egg';
import { AbstractBinary, FetchResult, BinaryItem } from './AbstractBinary';
import { BinaryTaskConfig } from '../../../../config/binaries';
import { Inject, SingletonProto } from '@eggjs/tegg';
import type { EggAppConfig } from 'egg';
import { BinaryType } from '../../enum/Binary.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Api)
export class ApiBinary extends AbstractBinary {
private apiUrl: string;
constructor(httpclient: EggContextHttpClient, logger: EggLogger, binaryConfig: BinaryTaskConfig, apiUrl: string) {
super(httpclient, logger, binaryConfig);
this.apiUrl = apiUrl;
@Inject()
private readonly config: EggAppConfig;
async initFetch() {
// do nothing
return;
}
async fetch(dir: string): Promise<FetchResult | undefined> {
const url = `${this.apiUrl}/${this.binaryConfig.category}${dir}`;
async fetch(
dir: string,
binaryName: string,
lastData?: Record<string, unknown>
): Promise<FetchResult | undefined> {
const apiUrl =
this.config.cnpmcore.syncBinaryFromAPISource ||
`${this.config.cnpmcore.sourceRegistry}/-/binary`;
let url = `${apiUrl}/${binaryName}${dir}`;
if (lastData && lastData.lastSyncTime) {
url += `?since=${lastData.lastSyncTime}&limit=100`;
}
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[] = [];
@@ -22,6 +46,7 @@ export class ApiBinary extends AbstractBinary {
name: item.name,
isDir: item.type === 'dir',
url: item.url,
// oxlint-disable-next-line unicorn/explicit-length-check
size: item.size || '-',
date: item.date,
});

View File

@@ -1,22 +1,49 @@
import path from 'path';
import { AbstractBinary, FetchResult, BinaryItem } from './AbstractBinary';
import path from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import binaries, {
type BinaryName,
type BinaryTaskConfig,
} from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Bucket)
export class BucketBinary extends AbstractBinary {
async fetch(dir: string): Promise<FetchResult | undefined> {
// /foo/ => foo/
const subDir = dir.substring(1);
const url = `${this.binaryConfig.distUrl}?delimiter=/&prefix=${encodeURIComponent(subDir)}`;
const xml = await this.requestXml(url);
return { items: this.parseItems(xml, dir), nextParams: null };
async initFetch() {
// do nothing
return;
}
protected parseItems(xml: string, dir: string) {
async fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined> {
// /foo/ => foo/
const binaryConfig = binaries[binaryName];
const subDir = dir.slice(1);
const url = `${binaryConfig.distUrl}?delimiter=/&prefix=${encodeURIComponent(subDir)}`;
const xml = await this.requestXml(url);
return { items: this.parseItems(xml, dir, binaryConfig), nextParams: null };
}
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();
@@ -31,17 +58,18 @@ export class BucketBinary extends AbstractBinary {
const name = path.basename(fullname);
const date = m[2].trim();
const size = parseInt(m[3].trim());
const size = Number.parseInt(m[3].trim());
items.push({
name,
isDir: false,
url: `${this.binaryConfig.distUrl}${fullname}`,
url: `${binaryConfig.distUrl}${fullname}`,
size,
date,
});
}
// <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>
@@ -50,7 +78,7 @@ export class BucketBinary extends AbstractBinary {
const fullname = m[1].trim();
const name = `${path.basename(fullname)}/`;
const fullpath = `${dir}${name}`;
if (this.binaryConfig.ignoreDirs?.includes(fullpath)) continue;
if (binaryConfig.ignoreDirs?.includes(fullpath)) continue;
let date = '-';
// root dir children, should set date to '2022-04-19T01:00:00Z', sync per hour
if (dir === '/') {

View File

@@ -0,0 +1,178 @@
import { basename } from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.ChromeForTesting)
export class ChromeForTestingBinary extends AbstractBinary {
static lastTimestamp = '';
#timestamp = '';
private dirItems?: {
[key: string]: BinaryItem[];
};
async initFetch() {
this.dirItems = undefined;
}
async finishFetch(success: boolean) {
if (
success &&
this.#timestamp &&
ChromeForTestingBinary.lastTimestamp !== this.#timestamp
) {
ChromeForTestingBinary.lastTimestamp = this.#timestamp;
}
}
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: 30_000,
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;
}
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: `${item.version}/`,
date: item.revision,
size: '-',
isDir: true,
url: '',
});
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: download.url,
});
}
}
}
}
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,9 +1,22 @@
import { AbstractBinary, FetchResult, BinaryItem } from './AbstractBinary';
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Cypress)
export class CypressBinary extends AbstractBinary {
private dirItems: {
private dirItems?: {
[key: string]: BinaryItem[];
};
} | null;
async initFetch() {
this.dirItems = undefined;
}
async fetch(dir: string): Promise<FetchResult | undefined> {
if (!this.dirItems) {
@@ -12,7 +25,7 @@ export class CypressBinary extends AbstractBinary {
this.dirItems = {};
this.dirItems['/'] = [];
for (const version in data.versions) {
const major = parseInt(version.split('.', 1)[0]);
const major = Number.parseInt(version.split('.', 1)[0]);
// need >= 4.0.0
// https://npmmirror.com/mirrors/cypress/4.0.0/
if (major < 4) continue;
@@ -31,10 +44,26 @@ export class CypressBinary extends AbstractBinary {
// "https://cdn.cypress.io/desktop/4.0.0/darwin-x64/cypress.zip"
// "https://cdn.cypress.io/desktop/4.0.0/linux-x64/cypress.zip"
// "https://cdn.cypress.io/desktop/4.0.0/win32-x64/cypress.zip"
// "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', '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,225 @@
import path from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} 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: 30_000,
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.slice(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 = Number.parseInt(m[4].trim());
items.push({
name,
isDir: false,
url,
size,
date,
});
}
return items;
}
}

View File

@@ -1,9 +1,21 @@
import { BinaryItem, FetchResult } from './AbstractBinary';
import { GithubBinary } from './GithubBinary';
import { SingletonProto } from '@eggjs/tegg';
import binaries, { type BinaryName } from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import { GithubBinary } from './GithubBinary.js';
import {
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Electron)
export class ElectronBinary extends GithubBinary {
async fetch(dir: string): Promise<FetchResult | undefined> {
const releases = await this.initReleases();
async fetch(
dir: string,
binaryName: BinaryName = 'electron'
): Promise<FetchResult | undefined> {
const releases = await this.initReleases(binaryName, binaries.electron);
if (!releases) return;
let items: BinaryItem[] = [];
@@ -19,7 +31,7 @@ export class ElectronBinary extends GithubBinary {
// v14.2.6 => 14.2.6
if (/^v\d+?\./.test(item.tag_name)) {
items.push({
name: `${item.tag_name.substring(1)}/`,
name: `${item.tag_name.slice(1)}/`,
isDir: true,
url: item.url,
size: '-',
@@ -29,8 +41,11 @@ export class ElectronBinary extends GithubBinary {
}
} else {
for (const item of releases) {
if (dir === `/${item.tag_name}/` || dir === `/${item.tag_name.substring(1)}/`) {
items = this.formatItems(item);
if (
dir === `/${item.tag_name}/` ||
dir === `/${item.tag_name.slice(1)}/`
) {
items = this.formatItems(item, binaries.electron);
break;
}
}

View File

@@ -1,35 +1,84 @@
import { AbstractBinary, FetchResult, BinaryItem } from './AbstractBinary';
import { SingletonProto } from '@eggjs/tegg';
import binaries, {
type BinaryName,
type BinaryTaskConfig,
} from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.GitHub)
export class GithubBinary extends AbstractBinary {
private releases?: any[];
// oxlint-disable-next-line typescript-eslint/no-explicit-any
private releases: Record<string, any[] | undefined> = {};
protected async initReleases() {
if (!this.releases) {
async initFetch(binaryName: BinaryName) {
this.releases[binaryName] = undefined;
}
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
// https://api.github.com/repos/electron/electron/releases?per_page=100&page=3
// oxlint-disable-next-line typescript-eslint/no-explicit-any
let releases: any[] = [];
const maxPage = this.binaryConfig.options?.maxPage || 1;
const maxPage = binaryConfig.options?.maxPage || 1;
for (let i = 0; i < maxPage; i++) {
const url = `https://api.github.com/repos/${this.binaryConfig.repo}/releases?per_page=100&page=${i + 1}`;
const data = await this.requestJSON(url);
const url = `https://api.github.com/repos/${binaryConfig.repo}/releases?per_page=100&page=${i + 1}`;
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)) {
this.logger.warn('[GithubBinary.fetch:response-data-not-array] data: %j, url: %s', data, url);
// {"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
);
return;
}
this.logger.warn(
'[GithubBinary.fetch:response-data-not-array] data: %j, url: %s',
data,
url
);
return;
}
releases = releases.concat(data);
}
this.releases = releases;
this.releases[binaryName] = releases;
}
return this.releases;
return this.releases[binaryName];
}
protected formatItems(releaseItem: any) {
// oxlint-disable-next-line typescript-eslint/no-explicit-any
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,
@@ -45,7 +94,7 @@ export class GithubBinary extends AbstractBinary {
items.push({
name: `${releaseItem.tag_name}.tar.gz`,
isDir: false,
url: `https://github.com/${this.binaryConfig.repo}/archive/${releaseItem.tag_name}.tar.gz`,
url: `https://github.com/${binaryConfig.repo}/archive/${releaseItem.tag_name}.tar.gz`,
size: '-',
date: releaseItem.published_at,
});
@@ -54,7 +103,7 @@ export class GithubBinary extends AbstractBinary {
items.push({
name: `${releaseItem.tag_name}.zip`,
isDir: false,
url: `https://github.com/${this.binaryConfig.repo}/archive/${releaseItem.tag_name}.zip`,
url: `https://github.com/${binaryConfig.repo}/archive/${releaseItem.tag_name}.zip`,
size: '-',
date: releaseItem.published_at,
});
@@ -62,8 +111,12 @@ export class GithubBinary extends AbstractBinary {
return items;
}
async fetch(dir: string): Promise<FetchResult | undefined> {
const releases = await this.initReleases();
async fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined> {
const binaryConfig = binaries[binaryName];
const releases = await this.initReleases(binaryName, binaryConfig);
if (!releases) return;
let items: BinaryItem[] = [];
@@ -80,7 +133,7 @@ export class GithubBinary extends AbstractBinary {
} else {
for (const item of releases) {
if (dir === `/${item.tag_name}/`) {
items = this.formatItems(item);
items = this.formatItems(item, binaryConfig);
break;
}
}

View File

@@ -1,94 +1,109 @@
import { AbstractBinary, FetchResult, BinaryItem } from './AbstractBinary';
import { SingletonProto } from '@eggjs/tegg';
import binaries, { type BinaryName } from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Imagemin)
export class ImageminBinary extends AbstractBinary {
private dirItems: {
[key: string]: BinaryItem[];
};
async initFetch() {
// do nothing
return;
}
async fetch(dir: string): Promise<FetchResult | undefined> {
if (!this.dirItems) {
this.dirItems = {};
const npmPackageName = this.binaryConfig.options?.npmPackageName ?? this.binaryConfig.category;
const pkgUrl = `https://registry.npmjs.com/${npmPackageName}`;
const data = await this.requestJSON(pkgUrl);
this.dirItems = {};
this.dirItems['/'] = [];
// mini version 4.0.0
// https://github.com/imagemin/jpegtran-bin/blob/v4.0.0/lib/index.js
// https://github.com/imagemin/pngquant-bin/blob/v4.0.0/lib/index.js
for (const version in data.versions) {
const major = parseInt(version.split('.', 1)[0]);
if (major < 4) continue;
// >= 4.0.0
const date = data.time[version];
// https://raw.githubusercontent.com/imagemin/jpegtran-bin/v${pkg.version}/vendor/`
this.dirItems['/'].push({
name: `v${version}/`,
async fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined> {
const binaryConfig = binaries[binaryName];
const dirItems: {
[key: string]: BinaryItem[];
} = {};
const npmPackageName = binaryConfig.options?.npmPackageName ?? binaryName;
const pkgUrl = `https://registry.npmjs.com/${npmPackageName}`;
const data = await this.requestJSON(pkgUrl);
dirItems['/'] = [];
// mini version 4.0.0
// https://github.com/imagemin/jpegtran-bin/blob/v4.0.0/lib/index.js
// https://github.com/imagemin/pngquant-bin/blob/v4.0.0/lib/index.js
for (const version in data.versions) {
const major = Number.parseInt(version.split('.', 1)[0]);
if (major < 4) continue;
// >= 4.0.0
const date = data.time[version];
// https://raw.githubusercontent.com/imagemin/jpegtran-bin/v${pkg.version}/vendor/`
dirItems['/'].push({
name: `v${version}/`,
date,
size: '-',
isDir: true,
url: '',
});
const versionDir = `/v${version}/`;
dirItems[versionDir] = [];
dirItems[versionDir].push({
name: 'vendor/',
date,
size: '-',
isDir: true,
url: '',
});
const versionVendorDir = `/v${version}/vendor/`;
dirItems[versionVendorDir] = [];
for (const platform of binaryConfig.options?.nodePlatforms ?? []) {
dirItems[versionVendorDir].push({
name: `${platform}/`,
date,
size: '-',
isDir: true,
url: '',
});
const versionDir = `/v${version}/`;
this.dirItems[versionDir] = [];
this.dirItems[versionDir].push({
name: 'vendor/',
date,
size: '-',
isDir: true,
url: '',
});
const versionVendorDir = `/v${version}/vendor/`;
this.dirItems[versionVendorDir] = [];
for (const platform of this.binaryConfig.options!.nodePlatforms!) {
this.dirItems[versionVendorDir].push({
name: `${platform}/`,
date,
size: '-',
isDir: true,
url: '',
});
const platformDir = `/v${version}/vendor/${platform}/`;
this.dirItems[platformDir] = [];
const archs = this.binaryConfig.options!.nodeArchs![platform];
if (archs.length === 0) {
for (const name of this.binaryConfig.options!.binFiles![platform]) {
this.dirItems[platformDir].push({
const platformDir = `/v${version}/vendor/${platform}/`;
dirItems[platformDir] = [];
const archs = binaryConfig.options?.nodeArchs?.[platform] ?? [];
if (archs.length === 0) {
for (const name of binaryConfig.options?.binFiles?.[platform] ?? []) {
dirItems[platformDir].push({
name,
date,
size: '-',
isDir: false,
url: `${binaryConfig.distUrl}/${binaryConfig.repo}${platformDir}${name}`,
ignoreDownloadStatuses: [404],
});
}
} else {
for (const arch of archs) {
dirItems[platformDir].push({
name: `${arch}/`,
date,
size: '-',
isDir: true,
url: '',
});
const platformArchDir = `/v${version}/vendor/${platform}/${arch}/`;
dirItems[platformArchDir] = [];
for (const name of binaryConfig.options?.binFiles?.[platform] ??
[]) {
dirItems[platformArchDir].push({
name,
date,
size: '-',
isDir: false,
url: `${this.binaryConfig.distUrl}/${this.binaryConfig.repo}${platformDir}${name}`,
ignoreDownloadStatuses: [ 404 ],
url: `${binaryConfig.distUrl}/${binaryConfig.repo}${platformArchDir}${name}`,
ignoreDownloadStatuses: [404],
});
}
} else {
for (const arch of archs) {
this.dirItems[platformDir].push({
name: `${arch}/`,
date,
size: '-',
isDir: true,
url: '',
});
const platformArchDir = `/v${version}/vendor/${platform}/${arch}/`;
this.dirItems[platformArchDir] = [];
for (const name of this.binaryConfig.options!.binFiles![platform]) {
this.dirItems[platformArchDir].push({
name,
date,
size: '-',
isDir: false,
url: `${this.binaryConfig.distUrl}/${this.binaryConfig.repo}${platformArchDir}${name}`,
ignoreDownloadStatuses: [ 404 ],
});
}
}
}
}
}
}
return { items: this.dirItems[dir] };
return { items: dirItems[dir] };
}
}

View File

@@ -1,8 +1,28 @@
import { AbstractBinary, FetchResult, BinaryItem } from './AbstractBinary';
import { basename } from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import binaries, { type BinaryName } from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Node)
export class NodeBinary extends AbstractBinary {
async fetch(dir: string): Promise<FetchResult | undefined> {
const url = `${this.binaryConfig.distUrl}${dir}`;
async initFetch() {
// do nothing
return;
}
async fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined> {
const binaryConfig = binaries[binaryName];
const url = `${binaryConfig.distUrl}${dir}`;
const html = await this.requestXml(url);
// <a href="v9.8.0/">v9.8.0/</a> 08-Mar-2018 01:55 -
// <a href="v9.9.0/">v9.9.0/</a> 21-Mar-2018 15:47 -
@@ -10,25 +30,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 (this.binaryConfig.ignoreFiles?.includes(`${dir}${name}`)) 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,171 +1,264 @@
import { AbstractBinary, FetchResult, BinaryItem } from './AbstractBinary';
import { join } from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import binaries, { type BinaryName } from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.NodePreGyp)
export class NodePreGypBinary extends AbstractBinary {
private dirItems: {
[key: string]: BinaryItem[];
};
async initFetch() {
// do nothing
return;
}
// https://github.com/mapbox/node-pre-gyp
async fetch(dir: string): Promise<FetchResult | undefined> {
if (!this.dirItems) {
this.dirItems = {};
const pkgUrl = `https://registry.npmjs.com/${this.binaryConfig.category}`;
const data = await this.requestJSON(pkgUrl);
this.dirItems = {};
this.dirItems['/'] = [];
const nodeABIVersions = await this.listNodeABIVersions();
const nodePlatforms = this.listNodePlatforms();
const nodeArchs = this.listNodeArchs();
const nodeLibcs = this.listNodeLibcs();
for (const version in data.versions) {
const date = data.time[version];
const pkgVersion = data.versions[version];
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';
if (!binaryFile) continue;
const moduleName = pkgVersion.binary.module_name || pkgVersion.name;
binaryFile = binaryFile.replace('{version}', version)
.replace('{module_name}', moduleName);
async fetch(
dir: string,
binaryName: BinaryName
): Promise<FetchResult | undefined> {
const binaryConfig = binaries[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[];
} = {
'/': [],
};
const nodeABIVersions = await this.listNodeABIVersions();
const nodePlatforms = this.listNodePlatforms();
const nodeArchs = this.listNodeArchs(binaryConfig);
const nodeLibcs = this.listNodeLibcs();
for (const version in data.versions) {
const date = data.time[version];
const pkgVersion = data.versions[version];
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';
if (!binaryFile) continue;
const moduleName = pkgVersion.binary.module_name || pkgVersion.name;
binaryFile = binaryFile
.replace('{version}', version)
.replace('{module_name}', moduleName);
let currentDir = this.dirItems['/'];
let versionPrefix = '';
const remotePath = pkgVersion.binary.remote_path;
const napiVersions = pkgVersion.binary.napi_versions ?? [];
if (this.binaryConfig.options?.requiredNapiVersions && napiVersions.length === 0) continue;
if (remotePath?.includes('{version}')) {
const dirName = remotePath.includes('v{version}') ? `v${version}` : version;
versionPrefix = `/${dirName}`;
this.dirItems['/'].push({
name: `${dirName}/`,
date,
size: '-',
isDir: true,
url: '',
});
currentDir = this.dirItems[`/${dirName}/`] = [];
}
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 (remotePath?.includes('{version}')) {
const dirName = remotePath.includes('v{version}')
? `v${version}`
: version;
versionPrefix = `/${dirName}`;
dirItems['/'].push({
name: `${dirName}/`,
date,
size: '-',
isDir: true,
url: '',
});
currentDir = [];
dirItems[`/${dirName}/`] = currentDir;
}
// https://node-precompiled-binaries.grpc.io/?delimiter=/&prefix=grpc/v1.24.11/
// https://github.com/grpc/grpc-node/blob/grpc%401.24.x/packages/grpc-native-core/package.json#L50
// "binary": {
// "module_name": "grpc_node",
// "module_path": "src/node/extension_binary/{node_abi}-{platform}-{arch}-{libc}",
// "host": "https://node-precompiled-binaries.grpc.io/",
// "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}')) {
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}`)
.replace('{platform}', platform)
.replace('{arch}', arch)
.replace('{libc}', libc);
currentDir.push({
name,
date,
size: '-',
isDir: false,
url: `${this.binaryConfig.distUrl}/${this.binaryConfig.category}${versionPrefix}/${name}`,
ignoreDownloadStatuses: [ 404 ],
});
}
}
}
}
} 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}`)
.replace('{platform}', platform)
.replace('{arch}', arch);
currentDir.push({
name,
date,
size: '-',
isDir: false,
url: `${this.binaryConfig.distUrl}/${this.binaryConfig.category}${versionPrefix}/${name}`,
ignoreDownloadStatuses: [ 404 ],
});
}
}
}
} 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": {
// "module_name": "index",
// "module_path": "./lib/v{napi_build_version}",
// "remote_path": "./v{version}",
// "package_name": "{platform}-{arch}-{node_napi_label}-{libc}.tar.gz",
// "host": "https://skia-canvas.s3.us-east-1.amazonaws.com",
// "napi_versions": [
// 6
// ]
// },
// https://node-precompiled-binaries.grpc.io/?delimiter=/&prefix=grpc/v1.24.11/
// https://github.com/grpc/grpc-node/blob/grpc%401.24.x/packages/grpc-native-core/package.json#L50
// "binary": {
// "module_name": "grpc_node",
// "module_path": "src/node/extension_binary/{node_abi}-{platform}-{arch}-{libc}",
// "host": "https://node-precompiled-binaries.grpc.io/",
// "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}')
) {
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) {
for (const napiVersion of napiVersions) {
const name = binaryFile.replace('{platform}', platform)
.replace('{arch}', arch)
.replace('{node_napi_label}', `napi-v${napiVersion}`)
.replace('{libc}', libc);
currentDir.push({
name,
date,
size: '-',
isDir: false,
url: `${this.binaryConfig.distUrl}${versionPrefix}/${name}`,
ignoreDownloadStatuses: [ 404, 403 ],
});
}
const name = binaryFile
.replace('{node_abi}', `node-v${nodeAbi}`)
.replace('{platform}', platform)
.replace('{arch}', arch)
.replace('{libc}', libc);
currentDir.push({
name,
date,
size: '-',
isDir: false,
url: `${binaryConfig.distUrl}/${binaryName}${versionPrefix}/${name}`,
ignoreDownloadStatuses: [404],
});
}
}
}
} 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",
// "host": "https://node-precompiled-binaries.grpc.io/",
// "remote_path": "{name}/v{version}",
// "package_name": "{platform}-{arch}.tar.gz",
// "module_path": "bin"
// },
}
} 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('{platform}', platform)
const name = binaryFile
.replace('{node_abi}', `node-v${nodeAbi}`)
.replace('{platform}', platform)
.replace('{arch}', arch);
currentDir.push({
name,
date,
size: '-',
isDir: false,
url: `${this.binaryConfig.distUrl}/${this.binaryConfig.category}${versionPrefix}/${name}`,
ignoreDownloadStatuses: [ 404 ],
url: `${binaryConfig.distUrl}/${binaryName}${versionPrefix}/${name}`,
ignoreDownloadStatuses: [404],
});
}
}
}
} 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": {
// "module_name": "index",
// "module_path": "./lib/v{napi_build_version}",
// "remote_path": "./v{version}",
// "package_name": "{platform}-{arch}-{node_napi_label}-{libc}.tar.gz",
// "host": "https://skia-canvas.s3.us-east-1.amazonaws.com",
// "napi_versions": [
// 6
// ]
// },
for (const platform of nodePlatforms) {
const archs = nodeArchs[platform];
const libcs = nodeLibcs[platform];
for (const arch of archs) {
for (const libc of libcs) {
for (const napiVersion of napiVersions) {
const name = binaryFile
.replace('{platform}', platform)
.replace('{arch}', arch)
.replace('{node_napi_label}', `napi-v${napiVersion}`)
.replace('{libc}', libc);
currentDir.push({
name,
date,
size: '-',
isDir: false,
url: `${binaryConfig.distUrl}${versionPrefix}/${name}`,
ignoreDownloadStatuses: [404, 403],
});
}
}
}
}
} else if (binaryFile.includes('{platform}-{arch}-{node_napi_label}')) {
// "_id": "skia-canvas@0.9.22",
// "binary": {
// "module_name": "index",
// "module_path": "./lib/v{napi_build_version}",
// "remote_path": "./v{version}",
// "package_name": "{platform}-{arch}-{node_napi_label}.tar.gz",
// "host": "https://skia-canvas.s3.us-east-1.amazonaws.com",
// "napi_versions": [
// 6
// ]
// },
for (const platform of nodePlatforms) {
const archs = nodeArchs[platform];
for (const arch of archs) {
for (const napiVersion of napiVersions) {
const binaryFileName = binaryFile
.replace('{platform}', platform)
.replace('{arch}', arch)
.replace('{node_napi_label}', napiVersion);
remotePath = remotePath
.replace('{module_name}', moduleName)
.replace('{name}', binaryName)
.replace('{version}', version)
.replace('{configuration}', 'Release');
const binaryFilePath = join('/', remotePath, binaryFileName);
const remoteUrl = `${binaryConfig.distUrl}${binaryFilePath}`;
currentDir.push({
name: binaryFileName,
date,
size: '-',
isDir: false,
url: remoteUrl,
ignoreDownloadStatuses: [404],
});
}
}
}
} 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",
// "host": "https://node-precompiled-binaries.grpc.io/",
// "remote_path": "{name}/v{version}",
// "package_name": "{platform}-{arch}.tar.gz",
// "module_path": "bin"
// },
// handle {configuration}
// "binary": {
// "module_name": "wrtc",
// "module_path": "./build/{configuration}/",
// "remote_path": "./{module_name}/v{version}/{configuration}/",
// "package_name": "{platform}-{arch}.tar.gz",
// "host": "https://node-webrtc.s3.amazonaws.com"
// },
for (const platform of nodePlatforms) {
const archs = nodeArchs[platform];
for (const arch of archs) {
const binaryFileName = binaryFile
.replace('{platform}', platform)
.replace('{arch}', arch);
remotePath = remotePath
.replace('{module_name}', moduleName)
.replace('{name}', binaryName)
.replace('{version}', version)
.replace('{configuration}', 'Release');
const binaryFilePath = join('/', remotePath, binaryFileName);
const remoteUrl = `${binaryConfig.distUrl}${binaryFilePath}`;
currentDir.push({
name: binaryFileName,
date,
size: '-',
isDir: false,
url: remoteUrl,
ignoreDownloadStatuses: [404],
});
}
}
}
}
return { items: this.dirItems[dir] };
return { items: dirItems[dir] };
}
}

View File

@@ -1,14 +1,26 @@
import { FetchResult, BinaryItem } from './AbstractBinary';
import { BucketBinary } from './BucketBinary';
import { SingletonProto } from '@eggjs/tegg';
import binaries from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import {
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
import { BucketBinary } from './BucketBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Nwjs)
export class NwjsBinary extends BucketBinary {
private s3Url = 'https://nwjs2.s3.amazonaws.com/?delimiter=/&prefix=';
async fetch(dir: string): Promise<FetchResult | undefined> {
const binaryConfig = binaries.nwjs;
const isRootDir = dir === '/';
// /foo/ => foo/
const subDir = dir.substring(1);
const url = isRootDir ? this.binaryConfig.distUrl : `${this.s3Url}${encodeURIComponent(subDir)}`;
const subDir = dir.slice(1);
const url = isRootDir
? binaryConfig.distUrl
: `${this.s3Url}${encodeURIComponent(subDir)}`;
const xml = await this.requestXml(url);
if (!xml) return;
@@ -19,7 +31,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();
@@ -37,6 +50,6 @@ export class NwjsBinary extends BucketBinary {
return { items, nextParams: null };
}
return { items: this.parseItems(xml, dir), nextParams: null };
return { items: this.parseItems(xml, dir, binaryConfig), nextParams: null };
}
}

View File

@@ -1,7 +1,13 @@
import { AbstractBinary, 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.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
const PACKAGE_URL = 'https://registry.npmjs.com/playwright-core';
const DOWNLOAD_HOST = 'https://playwright.azureedge.net/';
@@ -9,164 +15,408 @@ 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 initFetch() {
this.dirItems = undefined;
}
async fetch(dir: string): Promise<FetchResult | undefined> {
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: [
@@ -181,23 +431,73 @@ export class PlaywrightBinary extends AbstractBinary {
browsers.push(...data.browsers);
})
.catch(err => {
this.logger.warn('[PlaywrightBinary.fetch:error] Playwright version %s browser data request failed: %s', version, err);
}),
),
/* c8 ignore next 2 */
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.some(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.some(
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: '',
@@ -206,8 +506,14 @@ 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 });
if (!this.dirItems[dir].some(item => item.name === name)) {
this.dirItems[dir].push({
name,
isDir: false,
url,
size: '-',
date: itemDate,
});
}
}
}
@@ -216,4 +522,3 @@ export class PlaywrightBinary extends AbstractBinary {
return { items: this.dirItems[dir] ?? [], nextParams: null };
}
}

View File

@@ -0,0 +1,143 @@
import path from 'node:path';
import { SingletonProto } from '@eggjs/tegg';
import binaries, { type BinaryName } from '../../../../config/binaries.js';
import { BinaryType } from '../../enum/Binary.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} 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 = Number.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.slice(1);
const url = `${binaryConfig.distUrl}?delimiter=/&prefix=${encodeURIComponent(subDir)}`;
const result = await this.requestJSON(url);
return { items: this.#parseItems(result), nextParams: null };
}
// oxlint-disable-next-line typescript-eslint/no-explicit-any
#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,76 +1,51 @@
import { AbstractBinary, FetchResult, BinaryItem } from './AbstractBinary';
import { SingletonProto } from '@eggjs/tegg';
import { XMLParser } from 'fast-xml-parser';
import { BinaryType } from '../../enum/Binary.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
export const platforms = ['Linux_x64', 'Mac', 'Mac_Arm', 'Win', 'Win_x64'];
const MAX_DEPTH = 1;
@SingletonProto()
@BinaryAdapter(BinaryType.Puppeteer)
export class PuppeteerBinary extends AbstractBinary {
private dirItems: {
private dirItems?: {
[key: string]: BinaryItem[];
};
async fetch(dir: string): Promise<FetchResult | undefined> {
async initFetch() {
this.dirItems = undefined;
}
async fetch(
dir: string,
_binaryName: string,
lastData?: Record<string, unknown>
): Promise<FetchResult | undefined> {
if (!this.dirItems) {
const pkgUrl = 'https://registry.npmjs.com/puppeteer';
const data = await this.requestJSON(pkgUrl);
const s3Url = 'https://chromium-browser-snapshots.storage.googleapis.com';
const chromiumRevisions = new Map<string, string>();
this.dirItems = {};
this.dirItems['/'] = [];
const chromiumRevisions = new Map<string, string>();
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) : '';
if (revision && !chromiumRevisions.has(revision)) {
chromiumRevisions.set(revision, data.time[version]);
}
}
// https://unpkg.com/puppeteer@5.1.0/lib/cjs/revisions.js
// https://unpkg.com/puppeteer@latest/lib/cjs/puppeteer/revisions.js
// exports.PUPPETEER_REVISIONS = {
// chromium: '768783',
// firefox: 'latest',
// };
const unpkgURL = 'https://unpkg.com/puppeteer@latest/lib/cjs/puppeteer/revisions.js';
const text = await this.requestXml(unpkgURL);
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 lastRevision = await this.requestXml(LAST_CHANGE_URL);
if (lastRevision) {
chromiumRevisions.set(lastRevision, new Date().toISOString());
}
// old versions
// v5.0.0
chromiumRevisions.set('756035', data.time['5.0.0']);
// v5.2.0
chromiumRevisions.set('768783', data.time['5.2.0']);
// v5.2.1
chromiumRevisions.set('782078', data.time['5.2.1']);
// v5.3.0
chromiumRevisions.set('800071', data.time['5.3.0']);
// v5.4.0
chromiumRevisions.set('809590', data.time['5.4.0']);
// v5.5.0
chromiumRevisions.set('818858', data.time['5.5.0']);
// v6.0.0
chromiumRevisions.set('843427', data.time['6.0.0']);
// "7.0.0"
chromiumRevisions.set('848005', data.time['7.0.0']);
// https://github.com/puppeteer/puppeteer/blob/v8.0.0/src/revisions.ts#L23
// "8.0.0":"2021-02-26T08:36:50.107Z"
chromiumRevisions.set('856583', data.time['8.0.0']);
// "9.0.0":"2021-04-21T11:27:32.513Z"
chromiumRevisions.set('869685', data.time['9.0.0']);
// "10.0.0":"2021-05-31T12:42:27.486Z"
chromiumRevisions.set('884014', data.time['10.0.0']);
// "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' ];
for (const platform of platforms) {
const revision = lastData?.[platform] as string;
if (!revision) {
// 丢弃库中历史不带 lastData 的任务,防止遍历任务过多
this.logger.info(
'drop puppeteer task if has no last data for platform %s, lastPlatform',
platform,
lastData
);
return;
}
let marker = revision ? `${platform}/${revision}/REVISIONS` : undefined;
this.dirItems['/'].push({
name: `${platform}/`,
date: new Date().toISOString(),
@@ -79,8 +54,35 @@ export class PuppeteerBinary extends AbstractBinary {
url: '',
});
this.dirItems[`/${platform}/`] = [];
let i = 0;
do {
let requestUrl = s3Url + '?prefix=' + platform + '&max-keys=100';
if (marker) {
requestUrl += '&marker=' + marker;
}
const xml = await this.requestXml(requestUrl);
const parser = new XMLParser();
const obj = parser.parse(xml);
if (
obj.ListBucketResult.IsTruncated === true &&
obj.ListBucketResult.NextMarker
) {
marker = obj.ListBucketResult.NextMarker;
} else {
marker = undefined;
}
for (const content of obj.ListBucketResult.Contents) {
// /Linux_x64/1041455/REVISIONS
if (content.Key.endsWith('/REVISIONS')) {
const revision = content.Key.split('/')[1].trim();
chromiumRevisions.set(revision, content.LastModified);
}
}
// 最多遍历 100 次防止内存爆炸,下次同步任务会继续
} while (i++ < MAX_DEPTH && marker !== undefined);
}
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',
@@ -105,7 +107,7 @@ export class PuppeteerBinary extends AbstractBinary {
size: '-',
isDir: false,
url: `https://storage.googleapis.com/chromium-browser-snapshots/${platform}/${revision}/${name}`,
ignoreDownloadStatuses: [ 404 ],
ignoreDownloadStatuses: [404],
},
];
}
@@ -116,15 +118,14 @@ 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') {
// Windows archive name changed at r591479.
return parseInt(revision, 10) > 591479 ? 'chrome-win' : 'chrome-win32';
return Number.parseInt(revision, 10) > 591_479
? 'chrome-win'
: 'chrome-win32';
}
return '';
}

View File

@@ -1,79 +1,91 @@
import { AbstractBinary, FetchResult, BinaryItem } from './AbstractBinary';
import { SingletonProto } from '@eggjs/tegg';
import { BinaryType } from '../../enum/Binary.js';
import {
AbstractBinary,
BinaryAdapter,
type BinaryItem,
type FetchResult,
} from './AbstractBinary.js';
@SingletonProto()
@BinaryAdapter(BinaryType.Sqlcipher)
export class SqlcipherBinary extends AbstractBinary {
private dirItems: {
[key: string]: BinaryItem[];
};
async initFetch() {
// do nothing
return;
}
async fetch(dir: string): Promise<FetchResult | undefined> {
if (!this.dirItems) {
this.dirItems = {};
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);
this.dirItems = {};
this.dirItems['/'] = [];
// https://github.com/journeyapps/node-sqlcipher/blob/master/.circleci/config.yml#L407
// https://github.com/journeyapps/node-sqlcipher/issues/35#issuecomment-698924173
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-darwin-arm64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-darwin-arm64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-darwin-x64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-darwin-x64.tar.gz
const dirItems: {
[key: string]: BinaryItem[];
} = {
'/': [],
};
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
// https://github.com/journeyapps/node-sqlcipher/issues/35#issuecomment-698924173
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-darwin-arm64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-darwin-arm64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-darwin-x64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-darwin-x64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-linux-x64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-linux-x64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-linux-x64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-linux-x64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-win32-arm64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-win32-arm64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-win32-ia32.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-win32-ia32.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-win32-x64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-win32-x64.tar.gz
const nodePlatformAndArchs = [
'linux-x64',
'darwin-x64',
'darwin-arm64',
'win32-x64',
'win32-arm64',
'win32-ia32',
];
for (const version in data.versions) {
const major = parseInt(version.split('.', 1)[0]);
if (major < 5) continue;
// >= 5.0.0
const pkgVersion = data.versions[version];
const napiVersions = pkgVersion.binary && pkgVersion.binary.napi_versions || [];
const date = data.time[version];
this.dirItems['/'].push({
name: `v${version}/`,
date,
size: '-',
isDir: true,
url: '',
});
const versionDir = `/v${version}/`;
this.dirItems[versionDir] = [];
for (const nodePlatformAndArch of nodePlatformAndArchs) {
// napi
for (const napiVersion of napiVersions) {
// >= 5.0.0
// "package_name": "napi-v{napi_build_version}-{platform}-{arch}.tar.gz",
// "napi_versions": [
// 3, 6
// ]
const name = `napi-v${napiVersion}-${nodePlatformAndArch}.tar.gz`;
this.dirItems[versionDir].push({
name,
date,
size: '-',
isDir: false,
url: `${s3Url}/v${version}/${name}`,
ignoreDownloadStatuses: [ 404, 403 ],
});
}
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-win32-arm64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-win32-arm64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-win32-ia32.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-win32-ia32.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v3-win32-x64.tar.gz
// https://journeyapps-node-binary.s3.amazonaws.com/@journeyapps/sqlcipher/v5.3.0/napi-v6-win32-x64.tar.gz
const nodePlatformAndArchs = [
'linux-x64',
'darwin-x64',
'darwin-arm64',
'win32-x64',
'win32-arm64',
'win32-ia32',
];
for (const version in data.versions) {
const major = Number.parseInt(version.split('.', 1)[0]);
if (major < 5) continue;
// >= 5.0.0
const pkgVersion = data.versions[version];
const napiVersions =
(pkgVersion.binary && pkgVersion.binary.napi_versions) || [];
const date = data.time[version];
dirItems['/'].push({
name: `v${version}/`,
date,
size: '-',
isDir: true,
url: '',
});
const versionDir = `/v${version}/`;
dirItems[versionDir] = [];
for (const nodePlatformAndArch of nodePlatformAndArchs) {
// napi
for (const napiVersion of napiVersions) {
// >= 5.0.0
// "package_name": "napi-v{napi_build_version}-{platform}-{arch}.tar.gz",
// "napi_versions": [
// 3, 6
// ]
const name = `napi-v${napiVersion}-${nodePlatformAndArch}.tar.gz`;
dirItems[versionDir].push({
name,
date,
size: '-',
isDir: false,
url: `${s3Url}/v${version}/${name}`,
ignoreDownloadStatuses: [404, 403],
});
}
}
}
return { items: this.dirItems[dir] };
return { items: dirItems[dir] };
}
}

View File

@@ -0,0 +1,49 @@
import {
Inject,
QualifierImplDecoratorUtil,
type ImplDecorator,
} 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 interface ChangesStreamChange {
seq: string;
fullname: string;
}
export abstract class AbstractChangeStream {
@Inject()
protected logger: EggLogger;
@Inject()
protected httpclient: EggHttpClient;
abstract getInitialSince(registry: Registry): Promise<string>;
abstract fetchChanges(
registry: Registry,
since: string
): AsyncGenerator<ChangesStreamChange>;
getChangesStreamUrl(
registry: Registry,
since: string,
limit?: number
): string {
const url = new URL(registry.changeStream);
url.searchParams.set('since', since);
if (limit) {
url.searchParams.set('limit', String(limit));
}
return url.toString();
}
}
export const RegistryChangesStream: ImplDecorator<
AbstractChangeStream,
typeof RegistryType
> = QualifierImplDecoratorUtil.generatorDecorator(
AbstractChangeStream,
CHANGE_STREAM_ATTRIBUTE
);

View File

@@ -0,0 +1,59 @@
import { SingletonProto } from '@eggjs/tegg';
import { E500 } from 'egg-errors';
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 { status, data } = await this.httpclient.request(db, {
followRedirect: true,
timeout: 10_000,
dataType: 'json',
});
if (!data.update_seq) {
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
);
return since;
}
async *fetchChanges(registry: Registry, since: string) {
const db = this.getChangesStreamUrl(registry, since);
// json mode
const { data } = await this.httpclient.request(db, {
followRedirect: true,
timeout: 30_000,
dataType: 'json',
gzip: true,
});
if (data.results?.length > 0) {
for (const change of data.results) {
const seq = String(change.seq);
const fullname = change.id;
// cnpmcore 默认返回 >= 需要做特殊判断
if (seq && fullname && seq !== since) {
const change = {
fullname,
seq,
};
yield change;
}
}
}
}
}

View File

@@ -0,0 +1,85 @@
import { SingletonProto } from '@eggjs/tegg';
import { E500 } from 'egg-errors';
import { RegistryType } from '../../../common/enum/Registry.js';
import type { Registry } from '../../../core/entity/Registry.js';
import {
AbstractChangeStream,
RegistryChangesStream,
} from './AbstractChangesStream.js';
const MAX_LIMIT = 10_000;
interface 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(Date.now());
this.logger.warn(
`[CnpmjsorgChangesStream.getInitialSince] since: ${since}, skip query ${registry.changeStream}`
);
return since;
}
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}`
);
}
const db = this.getChangesStreamUrl(registry, since, limit);
// json mode
const res = await this.httpclient.request<FetchResults>(db, {
followRedirect: true,
timeout: 30_000,
dataType: 'json',
gzip: true,
});
const { results = [] } = res.data;
if (results?.length >= limit) {
const [first] = results;
const last = results[results.length - 1];
if (first.gmt_modified === last.gmt_modified) {
return await this.tryFetch(registry, since, limit + 1000);
}
}
return res;
}
async *fetchChanges(registry: Registry, since: string) {
// ref: https://github.com/cnpm/cnpmjs.org/pull/1734
// 由于 cnpmjsorg 无法计算准确的 seq
// since 是一个时间戳,需要确保一次返回的结果中首尾两个 gmtModified 不相等
const { data } = await this.tryFetch(registry, since);
if (data.results?.length > 0) {
for (const change of data.results) {
const seq = new Date(change.gmt_modified).getTime() + '';
const fullname = change.id;
if (seq && fullname && seq !== since) {
const change = {
fullname,
seq,
};
yield change;
}
}
}
}
}

View File

@@ -0,0 +1,84 @@
import { SingletonProto } from '@eggjs/tegg';
import { E500 } from 'egg-errors';
import { RegistryType } from '../../../common/enum/Registry.js';
import type { Registry } from '../../../core/entity/Registry.js';
import {
AbstractChangeStream,
RegistryChangesStream,
type ChangesStreamChange,
} 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 { status, data } = await this.httpclient.request(db, {
followRedirect: true,
timeout: 10_000,
dataType: 'json',
headers: {
'npm-replication-opt-in': 'true',
},
});
const since = String(data.update_seq - 10);
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
);
return since;
}
async *fetchChanges(
registry: Registry,
since: string
): AsyncGenerator<ChangesStreamChange> {
const db = this.getChangesStreamUrl(registry, since);
const { data, headers } = await this.httpclient.request(db, {
timeout: 60_000,
headers: {
// https://github.com/orgs/community/discussions/152515
'npm-replication-opt-in': 'true',
},
dataType: 'json',
gzip: true,
});
const count = data.results?.length;
const last_seq = data.last_seq;
this.logger.info(
'[NpmChangesStream.fetchChanges] %s, count: %s, last_seq: %s, headers: %j',
db,
count,
last_seq,
headers
);
if (data.results?.length > 0) {
for (const change of data.results) {
// {
// seq: 2495018,
// id: 'ng-create-all-project',
// changes: [ { rev: '3-be3a014aab8e379ba28a28adb8e10142' }, [length]: 1 ],
// deleted: true
// },
const seq = String(change.seq);
const fullname = change.id;
if (seq && fullname && seq !== since) {
const change = {
fullname,
seq,
};
yield change;
}
}
}
}
}

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 { Advice, type AdviceContext, type IAdvice } 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,2 +1,33 @@
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',
delete = 'delete',
}
export enum PresetRegistryName {
default = 'default',
self = 'self',
}
export enum PackageAccessLevel {
write = 'write',
read = 'read',
}

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;

17
app/common/enum/Binary.ts Normal file
View File

@@ -0,0 +1,17 @@
export enum BinaryType {
Api = 'api',
Bucket = 'bucket',
Cypress = 'cypress',
Electron = 'electron',
GitHub = 'github',
Imagemin = 'imagemin',
Node = 'node',
NodePreGyp = 'nodePreGyp',
Nwjs = 'nwjs',
Playwright = 'playwright',
Puppeteer = 'puppeteer',
Prisma = 'prisma',
Sqlcipher = 'sqlcipher',
ChromeForTesting = 'chromeForTesting',
Edgedriver = 'edgedriver',
}

19
app/common/enum/Hook.ts Normal file
View File

@@ -0,0 +1,19 @@
export enum HookType {
Package = 'package',
Scope = 'scope',
Owner = 'owner',
}
export enum HookEventType {
Star = 'package:star',
Unstar = 'package:unstar',
Publish = 'package:publish',
Unpublish = 'package:unpublish',
Owner = 'package:owner',
OwnerRm = 'package:owner-rm',
DistTag = 'package:dist-tag',
DistTagRm = 'package:dist-tag-rm',
Deprecated = 'package:deprecated',
Undeprecated = 'package:undeprecated',
Change = 'package:change',
}

View File

@@ -0,0 +1,5 @@
export enum RegistryType {
Npm = 'npm',
Cnpmcore = 'cnpmcore',
Cnpmjsorg = 'cnpmjsorg',
}

View File

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

4
app/common/enum/Total.ts Normal file
View File

@@ -0,0 +1,4 @@
export enum TotalType {
PackageCount = 'packageCount',
PackageVersionCount = 'packageVersionCount',
}

View File

@@ -1,5 +1,11 @@
export enum LoginResultCode {
UserNotFound,
Success,
Fail,
UserNotFound = 0,
Success = 1,
Fail = 2,
}
export enum WanStatusCode {
UserNotFound = 0,
Unbound = 1,
Bound = 2,
}

View File

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

View File

@@ -1,5 +1,8 @@
import { Readable } from 'stream';
import { IncomingHttpHeaders } from 'http';
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;
@@ -16,8 +19,12 @@ export interface UploadOptions {
export interface AppendOptions {
key: string;
position?: string,
headers?: IncomingHttpHeaders,
position?: string;
headers?: IncomingHttpHeaders;
}
export interface DownloadOptions {
timeout: number;
}
export interface NFSClient {
@@ -33,5 +40,45 @@ export interface NFSClient {
createDownloadStream(key: string): Promise<Readable | undefined>;
download(
key: string,
filepath: string,
options: DownloadOptions
): Promise<void>;
url?(key: string): string;
}
export interface QueueAdapter {
push<T>(key: string, item: T): Promise<boolean>;
pop<T>(key: string): Promise<T | null>;
length(key: string): Promise<number>;
}
export interface SearchAdapter {
// oxlint-disable-next-line typescript-eslint/no-explicit-any
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;
}
export interface userResult {
name: string;
email: string;
}
export interface AuthClient {
getAuthUrl(ctx: EggContext): Promise<AuthUrlResult>;
ensureCurrentUser(): Promise<userResult | null>;
}
declare module 'egg' {
// @ts-expect-error avoid TS2310 Type 'EggAppConfig' recursively references itself as a base type.
interface EggAppConfig {
cnpmcore: CnpmcoreConfig;
}
}

View File

@@ -1,5 +1,5 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
interface BinaryData extends EntityData {
binaryId: string;
@@ -21,7 +21,7 @@ export class Binary extends Entity {
isDir: boolean;
size: number;
date: string;
sourceUrl?: string;
sourceUrl: string;
ignoreDownloadStatuses?: number[];
constructor(data: BinaryData) {

View File

@@ -8,15 +8,15 @@ export type BugVersionPackages = Record<string, BugVersionPackage>;
export class BugVersion {
private readonly data: BugVersionPackages;
constructor(data) {
constructor(data: BugVersionPackages) {
this.data = data;
}
listAllPackagesHasBugs(): Array<string> {
listAllPackagesHasBugs(): string[] {
return Object.keys(this.data);
}
listBugVersions(pkgName: string): Array<string> {
listBugVersions(pkgName: string): string[] {
const bugVersionPackage = this.data[pkgName];
if (!bugVersionPackage) {
return [];
@@ -31,18 +31,24 @@ export class BugVersion {
}
// TODO manifest typing
// oxlint-disable-next-line typescript-eslint/no-explicit-any
fixManifest(bugVersionManifest: any, fixVersionManifest: any): any {
// If the tarball is same, manifest has fixed.
if (bugVersionManifest.dist.tarball === fixVersionManifest.dist.tarball) {
return;
}
const advice = this.fixVersion(bugVersionManifest.name, bugVersionManifest.version);
const advice = this.fixVersion(
bugVersionManifest.name,
bugVersionManifest.version
);
if (!advice) {
return;
}
const newManifest = JSON.parse(JSON.stringify(fixVersionManifest));
const newManifest = structuredClone(fixVersionManifest);
const hotfixDeprecated = `[WARNING] Use ${advice.version} instead of ${bugVersionManifest.version}, reason: ${advice.reason}`;
newManifest.deprecated = bugVersionManifest.deprecated ? `${bugVersionManifest.deprecated} (${hotfixDeprecated})` : hotfixDeprecated;
newManifest.deprecated = bugVersionManifest.deprecated
? `${bugVersionManifest.deprecated} (${hotfixDeprecated})`
: hotfixDeprecated;
// don't change version
newManifest.version = bugVersionManifest.version;
return newManifest;

View File

@@ -1,10 +1,11 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
interface ChangeData extends EntityData {
changeId: string;
type: string;
targetName: string;
// oxlint-disable-next-line typescript-eslint/no-explicit-any
data: any;
}
@@ -12,6 +13,7 @@ export class Change extends Entity {
changeId: string;
type: string;
targetName: string;
// oxlint-disable-next-line typescript-eslint/no-explicit-any
data: any;
constructor(data: ChangeData) {

View File

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

67
app/core/entity/Hook.ts Normal file
View File

@@ -0,0 +1,67 @@
import crypto from 'node:crypto';
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
import type { HookType } from '../../common/enum/Hook.js';
export type CreateHookData = Omit<
EasyData<HookData, 'hookId'>,
'enable' | 'latestTaskId'
>;
export interface HookData extends EntityData {
hookId: string;
type: HookType;
ownerId: string;
name: string;
endpoint: string;
secret: string;
latestTaskId?: string;
enable: boolean;
}
export class Hook extends Entity {
readonly hookId: string;
readonly type: HookType;
readonly ownerId: string;
readonly name: string;
endpoint: string;
secret: string;
enable: boolean;
latestTaskId?: string;
constructor(data: HookData) {
super(data);
this.hookId = data.hookId;
this.type = data.type;
this.ownerId = data.ownerId;
this.name = data.name;
this.endpoint = data.endpoint;
this.secret = data.secret;
this.latestTaskId = data.latestTaskId;
this.enable = data.enable;
}
static create(data: CreateHookData): Hook {
const hookData: EasyData<HookData, 'hookId'> = {
...data,
enable: true,
latestTaskId: undefined,
};
const newData = EntityUtil.defaultData(hookData, 'hookId');
return new Hook(newData);
}
// payload 可能会特别大,如果做多次 stringify 浪费太多 cpu
signPayload(payload: object) {
const payloadStr = JSON.stringify(payload);
const digest = crypto
.createHmac('sha256', this.secret)
.update(JSON.stringify(payload))
.digest('hex');
return {
digest,
payloadStr,
};
}
}

View File

@@ -0,0 +1,93 @@
import { HookEventType } from '../../common/enum/Hook.js';
export interface PublishChangePayload {
'dist-tag'?: string;
version: string;
}
export interface UnpublishChangePayload {
'dist-tag'?: string;
version?: string;
}
export interface DistTagChangePayload {
'dist-tag': string;
}
export interface PackageOwnerPayload {
maintainer: string;
}
export interface DeprecatedChangePayload {
deprecated: string;
}
export class HookEvent<T = object> {
readonly changeId: string;
readonly event: HookEventType;
readonly fullname: string;
readonly type: 'package';
readonly version: '1.0.0';
readonly change: T;
readonly time: number;
constructor(event: HookEventType, changeId: string, fullname: string, change: T) {
this.changeId = changeId;
this.event = event;
this.fullname = fullname;
this.type = 'package';
this.version = '1.0.0';
this.change = change;
this.time = Date.now();
}
static createPublishEvent(fullname: string, changeId: string, version: string, distTag?: string): HookEvent<PublishChangePayload> {
return new HookEvent(HookEventType.Publish, changeId, fullname, {
'dist-tag': distTag,
version,
});
}
static createUnpublishEvent(fullname: string, changeId: string, version?: string, distTag?: string): HookEvent<UnpublishChangePayload> {
return new HookEvent(HookEventType.Unpublish, changeId, fullname, {
'dist-tag': distTag,
version,
});
}
static createOwnerEvent(fullname: string, changeId: string, maintainer: string): HookEvent<PackageOwnerPayload> {
return new HookEvent(HookEventType.Owner, changeId, fullname, {
maintainer,
});
}
static createOwnerRmEvent(fullname: string, changeId: string, maintainer: string): HookEvent<PackageOwnerPayload> {
return new HookEvent(HookEventType.OwnerRm, changeId, fullname, {
maintainer,
});
}
static createDistTagEvent(fullname: string, changeId: string, distTag: string): HookEvent<DistTagChangePayload> {
return new HookEvent(HookEventType.DistTag, changeId, fullname, {
'dist-tag': distTag,
});
}
static createDistTagRmEvent(fullname: string, changeId: string, distTag: string): HookEvent<DistTagChangePayload> {
return new HookEvent(HookEventType.DistTagRm, changeId, fullname, {
'dist-tag': distTag,
});
}
static createDeprecatedEvent(fullname: string, changeId: string, deprecated: string): HookEvent<DeprecatedChangePayload> {
return new HookEvent(HookEventType.Deprecated, changeId, fullname, {
deprecated,
});
}
static createUndeprecatedEvent(fullname: string, changeId: string, deprecated: string): HookEvent<DeprecatedChangePayload> {
return new HookEvent(HookEventType.Undeprecated, changeId, fullname, {
deprecated,
});
}
}

View File

@@ -1,7 +1,7 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import { Dist } from './Dist';
import { getFullname } from '../../common/PackageUtil';
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
import { Dist } from './Dist.js';
import { getFullname } from '../../common/PackageUtil.js';
interface PackageData extends EntityData {
scope: string;
@@ -11,6 +11,7 @@ interface PackageData extends EntityData {
description: string;
abbreviatedsDist?: Dist;
manifestsDist?: Dist;
registryId?: string;
}
export enum DIST_NAMES {
@@ -21,6 +22,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;
@@ -36,6 +44,7 @@ export class Package extends Entity {
description: string;
abbreviatedsDist?: Dist;
manifestsDist?: Dist;
registryId?: string;
constructor(data: PackageData) {
super(data);
@@ -46,6 +55,7 @@ export class Package extends Entity {
this.description = data.description;
this.abbreviatedsDist = data.abbreviatedsDist;
this.manifestsDist = data.manifestsDist;
this.registryId = data.registryId;
}
static create(data: EasyData<PackageData, 'packageId'>): Package {
@@ -81,6 +91,11 @@ export class Package extends Entity {
return this.createDist(DIST_NAMES.ABBREVIATED_MANIFESTS, info);
}
createPackageVersionFile(path: string, version: string, info: FileInfo) {
// path should starts with `/`, e.g.: '/foo/bar/index.js'
return this.createDist(`files${path}`, info, version);
}
private distDir(filename: string, version?: string) {
if (version) {
return `/packages/${this.fullname}/${version}/${filename}`;

View File

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

View File

@@ -1,6 +1,7 @@
import { Dist } from './Dist';
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import type { Dist } from './Dist.js';
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
import { PaddingSemVer } from './PaddingSemVer.js';
interface PackageVersionData extends EntityData {
packageId: string;
@@ -11,6 +12,8 @@ interface PackageVersionData extends EntityData {
tarDist: Dist;
readmeDist: Dist;
publishTime: Date;
paddingVersion?: string | null;
isPreRelease?: boolean | null;
}
export class PackageVersion extends Entity {
@@ -22,6 +25,8 @@ export class PackageVersion extends Entity {
tarDist: Dist;
readmeDist: Dist;
publishTime: Date;
paddingVersion: string;
isPreRelease: boolean;
constructor(data: PackageVersionData) {
super(data);
@@ -33,9 +38,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,5 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
interface PackageVersionBlockData extends EntityData {
packageVersionBlockId: string;
@@ -22,7 +22,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

@@ -0,0 +1,47 @@
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
import type { Dist } from './Dist.js';
interface PackageVersionFileData extends EntityData {
packageVersionFileId: string;
packageVersionId: string;
dist: Dist;
directory: string;
name: string;
contentType: string;
mtime: Date;
}
export class PackageVersionFile extends Entity {
packageVersionFileId: string;
packageVersionId: string;
dist: Dist;
directory: string;
name: string;
contentType: string;
mtime: Date;
constructor(data: PackageVersionFileData) {
super(data);
this.packageVersionFileId = data.packageVersionFileId;
this.packageVersionId = data.packageVersionId;
this.dist = data.dist;
this.directory = data.directory;
this.name = data.name;
this.contentType = data.contentType;
this.mtime = data.mtime;
}
get path() {
return this.directory === '/'
? `/${this.name}`
: `${this.directory}/${this.name}`;
}
static create(
data: EasyData<PackageVersionFileData, 'packageVersionFileId'>
): PackageVersionFile {
const newData = EntityUtil.defaultData(data, 'packageVersionFileId');
return new PackageVersionFile(newData);
}
}

View File

@@ -1,10 +1,11 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
interface PackageVersionManifestData extends EntityData {
packageId: string;
packageVersionId: string;
packageVersionManifestId: string;
// oxlint-disable-next-line typescript-eslint/no-explicit-any
manifest: any;
}
@@ -12,6 +13,7 @@ export class PackageVersionManifest extends Entity {
packageId: string;
packageVersionId: string;
packageVersionManifestId: string;
// oxlint-disable-next-line typescript-eslint/no-explicit-any
manifest: any;
constructor(data: PackageVersionManifestData) {
@@ -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,53 @@
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);
// @ts-expect-error type definition is not correct
if (this.semver.includePrerelease) {
this.isPreRelease = true;
} else if (this.semver.prerelease && this.semver.prerelease.length > 0) {
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,43 @@
import { Entity, type EntityData } from './Entity.js';
import { isPkgManifest, type DIST_NAMES } from './Package.js';
import type { EasyData } from '../util/EntityUtil.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

@@ -0,0 +1,47 @@
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
import type { RegistryType } from '../../common/enum/Registry.js';
interface RegistryData extends EntityData {
name: string;
registryId: string;
host: string;
changeStream: string;
userPrefix: string;
type: RegistryType;
authToken?: string;
}
export type CreateRegistryData = Omit<
EasyData<RegistryData, 'registryId'>,
'id'
>;
export class Registry extends Entity {
name: string;
registryId: string;
host: string;
changeStream: string;
userPrefix: string;
type: RegistryType;
authToken?: string;
constructor(data: RegistryData) {
super(data);
this.name = data.name;
this.registryId = data.registryId;
this.host = data.host;
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<RegistryData, 'registryId'>(
data,
'registryId'
);
return new Registry(newData);
}
}

28
app/core/entity/Scope.ts Normal file
View File

@@ -0,0 +1,28 @@
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
interface ScopeData extends EntityData {
name: string;
scopeId: string;
registryId: string;
}
export type CreateScopeData = Omit<EasyData<ScopeData, 'scopeId'>, 'id'>;
export class Scope extends Entity {
name: string;
registryId: string;
scopeId: string;
constructor(data: ScopeData) {
super(data);
this.name = data.name;
this.registryId = data.registryId;
this.scopeId = data.scopeId;
}
static create(data: CreateScopeData): Scope {
const newData = EntityUtil.defaultData(data, 'scopeId');
return new Scope(newData);
}
}

View File

@@ -0,0 +1,84 @@
import { Comparator, Range } 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) {
// @ts-expect-error type definition is not correct
if (comparator.semver === Comparator.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: Comparator[]) {
const condition: object[] = [];
for (const comparator of comparatorSet) {
condition.push(this.comparatorToSql(comparator));
}
return { $and: condition };
}
private generateWhere() {
const conditions: 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,46 +1,120 @@
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 os from 'node:os';
import path from 'node:path';
interface TaskData extends EntityData {
import { InternalServerError } from 'egg-errors';
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
import { TaskState, TaskType } 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 { isPkgManifest, type DIST_NAMES } from './Package.js';
export const HOST_NAME = os.hostname();
export const PID = process.pid;
export interface TaskBaseData {
taskWorker: string;
shouldNotMerge?: boolean;
}
export interface TaskData<T = TaskBaseData> extends EntityData {
taskId: string;
type: TaskType;
state: TaskState;
targetName: string;
authorId: string;
authorIp: string;
data: any;
data: T;
logPath?: string;
logStorePosition?: string;
attempts?: number;
error?: string;
bizId?: string;
}
export type SyncPackageTaskOptions = {
export interface SyncPackageTaskOptions {
authorId?: string;
authorIp?: string;
tips?: string;
skipDependencies?: boolean;
syncDownloadData?: boolean;
};
// force sync history version
forceSyncHistory?: boolean;
registryId?: string;
specificVersions?: string[];
}
export class Task extends Entity {
export interface UpdateProxyCacheTaskOptions {
fullname: string;
version?: string;
fileType: DIST_NAMES;
}
export interface CreateHookTaskData extends TaskBaseData {
hookEvent: HookEvent;
}
export interface TriggerHookTaskData extends TaskBaseData {
hookEvent: HookEvent;
hookId: string;
responseStatus?: number;
}
export interface CreateSyncPackageTaskData extends TaskBaseData {
tips?: string;
skipDependencies?: boolean;
syncDownloadData?: boolean;
forceSyncHistory?: boolean;
specificVersions?: string[];
}
export interface CreateUpdateProxyCacheTaskData extends TaskBaseData {
fullname: string;
version?: string;
fileType: DIST_NAMES;
filePath: string;
}
export type SyncBinaryTaskData = Record<string, unknown> & TaskBaseData;
export interface ChangesStreamTaskData extends TaskBaseData {
since: string;
last_package?: string;
last_package_created?: Date;
task_count?: number;
registryId?: string;
}
export interface TaskUpdateCondition {
taskId: string;
attempts: number;
}
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 type SyncBinaryTask = Task<SyncBinaryTaskData>;
export class Task<T extends TaskBaseData = TaskBaseData> extends Entity {
taskId: string;
type: TaskType;
state: TaskState;
targetName: string;
taskWorker: string;
authorId: string;
authorIp: string;
data: any;
data: T;
logPath: string;
logStorePosition: string;
attempts: number;
error: string;
bizId?: string;
constructor(data: TaskData) {
constructor(data: TaskData<T>) {
super(data);
this.taskId = data.taskId;
this.type = data.type;
@@ -53,6 +127,7 @@ export class Task extends Entity {
this.logStorePosition = data.logStorePosition ?? '';
this.attempts = data.attempts ?? 0;
this.error = data.error ?? '';
this.bizId = data.bizId;
}
public resetLogPath() {
@@ -61,15 +136,20 @@ export class Task extends Entity {
}
public setExecuteWorker() {
this.data.taskWorker = `${os.hostname()}:${process.pid}`;
this.data.taskWorker = `${HOST_NAME}:${PID}`;
}
private static create(data: EasyData<TaskData, 'taskId'>): Task {
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): Task {
public static createSyncPackage(
fullname: string,
options?: SyncPackageTaskOptions
): CreateSyncPackageTask {
const data = {
type: TaskType.SyncPackage,
state: TaskState.Waiting,
@@ -80,8 +160,11 @@ export class Task extends Entity {
// task execute worker
taskWorker: '',
tips: options?.tips,
registryId: options?.registryId ?? '',
skipDependencies: options?.skipDependencies,
syncDownloadData: options?.syncDownloadData,
forceSyncHistory: options?.forceSyncHistory,
specificVersions: options?.specificVersions,
},
};
const task = this.create(data);
@@ -89,29 +172,92 @@ export class Task extends Entity {
return task;
}
public static createChangesStream(targetName: string): Task {
public static createChangesStream(
targetName: string,
registryId = '',
since = ''
): ChangesStreamTask {
const data = {
type: TaskType.ChangesStream,
state: TaskState.Waiting,
targetName,
authorId: `pid_${PID}`,
authorIp: HOST_NAME,
data: {
// task execute worker
taskWorker: '',
registryId,
since,
},
};
return this.create(data) as ChangesStreamTask;
}
public updateSyncData({ lastSince, taskCount, lastPackage }: SyncInfo) {
const syncData = this.data as unknown as ChangesStreamTaskData;
// 更新任务记录信息
syncData.since = lastSince;
syncData.task_count = (syncData.task_count || 0) + taskCount;
if (taskCount > 0) {
syncData.last_package = lastPackage;
syncData.last_package_created = new Date();
}
}
public static createCreateHookTask(hookEvent: HookEvent): CreateHookTask {
const data = {
type: TaskType.CreateHook,
state: TaskState.Waiting,
targetName: hookEvent.fullname,
authorId: `pid_${process.pid}`,
authorIp: os.hostname(),
bizId: `CreateHook:${hookEvent.changeId}`,
data: {
// task execute worker
taskWorker: '',
hookEvent,
},
};
const task = this.create(data);
task.logPath = `/packages/${hookEvent.fullname}/hooks/${dayjs().format('YYYY/MM/DDHHmm')}-${task.taskId}.log`;
return task;
}
public static createTriggerHookTask(
hookEvent: HookEvent,
hookId: string
): TriggerHookTask {
const data = {
type: TaskType.TriggerHook,
state: TaskState.Waiting,
targetName: hookEvent.fullname,
authorId: `pid_${process.pid}`,
bizId: `TriggerHook:${hookEvent.changeId}:${hookId}`,
authorIp: os.hostname(),
data: {
// task execute worker
taskWorker: '',
since: '',
hookEvent,
hookId,
},
};
return this.create(data);
const task = this.create(data);
task.logPath = `/packages/${hookEvent.fullname}/hooks/${dayjs().format('YYYY/MM/DDHHmm')}-${task.taskId}.log`;
return task;
}
public static createSyncBinary(targetName: string, lastData: any): Task {
public static createSyncBinary(
targetName: string,
lastData?: Record<string, unknown>
): Task {
const data = {
type: TaskType.SyncBinary,
state: TaskState.Waiting,
targetName,
authorId: `pid_${process.pid}`,
authorIp: os.hostname(),
authorId: `pid_${PID}`,
authorIp: HOST_NAME,
bizId: `SyncBinary:${targetName}`,
data: {
// task execute worker
taskWorker: '',
@@ -122,4 +268,60 @@ export class Task extends Entity {
task.logPath = `/binaries/${targetName}/syncs/${dayjs().format('YYYY/MM/DDHHmm')}-${task.taskId}.log`;
return task;
}
needMergeWhenWaiting(): boolean {
// 历史任务补偿时,将 shouldNotMerge 设置为 true避免合并
// 补偿任务单独执行
if (this.data.shouldNotMerge === true) {
return false;
}
// 仅合并二进制镜像与 npm 包
return [TaskType.SyncBinary, TaskType.SyncPackage].includes(this.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,
attempts: this.attempts,
};
this.setExecuteWorker();
this.state = TaskState.Processing;
this.attempts += 1;
return condition;
}
}
export interface SyncInfo {
lastSince: string;
taskCount: number;
lastPackage?: string;
}

View File

@@ -1,14 +1,41 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import dayjs from 'dayjs';
interface TokenData extends EntityData {
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
export enum TokenType {
granular = 'granular',
classic = 'classic',
}
interface BaseTokenData extends EntityData {
tokenId: string;
tokenMark: string;
tokenKey: string;
cidrWhitelist: string[];
cidrWhitelist?: string[];
userId: string;
isReadonly: boolean;
isAutomation: boolean;
isReadonly?: boolean;
type?: TokenType | string;
lastUsedAt?: Date;
}
interface ClassicTokenData extends BaseTokenData {
isAutomation?: boolean;
}
interface GranularTokenData extends BaseTokenData {
name: string;
description?: string;
allowedScopes?: string[];
allowedPackages?: string[];
expires: number;
expiredAt: Date;
}
type TokenData = ClassicTokenData | GranularTokenData;
export function isGranularToken(
data: TokenData | Token
): data is GranularTokenData {
return data.type === TokenType.granular;
}
export class Token extends Entity {
@@ -19,7 +46,15 @@ export class Token extends Entity {
readonly userId: string;
readonly isReadonly: boolean;
readonly isAutomation: boolean;
token?: string;
readonly type?: TokenType;
readonly name?: string;
readonly description?: string;
readonly allowedScopes?: string[];
readonly expiredAt?: Date;
readonly expires?: number;
lastUsedAt: Date | null;
allowedPackages?: string[];
token: string;
constructor(data: TokenData) {
super(data);
@@ -27,13 +62,30 @@ export class Token extends Entity {
this.tokenId = data.tokenId;
this.tokenMark = data.tokenMark;
this.tokenKey = data.tokenKey;
this.cidrWhitelist = data.cidrWhitelist;
this.isReadonly = data.isReadonly;
this.isAutomation = data.isAutomation;
this.cidrWhitelist = data.cidrWhitelist || [];
this.isReadonly = data.isReadonly || false;
this.type = (data.type as TokenType) || TokenType.classic;
this.lastUsedAt = data.lastUsedAt || null;
if (isGranularToken(data)) {
this.name = data.name;
this.description = data.description;
this.allowedScopes = data.allowedScopes;
this.expiredAt = data.expiredAt;
this.allowedPackages = data.allowedPackages;
this.isAutomation = false;
} else {
this.isAutomation = data.isAutomation || false;
}
}
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();
}
return new Token(newData);
}
}

View File

@@ -1,5 +1,6 @@
import { Entity, EntityData } from './Entity';
import { EasyData, EntityUtil } from '../util/EntityUtil';
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
import { cleanUserPrefix } from '../../common/PackageUtil.js';
interface UserData extends EntityData {
userId: string;
@@ -15,6 +16,7 @@ interface UserData extends EntityData {
export class User extends Entity {
userId: string;
name: string;
displayName: string;
email: string;
passwordSalt: string;
passwordIntegrity: string;
@@ -26,6 +28,7 @@ export class User extends Entity {
super(data);
this.userId = data.userId;
this.name = data.name;
this.displayName = cleanUserPrefix(this.name);
this.email = data.email;
this.passwordSalt = data.passwordSalt;
this.passwordIntegrity = data.passwordIntegrity;

View File

@@ -0,0 +1,34 @@
import { Entity, type EntityData } from './Entity.js';
import { EntityUtil, type EasyData } from '../util/EntityUtil.js';
interface WebauthnCredentialData extends EntityData {
wancId: string;
userId: string;
credentialId: string;
publicKey: string;
browserType?: string;
}
export class WebauthnCredential extends Entity {
wancId: string;
userId: string;
credentialId: string;
publicKey: string;
browserType?: string;
constructor(data: WebauthnCredentialData) {
super(data);
this.wancId = data.wancId;
this.userId = data.userId;
this.credentialId = data.credentialId;
this.publicKey = data.publicKey;
this.browserType = data.browserType;
}
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

@@ -1,18 +1,18 @@
import { Event, Inject } from '@eggjs/tegg';
import {
PACKAGE_UNPUBLISHED,
PACKAGE_BLOCKED,
PACKAGE_UNBLOCKED,
PACKAGE_VERSION_ADDED,
PACKAGE_VERSION_REMOVED,
PACKAGE_TAG_ADDED,
PACKAGE_TAG_CHANGED,
PACKAGE_TAG_REMOVED,
PACKAGE_MAINTAINER_CHANGED,
PACKAGE_MAINTAINER_REMOVED,
PACKAGE_META_CHANGED,
} from './index';
import { CacheService } from '../../core/service/CacheService';
PACKAGE_TAG_ADDED,
PACKAGE_TAG_CHANGED,
PACKAGE_TAG_REMOVED,
PACKAGE_UNBLOCKED,
PACKAGE_UNPUBLISHED,
PACKAGE_VERSION_ADDED,
PACKAGE_VERSION_REMOVED,
} 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,90 +1,189 @@
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,
type PackageMetaChange,
PACKAGE_MAINTAINER_CHANGED,
PACKAGE_MAINTAINER_REMOVED,
PACKAGE_META_CHANGED,
} from './index';
import { ChangeRepository } from '../../repository/ChangeRepository';
import { Change } from '../entity/Change';
PACKAGE_TAG_ADDED,
PACKAGE_TAG_CHANGED,
PACKAGE_TAG_REMOVED,
PACKAGE_UNPUBLISHED,
PACKAGE_VERSION_ADDED,
PACKAGE_VERSION_REMOVED,
} 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()
private readonly changeRepository: ChangeRepository;
protected async addChange(type: string, fullname: string, data: object) {
await this.changeRepository.addChange(Change.create({
@Inject()
protected readonly taskService: TaskService;
@Inject()
protected readonly config: EggAppConfig;
protected get hookEnable() {
return this.config.cnpmcore.hookEnable;
}
protected async addChange(
type: string,
fullname: string,
data: object
): Promise<Change> {
const change = Change.create({
type,
targetName: fullname,
data,
}));
});
await this.changeRepository.addChange(change);
return change;
}
}
@Event(PACKAGE_UNPUBLISHED)
export class PackageUnpublished extends ChangesStreamEvent {
export class PackageUnpublishedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string) {
await this.addChange(PACKAGE_UNPUBLISHED, fullname, {});
const change = await this.addChange(PACKAGE_UNPUBLISHED, fullname, {});
if (this.hookEnable) {
const task = Task.createCreateHookTask(
HookEvent.createUnpublishEvent(fullname, change.changeId)
);
await this.taskService.createTask(task, true);
}
}
}
@Event(PACKAGE_VERSION_ADDED)
export class PackageVersionAdded extends ChangesStreamEvent {
async handle(fullname: string, version: string) {
await this.addChange(PACKAGE_VERSION_ADDED, fullname, { version });
export class PackageVersionAddedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, version: string, tag?: string) {
const change = await this.addChange(PACKAGE_VERSION_ADDED, fullname, {
version,
});
if (this.hookEnable) {
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 {
async handle(fullname: string, version: string) {
await this.addChange(PACKAGE_VERSION_REMOVED, fullname, { version });
export class PackageVersionRemovedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, version: string, tag?: string) {
const change = await this.addChange(PACKAGE_VERSION_REMOVED, fullname, {
version,
});
if (this.hookEnable) {
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) {
await this.addChange(PACKAGE_TAG_ADDED, fullname, { tag });
const change = await this.addChange(PACKAGE_TAG_ADDED, fullname, { tag });
if (this.hookEnable) {
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) {
await this.addChange(PACKAGE_TAG_CHANGED, fullname, { tag });
const change = await this.addChange(PACKAGE_TAG_CHANGED, fullname, { tag });
if (this.hookEnable) {
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) {
await this.addChange(PACKAGE_TAG_REMOVED, fullname, { tag });
const change = await this.addChange(PACKAGE_TAG_REMOVED, fullname, { tag });
if (this.hookEnable) {
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 {
async handle(fullname: string) {
await this.addChange(PACKAGE_MAINTAINER_CHANGED, fullname, {});
export class PackageMaintainerChangedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, maintainers: User[]) {
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)
);
await this.taskService.createTask(task, true);
}
}
}
}
@Event(PACKAGE_MAINTAINER_REMOVED)
export class PackageMaintainerRemoved extends ChangesStreamEvent {
export class PackageMaintainerRemovedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, maintainer: string) {
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)
);
await this.taskService.createTask(task, true);
}
}
}
@Event(PACKAGE_META_CHANGED)
export class PackageMetaChanged extends ChangesStreamEvent {
async handle(fullname: string, meta: object) {
await this.addChange(PACKAGE_META_CHANGED, fullname, { ...meta });
export class PackageMetaChangedChangesStreamEvent extends ChangesStreamEvent {
async handle(fullname: string, meta: PackageMetaChange) {
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
)
);
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_BLOCKED,
PACKAGE_MAINTAINER_CHANGED,
PACKAGE_MAINTAINER_REMOVED,
PACKAGE_META_CHANGED,
PACKAGE_TAG_ADDED,
PACKAGE_TAG_CHANGED,
PACKAGE_TAG_REMOVED,
PACKAGE_UNBLOCKED,
PACKAGE_UNPUBLISHED,
PACKAGE_VERSION_ADDED,
PACKAGE_VERSION_REMOVED,
} 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

@@ -0,0 +1,91 @@
import { Event, Inject } from '@eggjs/tegg';
import type { EggAppConfig, EggLogger } from 'egg';
import { ForbiddenError } from 'egg-errors';
import {
PACKAGE_TAG_ADDED,
PACKAGE_TAG_CHANGED,
PACKAGE_VERSION_ADDED,
} 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 (!packageVersion) return;
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 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

@@ -0,0 +1,22 @@
import { Event, Inject } from '@eggjs/tegg';
import { PACKAGE_ADDED, PACKAGE_VERSION_ADDED } from './index.js';
import type { TotalRepository } from '../../repository/TotalRepository.js';
class TotalHandlerEvent {
@Inject()
protected readonly totalRepository: TotalRepository;
}
@Event(PACKAGE_ADDED)
export class PackageAddedTotalHandlerEvent extends TotalHandlerEvent {
async handle() {
await this.totalRepository.incrementPackageCount();
}
}
@Event(PACKAGE_VERSION_ADDED)
export class PackageVersionAddedTotalHandlerEvent extends TotalHandlerEvent {
async handle() {
await this.totalRepository.incrementPackageVersionCount();
}
}

View File

@@ -1,5 +1,7 @@
import '@eggjs/tegg';
import type { User } from '../entity/User.js';
export const PACKAGE_ADDED = 'PACKAGE_ADDED';
export const PACKAGE_UNPUBLISHED = 'PACKAGE_UNPUBLISHED';
export const PACKAGE_BLOCKED = 'PACKAGE_BLOCKED';
export const PACKAGE_UNBLOCKED = 'PACKAGE_UNBLOCKED';
@@ -12,18 +14,45 @@ export const PACKAGE_MAINTAINER_CHANGED = 'PACKAGE_MAINTAINER_CHANGED';
export const PACKAGE_MAINTAINER_REMOVED = 'PACKAGE_MAINTAINER_REMOVED';
export const PACKAGE_META_CHANGED = 'PACKAGE_META_CHANGED';
export interface PackageDeprecated {
version: string;
deprecated?: string;
}
export interface PackageMetaChange {
deprecateds?: PackageDeprecated[];
}
declare module '@eggjs/tegg' {
interface Events {
[PACKAGE_ADDED]: (fullname: string) => Promise<void>;
[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) => Promise<void>;
[PACKAGE_VERSION_REMOVED]: (fullname: string, version: 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) => Promise<void>;
[PACKAGE_MAINTAINER_REMOVED]: (fullname: string, maintainer: string) => Promise<void>;
[PACKAGE_META_CHANGED]: (fullname: string, meta: object) => 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,55 +1,39 @@
import { rm } from 'fs/promises';
import fs from 'node:fs/promises';
import {
AccessLevel,
ContextProto,
Inject,
SingletonProto,
type EggObjectFactory,
} from '@eggjs/tegg';
import {
EggContextHttpClient,
} from 'egg';
import fs from 'fs/promises';
import binaries, { SyncerClass } 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 { ApiBinary } from '../../common/adapter/binary/ApiBinary';
import { AbstractService } from '../../common/AbstractService';
import { NodeBinary } from '../../common/adapter/binary/NodeBinary';
import { NwjsBinary } from '../../common/adapter/binary/NwjsBinary';
import { BucketBinary } from '../../common/adapter/binary/BucketBinary';
import { CypressBinary } from '../../common/adapter/binary/CypressBinary';
import { SqlcipherBinary } from '../../common/adapter/binary/SqlcipherBinary';
import { PuppeteerBinary } from '../../common/adapter/binary/PuppeteerBinary';
import { GithubBinary } from '../../common/adapter/binary/GithubBinary';
import { ElectronBinary } from '../../common/adapter/binary/ElectronBinary';
import { NodePreGypBinary } from '../../common/adapter/binary/NodePreGypBinary';
import { ImageminBinary } from '../../common/adapter/binary/ImageminBinary';
import { PlaywrightBinary } from '../../common/adapter/binary/PlaywrightBinary';
import type { EggHttpClient } from 'egg';
import { sortBy } from 'lodash-es';
const BinaryClasses = {
[SyncerClass.NodeBinary]: NodeBinary,
[SyncerClass.NwjsBinary]: NwjsBinary,
[SyncerClass.BucketBinary]: BucketBinary,
[SyncerClass.CypressBinary]: CypressBinary,
[SyncerClass.SqlcipherBinary]: SqlcipherBinary,
[SyncerClass.PuppeteerBinary]: PuppeteerBinary,
[SyncerClass.GithubBinary]: GithubBinary,
[SyncerClass.ElectronBinary]: ElectronBinary,
[SyncerClass.NodePreGypBinary]: NodePreGypBinary,
[SyncerClass.ImageminBinary]: ImageminBinary,
[SyncerClass.PlaywrightBinary]: PlaywrightBinary,
};
import binaries, {
type BinaryName,
type CategoryName,
} from '../../../config/binaries.js';
import type { BinaryRepository } from '../../repository/BinaryRepository.js';
import { Task, type SyncBinaryTask } 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 {
AbstractBinary,
type BinaryItem,
} from '../../common/adapter/binary/AbstractBinary.js';
import { AbstractService } from '../../common/AbstractService.js';
import { BinaryType } from '../../common/enum/Binary.js';
import { TaskState, TaskType } from '../../common/enum/Task.js';
import { platforms } from '../../common/adapter/binary/PuppeteerBinary.js';
function isoNow() {
return new Date().toISOString();
}
@ContextProto({
@SingletonProto({
accessLevel: AccessLevel.PUBLIC,
})
export class BinarySyncerService extends AbstractService {
@@ -58,134 +42,310 @@ export class BinarySyncerService extends AbstractService {
@Inject()
private readonly taskService: TaskService;
@Inject()
private readonly httpclient: EggContextHttpClient;
private readonly httpclient: EggHttpClient;
@Inject()
private readonly nfsAdapter: NFSAdapter;
@Inject()
private readonly eggObjectFactory: EggObjectFactory;
public async findBinary(binaryName: string, parent: string, name: string) {
return await this.binaryRepository.findBinary(binaryName, parent, name);
// 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
) {
return await this.binaryRepository.findBinary(targetName, parent, name);
}
public async listDirBinaries(binary: Binary) {
return await this.binaryRepository.listBinaries(binary.category, `${binary.parent}${binary.name}`);
public async listDirBinaries(
binary: Binary,
options?: {
limit: number;
since: string;
}
) {
return await this.binaryRepository.listBinaries(
binary.category,
`${binary.parent}${binary.name}`,
options
);
}
public async listRootBinaries(binaryName: string) {
return await this.binaryRepository.listBinaries(binaryName, '/');
public async listRootBinaries(binaryName: BinaryName) {
// 通常 binaryName 和 category 是一样的,但是有些特殊的 binaryName 会有多个 category比如 canvas
// 所以查询 canvas 的时候,需要将 binaryName 和 category 的数据都查出来
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 versions = new Set(rootBinary.map(b => b.name));
if (categoryBinary) {
for (const b of categoryBinary) {
const version = b.name;
// 只将没有的版本添加进去
if (!versions.has(version)) {
rootBinary.push(b);
}
}
}
return rootBinary;
}
public async downloadBinary(binary: Binary) {
return await this.nfsAdapter.getDownloadUrlOrStream(binary.storePath);
}
public async createTask(binaryName: string, lastData?: any) {
return await this.taskService.createTask(Task.createSyncBinary(binaryName, lastData), false);
public async createTask(
binaryName: BinaryName,
lastData?: Record<string, unknown>
) {
// chromium-browser-snapshots 产物极大,完整遍历 s3 bucket 耗时会太长
// 必须从上次同步的 revision 之后开始遍历
// 如果需要补偿数据,可以
if (binaryName === 'chromium-browser-snapshots') {
lastData = lastData || {};
for (const platform of platforms) {
if (lastData[platform]) continue;
const binaryDir = await this.binaryRepository.findLatestBinaryDir(
'chromium-browser-snapshots',
`/${platform}/`
);
if (binaryDir) {
lastData[platform] = binaryDir.name.slice(0, -1);
}
}
const latestBinary = await this.binaryRepository.findLatestBinary(
'chromium-browser-snapshots'
);
if (latestBinary && !lastData.lastSyncTime) {
lastData.lastSyncTime = latestBinary.date;
}
}
try {
return await this.taskService.createTask(
Task.createSyncBinary(binaryName, lastData),
false
);
} catch (e) {
this.logger.error(
'[BinarySyncerService.createTask] binaryName: %s, error: %s',
binaryName,
e
);
}
}
public async findTask(taskId: string) {
return await this.taskService.findTask(taskId);
public async findTask(taskId: string): Promise<SyncBinaryTask | null> {
return (await this.taskService.findTask(taskId)) as SyncBinaryTask;
}
public async findTaskLog(task: Task) {
public async findTaskLog(task: SyncBinaryTask) {
return await this.taskService.findTaskLog(task);
}
public async findExecuteTask() {
return await this.taskService.findExecuteTask(TaskType.SyncBinary);
public async findExecuteTask(): Promise<SyncBinaryTask | null> {
return (await this.taskService.findExecuteTask(
TaskType.SyncBinary
)) as SyncBinaryTask;
}
public async executeTask(task: Task) {
const binaryName = task.targetName;
const binaryInstance = this.createBinaryInstance(binaryName);
public async executeTask(task: SyncBinaryTask) {
const binaryName = task.targetName as BinaryName;
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}" 🚧🚧🚧🚧🚧`);
if (!binaryInstance) {
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(binaryInstance, 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);
} catch (err: any) {
task.error = err.message;
logs.push(`[${isoNow()}] ❌ Synced "${binaryName}" fail, ${task.error}, log: ${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) {
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(binaryInstance: AbstractBinary, task: Task, dir: string, parentIndex = '') {
const binaryName = task.targetName;
const result = await binaryInstance.fetch(dir, task.data);
private async syncDir(
binaryAdapter: AbstractBinary,
task: SyncBinaryTask,
dir: string,
parentIndex = '',
latestVersionParent = '/'
) {
const binaryName = task.targetName as BinaryName;
const result = await binaryAdapter.fetch(dir, binaryName, task.data);
let hasDownloadError = false;
let hasItems = false;
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: ${binaryInstance.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(binaryInstance, 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 {
} else if (hasSubItems) {
// if any file download error, let dir sync again next time
// if empty dir, don't save it
if (hasSubItems) {
await this.saveBinaryItem(item);
}
await this.saveBinaryItem(item);
}
} 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
);
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
);
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!, 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) {
} catch (err) {
if (err.name === 'DownloadNotFoundError') {
this.logger.warn('Not found %s, skip it', item.sourceUrl);
logs.push(`[${isoNow()}][${dir}] 🧪️ [${parentIndex}${index}] Download ${item.sourceUrl} not found, skip it`);
this.logger.info('Not found %s, skip it', item.sourceUrl);
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 });
}
}
}
@@ -193,20 +353,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: string, 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) {
@@ -231,9 +406,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) {
@@ -241,24 +432,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 createBinaryInstance(binaryName: string): 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) {
const binaryConfig = binaries[binaryName];
const syncBinaryFromAPISource = config.syncBinaryFromAPISource || `${config.sourceRegistry}/-/binary`;
return new ApiBinary(this.httpclient, this.logger, binaryConfig, syncBinaryFromAPISource);
}
for (const binaryConfig of Object.values(binaries)) {
if (binaryConfig.category === binaryName) {
return new BinaryClasses[binaryConfig.syncer](this.httpclient, this.logger, binaryConfig);
}
binaryAdapter = await this.eggObjectFactory.getEggObject(
AbstractBinary,
BinaryType.Api
);
} else {
binaryAdapter = await this.eggObjectFactory.getEggObject(
AbstractBinary,
binaryConfig.type
);
}
await binaryAdapter.initFetch(binaryName);
return binaryAdapter;
}
}

View File

@@ -1,13 +1,18 @@
import { AccessLevel, ContextProto, Inject } from '@eggjs/tegg';
import { EggLogger } from 'egg';
import { AccessLevel, Inject, SingletonProto } from '@eggjs/tegg';
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';
@ContextProto({
@SingletonProto({
accessLevel: AccessLevel.PUBLIC,
})
export class BugVersionService {
@@ -23,53 +28,117 @@ 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,
// oxlint-disable-next-line typescript-eslint/no-explicit-any
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,
// oxlint-disable-next-line typescript-eslint/no-explicit-any
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,
// oxlint-disable-next-line typescript-eslint/no-explicit-any
manifest: any,
// oxlint-disable-next-line typescript-eslint/no-explicit-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,36 +1,42 @@
import {
AccessLevel,
ContextProto,
Inject,
} from '@eggjs/tegg';
import { CacheAdapter } from '../../common/adapter/CacheAdapter';
import { AbstractService } from '../../common/AbstractService';
import { AccessLevel, Inject, SingletonProto } 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';
type TotalData = {
export type UpstreamRegistryInfo = {
registry_name: string;
source_registry: string;
changes_stream_url: string;
} & ChangesStreamTaskData;
export interface DownloadInfo {
today: number;
yesterday: number;
samedayLastweek: number;
thisweek: number;
thismonth: number;
thisyear: number;
lastweek: number;
lastmonth: number;
lastyear: number;
}
export interface TotalData {
packageCount: number;
packageVersionCount: number;
lastPackage: string;
lastPackageVersion: string;
download: {
today: number;
yesterday: number;
samedayLastweek: number;
thisweek: number;
thismonth: number;
thisyear: number;
lastweek: number;
lastmonth: number;
lastyear: number;
};
changesStream: object,
download: DownloadInfo;
changesStream: ChangesStreamTaskData;
lastChangeId: number | bigint;
cacheTime: string;
};
upstreamRegistries: UpstreamRegistryInfo[];
}
const TOTAL_DATA_KEY = '__TOTAL_DATA__';
@ContextProto({
@SingletonProto({
accessLevel: AccessLevel.PUBLIC,
})
export class CacheService extends AbstractService {
@@ -47,51 +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: {},
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}`;
}
}

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