Compare commits

...

27 Commits

Author SHA1 Message Date
TZ | 天猪
3231a27e59 feat: devcontainer with codespaces 2023-11-08 22:22: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
41 changed files with 13704 additions and 157 deletions

View File

@@ -0,0 +1,43 @@
// Update the VARIANT arg in docker-compose.yml to pick a Node.js version
{
"name": "Node.js && Redis && MySQL",
"dockerComposeFile": "../docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
]
}
},
// Forwards ports
"forwardPorts": [
8080
],
"portsAttributes": {
"8080": {
"label": "Adminer",
"onAutoForward": "notify"
}
},
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": [
"bash .devcontainer/scripts/postCreateCommand.sh"
],
// Container Env
"containerEnv": {
"MYSQL_HOST": "mysql",
"MYSQL_USER": "root",
"MYSQL_PASSWORD": "root"
},
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "node"
}

View File

@@ -0,0 +1,4 @@
#!/bin/bash
pnpm i
socat TCP4-LISTEN:8080,reuseaddr,fork TCP:adminer:8080 &

View File

@@ -13,12 +13,9 @@ name: "CodeQL"
on:
push:
branches: [ main ]
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '41 13 * * 3'
branches: [ master ]
jobs:
analyze:

View File

@@ -3,7 +3,11 @@
name: Node.js CI
on: [push, pull_request]
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
test-mysql57-fs-nfs:

View File

@@ -6,7 +6,7 @@ on:
jobs:
release:
name: Node.js
uses: node-modules/github-actions/.github/workflows/node-release.yml@master
uses: cnpm/github-actions/.github/workflows/node-release.yml@master
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ lerna-debug.log*
.npmrc
package-lock.json
pnpm-lock.yaml
config/config.prod.ts
config/**/*.js

View File

@@ -1,5 +1,90 @@
# Changelog
## [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](https://github.com/cnpm/cnpmcore/commit/8e1f4ca880c6ad09f766807e6a751b5ae960b550))
## [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](https://github.com/cnpm/cnpmcore/commit/0179ef364a5bc6aac5eafafee7136bf61405ee43))
## [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](https://github.com/cnpm/cnpmcore/commit/18ef7f49affd2656cdba26fe21078f46ca1e0cc5))
## [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](https://github.com/cnpm/cnpmcore/commit/5bedb25f9dd29d684add0e1f4b827db0c8e0e818))
## [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](https://github.com/cnpm/cnpmcore/commit/cde4f03c30dea074a32dc32f22f16c16c08fbe0d))
## [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](https://github.com/cnpm/cnpmcore/commit/079176926dd00c14cbf937d19a8e21dba8376a46))
## [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](https://github.com/cnpm/cnpmcore/commit/22d401ee1f103a9702448d4749f0028a676eddc0))
## [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](https://github.com/cnpm/cnpmcore/commit/707a1d3809f14cc9a7d613a16f8bea4e5baa7127))
## [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](https://github.com/cnpm/cnpmcore/commit/413ec5685ee54dd90fcfcd5cb59a9b732ec73d84))
## [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](https://github.com/cnpm/cnpmcore/commit/4596b2127119f7c3c31f5fbe786504a7972d62a9))
* use oss-client v2 ([#596](https://github.com/cnpm/cnpmcore/issues/596)) ([d24e3bd](https://github.com/cnpm/cnpmcore/commit/d24e3bd235fb73b1c145ff3b06dcc168d65b0f9f))
## [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](https://github.com/cnpm/cnpmcore/commit/4596b2127119f7c3c31f5fbe786504a7972d62a9))
## [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](https://github.com/cnpm/cnpmcore/commit/88b6afb66e77c825d24189f904c448a9cbb86fab)), closes [/github.com/cnpm/cnpmcore/issues/585#issuecomment-1706009496](https://github.com/cnpm//github.com/cnpm/cnpmcore/issues/585/issues/issuecomment-1706009496)
## [3.43.4](https://github.com/cnpm/cnpmcore/compare/v3.43.3...v3.43.4) (2023-09-02)

View File

@@ -6,7 +6,12 @@ WORKDIR /usr/src/app
# Install app dependencies
COPY . .
RUN npm install -g npminstall --registry=https://registry.npmmirror.com \
# NPM Mirror
# npm install -g npminstall --registry=https://registry.npmmirror.com
# apk add --no-cache socat \
RUN apt-get update \
&& apt-get -y install socat \
&& npm install -g npminstall \
&& npminstall -c \
&& npm run tsc

View File

@@ -64,7 +64,7 @@ async function _downloadToTempfile(httpclient: EggContextHttpClient,
try {
// max 10 mins to download
// FIXME: should show download progress
const authorization = optionalConfig?.remoteAuthToken ? `Bearer ${optionalConfig?.remoteAuthToken}` : '';
const authorization = optionalConfig?.remoteAuthToken ? `Bearer ${optionalConfig.remoteAuthToken}` : '';
const { status, headers, res } = await httpclient.request(url, {
timeout: 60000 * 10,
headers: { authorization },

View File

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

View File

@@ -9,6 +9,7 @@ interface RegistryData extends EntityData {
changeStream: string;
userPrefix: string;
type: RegistryType;
authToken?: string;
}
export type CreateRegistryData = Omit<EasyData<RegistryData, 'registryId'>, 'id'>;
@@ -20,6 +21,7 @@ export class Registry extends Entity {
changeStream: string;
userPrefix: string;
type: RegistryType;
authToken?: string;
constructor(data: RegistryData) {
super(data);
@@ -29,10 +31,11 @@ export class Registry extends Entity {
this.changeStream = data.changeStream;
this.userPrefix = data.userPrefix;
this.type = data.type;
this.authToken = data.authToken;
}
public static create(data: CreateRegistryData): Registry {
const newData = EntityUtil.defaultData(data, 'registryId');
const newData = EntityUtil.defaultData<RegistryData, 'registryId'>(data, 'registryId');
return new Registry(newData);
}
}

View File

@@ -31,7 +31,6 @@ export interface TaskData<T = TaskBaseData> extends EntityData {
export type SyncPackageTaskOptions = {
authorId?: string;
authorIp?: string;
remoteAuthToken?: string;
tips?: string;
skipDependencies?: boolean;
syncDownloadData?: boolean;
@@ -52,7 +51,6 @@ export interface TriggerHookTaskData extends TaskBaseData {
}
export interface CreateSyncPackageTaskData extends TaskBaseData {
remoteAuthToken?: string;
tips?: string;
skipDependencies?: boolean;
syncDownloadData?: boolean;
@@ -133,7 +131,6 @@ export class Task<T extends TaskBaseData = TaskBaseData> extends Entity {
data: {
// task execute worker
taskWorker: '',
remoteAuthToken: options?.remoteAuthToken,
tips: options?.tips,
registryId: options?.registryId ?? '',
skipDependencies: options?.skipDependencies,

View File

@@ -146,9 +146,17 @@ export class BinarySyncerService extends AbstractService {
task.error = 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 (err.name === 'HttpClientRequestTimeoutError'
|| err.name === 'ConnectionError'
|| err.name === 'ConnectTimeoutError') {
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 this.taskService.finishTask(task, TaskState.Fail, logs.join('\n'));
}
}
@@ -210,7 +218,11 @@ export class BinarySyncerService extends AbstractService {
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}`);
}

View File

@@ -101,8 +101,14 @@ export class ChangesStreamService extends AbstractService {
await setTimeout(this.config.cnpmcore.checkChangesStreamInterval);
}
} catch (err) {
this.logger.error('[ChangesStreamService.executeTask:error] %s, exit now', err);
this.logger.error(err);
this.logger.warn('[ChangesStreamService.executeTask:error] %s, exit now', err.message);
if (err.name === 'HttpClientRequestTimeoutError'
|| err.name === 'ConnectTimeoutError'
|| err.name === 'BodyTimeoutError') {
this.logger.warn(err);
} else {
this.logger.error(err);
}
task.error = `${err}`;
await this.taskRepository.saveTask(task);
await this.suspendSync();

View File

@@ -1,5 +1,5 @@
import { AccessLevel, Inject, SingletonProto } from '@eggjs/tegg';
import type { estypes } from '@elastic/elasticsearch';
import { estypes, errors } from '@elastic/elasticsearch';
import dayjs from 'dayjs';
import { AbstractService } from '../../common/AbstractService';
@@ -67,7 +67,7 @@ export class PackageSearchService extends AbstractService {
keywords: manifest.keywords || [],
versions: Object.keys(manifest.versions),
description: manifest.description,
license: manifest.license,
license: typeof manifest.license === 'object' ? manifest.license?.type : manifest.license,
maintainers: manifest.maintainers,
author: formatAuthor(manifest.author),
'dist-tags': manifest['dist-tags'],
@@ -138,7 +138,16 @@ export class PackageSearchService extends AbstractService {
}
async removePackage(fullname: string) {
return await this.searchRepository.removePackage(fullname);
try {
return await this.searchRepository.removePackage(fullname);
} catch (error) {
// if the package does not exist, returns success
if (error instanceof errors.ResponseError && error?.statusCode === 404) {
this.logger.warn('[PackageSearchService.removePackage] remove package:%s not found', fullname);
return fullname;
}
throw error;
}
}
// https://github.com/npms-io/queries/blob/master/lib/search.js#L8C1-L78C2
@@ -213,7 +222,7 @@ export class PackageSearchService extends AbstractService {
private _buildScriptScore(params: { text: string | undefined, scoreEffect: number }) {
// keep search simple, only download(popularity)
const downloads = 'doc["downloads.all"].value';
const source = `doc["package.name.raw"].value.equals("${params.text}") ? 100000 + ${downloads} : _score * Math.pow(${downloads}, ${params.scoreEffect})`;
const source = `doc["package.name.raw"].value.equals(params.text) ? 100000 + ${downloads} : _score * Math.pow(${downloads}, params.scoreEffect)`;
return {
script: {
source,

View File

@@ -121,13 +121,13 @@ export class PackageSyncerService extends AbstractService {
const start = '2011-01-01';
const end = this.config.cnpmcore.syncDownloadDataMaxDate;
const registry = this.config.cnpmcore.syncDownloadDataSourceRegistry;
const remoteAuthToken = await this.registryManagerService.getAuthTokenByRegistryHost(registry);
const logs: string[] = [];
let downloads: { day: string; downloads: number }[];
logs.push(`[${isoNow()}][DownloadData] 🚧🚧🚧🚧🚧 Syncing "${fullname}" download data "${start}:${end}" on ${registry} 🚧🚧🚧🚧🚧`);
const failEnd = '❌❌❌❌❌ 🚮 give up 🚮 ❌❌❌❌❌';
try {
const { remoteAuthToken } = task.data as SyncPackageTaskOptions;
const { data, status, res } = await this.npmRegistry.getDownloadRanges(registry, fullname, start, end, { remoteAuthToken });
downloads = data.downloads || [];
logs.push(`[${isoNow()}][DownloadData] 🚧 HTTP [${status}] timing: ${JSON.stringify(res.timing)}, downloads: ${downloads.length}`);
@@ -164,7 +164,7 @@ export class PackageSyncerService extends AbstractService {
private async syncUpstream(task: Task) {
const registry = this.npmRegistry.registry;
const fullname = task.targetName;
const { remoteAuthToken } = task.data as SyncPackageTaskOptions;
const remoteAuthToken = await this.registryManagerService.getAuthTokenByRegistryHost(registry);
let logs: string[] = [];
let logId = '';
logs.push(`[${isoNow()}][UP] 🚧🚧🚧🚧🚧 Waiting sync "${fullname}" task on ${registry} 🚧🚧🚧🚧🚧`);
@@ -351,10 +351,11 @@ export class PackageSyncerService extends AbstractService {
public async executeTask(task: Task) {
const fullname = task.targetName;
const [ scope, name ] = getScopeAndName(fullname);
const { tips, skipDependencies: originSkipDependencies, syncDownloadData, forceSyncHistory, remoteAuthToken, specificVersions } = task.data as SyncPackageTaskOptions;
const { tips, skipDependencies: originSkipDependencies, syncDownloadData, forceSyncHistory, specificVersions } = task.data as SyncPackageTaskOptions;
let pkg = await this.packageRepository.findPackage(scope, name);
const registry = await this.initSpecRegistry(task, pkg, scope);
const registryHost = this.npmRegistry.registry;
const remoteAuthToken = registry.authToken;
let logs: string[] = [];
if (tips) {
logs.push(`[${isoNow()}] 👉👉👉👉👉 Tips: ${tips} 👈👈👈👈👈`);
@@ -660,7 +661,11 @@ export class PackageSyncerService extends AbstractService {
localFile = tmpfile;
logs.push(`[${isoNow()}] 🚧 [${syncIndex}] HTTP content-length: ${headers['content-length']}, timing: ${JSON.stringify(timing)} => ${localFile}`);
} catch (err: any) {
this.logger.error('Download tarball %s error: %s', tarball, err);
if (err.name === 'DownloadNotFoundError' || err.name === 'DownloadStatusInvalidError') {
this.logger.warn('Download tarball %s error: %s', tarball, err);
} else {
this.logger.error('Download tarball %s error: %s', tarball, err);
}
lastErrorMessage = `download tarball error: ${err}`;
logs.push(`[${isoNow()}] ❌ [${syncIndex}] Synced version ${version} fail, ${lastErrorMessage}`);
await this.taskService.appendTaskLog(task, logs.join('\n'));
@@ -875,7 +880,6 @@ export class PackageSyncerService extends AbstractService {
authorId: task.authorId,
authorIp: task.authorIp,
tips,
remoteAuthToken,
});
logs.push(`[${isoNow()}] 📦 Add dependency "${dependencyName}" sync task: ${dependencyTask.taskId}, db id: ${dependencyTask.id}`);
}

View File

@@ -14,10 +14,10 @@ import { Task } from '../entity/Task';
import { ChangesStreamMode, PresetRegistryName } from '../../common/constants';
import { RegistryType } from '../../common/enum/Registry';
export interface CreateRegistryCmd extends Pick<Registry, 'changeStream' | 'host' | 'userPrefix' | 'type' | 'name'> {
export interface CreateRegistryCmd extends Pick<Registry, 'changeStream' | 'host' | 'userPrefix' | 'type' | 'name' | 'authToken' > {
operatorId?: string;
}
export interface UpdateRegistryCmd extends Pick<Registry, 'changeStream' | 'host' | 'userPrefix' | 'type' | 'name' | 'registryId'> {
export interface UpdateRegistryCmd extends Pick<Registry, 'changeStream' | 'host' | 'type' | 'name' | 'authToken' > {
operatorId?: string;
}
export interface RemoveRegistryCmd extends Pick<Registry, 'registryId'> {
@@ -61,7 +61,7 @@ export class RegistryManagerService extends AbstractService {
}
async createRegistry(createCmd: CreateRegistryCmd): Promise<Registry> {
const { name, changeStream = '', host, userPrefix = '', type, operatorId = '-' } = createCmd;
const { name, changeStream = '', host, userPrefix = '', type, operatorId = '-', authToken } = createCmd;
this.logger.info('[RegistryManagerService.createRegistry:prepare] operatorId: %s, createCmd: %j', operatorId, createCmd);
const registry = Registry.create({
name,
@@ -69,6 +69,7 @@ export class RegistryManagerService extends AbstractService {
host,
userPrefix,
type,
authToken,
});
await this.registryRepository.saveRegistry(registry);
return registry;
@@ -76,8 +77,8 @@ export class RegistryManagerService extends AbstractService {
// 更新部分 registry 信息
// 不允许 userPrefix 字段变更
async updateRegistry(updateCmd: UpdateRegistryCmd) {
const { name, changeStream, host, type, registryId, operatorId = '-' } = updateCmd;
async updateRegistry(registryId: string, updateCmd: UpdateRegistryCmd) {
const { name, changeStream, host, type, operatorId = '-', authToken } = updateCmd;
this.logger.info('[RegistryManagerService.updateRegistry:prepare] operatorId: %s, updateCmd: %j', operatorId, updateCmd);
const registry = await this.registryRepository.findRegistryByRegistryId(registryId);
if (!registry) {
@@ -88,6 +89,7 @@ export class RegistryManagerService extends AbstractService {
changeStream,
host,
type,
authToken,
});
await this.registryRepository.saveRegistry(registry);
}
@@ -105,6 +107,10 @@ export class RegistryManagerService extends AbstractService {
return await this.registryRepository.findRegistry(registryName);
}
async findByRegistryHost(host?: string): Promise<Registry | null> {
return host ? await this.registryRepository.findRegistryByRegistryHost(host) : null;
}
// 删除 Registry 方法
// 可选传入 operatorId 作为参数,用于记录操作人员
// 同时删除对应的 scope 数据
@@ -156,4 +162,12 @@ export class RegistryManagerService extends AbstractService {
}
async getAuthTokenByRegistryHost(host: string): Promise<string|undefined> {
const registry = await this.findByRegistryHost(host);
if (!registry) {
return undefined;
}
return registry.authToken;
}
}

View File

@@ -67,7 +67,6 @@ export class PackageSyncController extends AbstractController {
const params = {
fullname,
remoteAuthToken: data.remoteAuthToken,
tips,
skipDependencies: !!data.skipDependencies,
syncDownloadData: !!data.syncDownloadData,
@@ -97,7 +96,6 @@ export class PackageSyncController extends AbstractController {
const task = await this.packageSyncerService.createTask(params.fullname, {
authorIp: ctx.ip,
authorId: authorized?.user.userId,
remoteAuthToken: params.remoteAuthToken,
tips: params.tips,
skipDependencies: params.skipDependencies,
syncDownloadData: params.syncDownloadData,

View File

@@ -13,10 +13,10 @@ import {
import { NotFoundError } from 'egg-errors';
import { AbstractController } from './AbstractController';
import { Static } from 'egg-typebox-validate/typebox';
import { RegistryManagerService } from '../../core/service/RegistryManagerService';
import { RegistryManagerService, UpdateRegistryCmd } from '../../core/service/RegistryManagerService';
import { AdminAccess } from '../middleware/AdminAccess';
import { ScopeManagerService } from '../../core/service/ScopeManagerService';
import { RegistryCreateOptions, QueryPageOptions, RegistryCreateSyncOptions } from '../typebox';
import { RegistryCreateOptions, QueryPageOptions, RegistryCreateSyncOptions, RegistryUpdateOptions } from '../typebox';
@HTTPController()
export class RegistryController extends AbstractController {
@@ -67,7 +67,7 @@ export class RegistryController extends AbstractController {
async createRegistry(@Context() ctx: EggContext, @HTTPBody() registryOptions: Static<typeof RegistryCreateOptions>) {
ctx.tValidate(RegistryCreateOptions, registryOptions);
const authorizedUser = await this.userRoleManager.requiredAuthorizedUser(ctx, 'setting');
const { name, changeStream, host, userPrefix = '', type } = registryOptions;
const { name, changeStream, host, userPrefix = '', type, authToken } = registryOptions;
await this.registryManagerService.createRegistry({
name,
changeStream,
@@ -75,6 +75,7 @@ export class RegistryController extends AbstractController {
userPrefix,
operatorId: authorizedUser.userId,
type,
authToken,
});
return { ok: true };
}
@@ -106,4 +107,29 @@ export class RegistryController extends AbstractController {
await this.registryManagerService.remove({ registryId: id, operatorId: authorizedUser.userId });
return { ok: true };
}
@HTTPMethod({
path: '/-/registry/:id',
method: HTTPMethodEnum.PATCH,
})
@Middleware(AdminAccess)
async updateRegistry(@Context() ctx: EggContext, @HTTPParam() id: string, @HTTPBody() updateRegistryOptions: Partial<UpdateRegistryCmd>) {
ctx.tValidate(RegistryUpdateOptions, updateRegistryOptions);
const registry = await this.registryManagerService.findByRegistryId(id);
if (!registry) {
throw new NotFoundError('registry not found');
} else {
const { name, changeStream, host, type, authToken } = registry;
const _updateRegistryOptions = {
name,
changeStream,
host,
type,
authToken,
...updateRegistryOptions,
};
await this.registryManagerService.updateRegistry(registry.registryId, _updateRegistryOptions);
}
return { ok: true };
}
}

View File

@@ -23,6 +23,18 @@ export class DownloadPackageVersionTarController extends AbstractController {
@Inject()
private nfsAdapter: NFSAdapter;
// Support OPTIONS Request on tgz download
@HTTPMethod({
// GET /:fullname/-/:filenameWithVersion.tgz
path: `/:fullname(${FULLNAME_REG_STRING})/-/:filenameWithVersion.tgz`,
method: HTTPMethodEnum.OPTIONS,
})
async downloadForOptions(@Context() ctx: EggContext) {
ctx.set('access-control-allow-origin', '*');
ctx.set('access-control-allow-methods', 'GET,HEAD');
ctx.status = 204;
}
@HTTPMethod({
// GET /:fullname/-/:filenameWithVersion.tgz
path: `/:fullname(${FULLNAME_REG_STRING})/-/:filenameWithVersion.tgz`,

View File

@@ -29,7 +29,20 @@ export class SyncBinaryWorker {
this.logger.info('[SyncBinaryWorker:executeTask:start] taskId: %s, targetName: %s, attempts: %s, params: %j, updatedAt: %s, delay %sms',
task.taskId, task.targetName, task.attempts, task.data, task.updatedAt,
startTime - task.updatedAt.getTime());
await this.binarySyncerService.executeTask(task);
try {
await this.binarySyncerService.executeTask(task);
} catch (err) {
const use = Date.now() - startTime;
this.logger.warn('[SyncBinaryWorker:executeTask:error] taskId: %s, targetName: %s, use %sms, error: %s',
task.taskId, task.targetName, use, err.message);
if (err.name === 'ConnectTimeoutError'
|| err.name === 'HttpClientRequestTimeoutError') {
this.logger.warn(err);
} else {
this.logger.error(err);
}
return;
}
const use = Date.now() - startTime;
this.logger.info('[SyncBinaryWorker:executeTask:success] taskId: %s, targetName: %s, use %sms',
task.taskId, task.targetName, use);

View File

@@ -79,12 +79,6 @@ export const TagWithVersionRule = Type.Object({
export const SyncPackageTaskRule = Type.Object({
fullname: Name,
remoteAuthToken: Type.Optional(
Type.String({
transform: [ 'trim' ],
maxLength: 200,
}),
),
tips: Type.String({
transform: [ 'trim' ],
maxLength: 1024,
@@ -210,35 +204,44 @@ export const RegistryCreateOptions = Type.Object({
maxLength: 256,
})),
type: Type.Enum(RegistryType),
authToken: Type.Optional(
Type.String({
transform: [ 'trim' ],
maxLength: 256,
}),
),
});
export const RegistryUpdateOptions = Type.Object({
name: Type.String({
transform: [ 'trim' ],
minLength: 1,
maxLength: 256,
}),
host: Type.String({
transform: [ 'trim' ],
minLength: 1,
maxLength: 4096,
}),
changeStream: Type.String({
transform: [ 'trim' ],
minLength: 1,
maxLength: 4096,
}),
userPrefix: Type.Optional(Type.String({
transform: [ 'trim' ],
minLength: 1,
maxLength: 256,
})),
type: Type.Enum(RegistryType),
registryId: Type.String({
transform: [ 'trim' ],
minLength: 1,
maxLength: 256,
}),
name: Type.Optional(
Type.String({
transform: [ 'trim' ],
minLength: 1,
maxLength: 256,
}),
),
host: Type.Optional(
Type.String({
transform: [ 'trim' ],
minLength: 1,
maxLength: 4096,
}),
),
changeStream: Type.Optional(
Type.String({
transform: [ 'trim' ],
minLength: 1,
maxLength: 4096,
}),
),
type: Type.Optional(Type.Enum(RegistryType)),
authToken: Type.Optional(
Type.String({
transform: [ 'trim' ],
minLength: 1,
maxLength: 256,
}),
),
});
export const ScopeCreateOptions = Type.Object({

View File

@@ -51,7 +51,7 @@ export type PackageJSONType = CnpmcorePatchInfo & {
url?: string;
email?: string;
};
license?: string;
license?: LicenseType | string;
author?: AuthorType | string;
contributors?: ContributorType[] | string[];
maintainers?: ContributorType[] | string[];
@@ -131,6 +131,11 @@ export type AuthorType = {
url?: string;
};
type LicenseType = {
type: string;
url: string;
};
type ContributorType = {
name?: string;
email?: string;

View File

@@ -38,6 +38,14 @@ export class RegistryRepository extends AbstractRepository {
return null;
}
async findRegistryByRegistryHost(host: string): Promise<RegistryEntity | null> {
const model = await this.Registry.findOne({ host });
if (model) {
return ModelConvertor.convertModelToEntity(model, RegistryEntity);
}
return null;
}
async saveRegistry(registry: Registry) {
if (registry.id) {
const model = await this.Registry.findOne({ id: registry.id });

View File

@@ -36,4 +36,7 @@ export class Registry extends Bone {
@Attribute(DataTypes.STRING(256))
type: RegistryType;
@Attribute(DataTypes.STRING(256), { name: 'auth_token' })
authToken?: string;
}

View File

@@ -881,6 +881,13 @@ const binaries = {
npmPackageName: 'libpg-query',
},
},
'fuse-t': {
category: 'fuse-t',
description: 'FUSE-T is a kext-less implementation of FUSE for macOS that uses NFS v4 local server instead of a kernel extension.',
type: BinaryType.GitHub,
repo: 'macos-fuse-t/fuse-t',
distUrl: 'https://github.com/macos-fuse-t/fuse-t/releases',
},
} as const;
export type BinaryName = keyof typeof binaries;

View File

@@ -100,6 +100,8 @@ export default (appInfo: EggAppConfig) => {
return ctx.get('Origin');
},
credentials: true,
// https://github.com/koajs/cors/blob/master/index.js#L10C57-L10C64
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS',
};
config.nfs = {

View File

@@ -1,5 +1,30 @@
version: '3.6'
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ../..:/workspaces:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
depends_on:
- mysql
- redis
networks:
- cnpm
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
# networks:
# - cnpm
# Uncomment the next line to use a non-root user for all processes.
# user: node
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
redis:
image: redis:6-alpine
# command: redis-server --appendonly yes --requirepass cnpm
@@ -7,7 +32,7 @@ services:
volumes:
- cnpm-redis:/data
ports:
- 6379:6379
- 6379
networks:
- cnpm
@@ -16,7 +41,7 @@ services:
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
restart: always
environment:
MYSQL_ROOT_PASSWORD:
MYSQL_ROOT_PASSWORD: root
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
# MYSQL_DATABASE: 'cnpmcore_unittest'
MYSQL_USER: user
@@ -25,33 +50,30 @@ services:
- cnpm-mysql:/var/lib/mysql
# - ./conf.d/mysql/:/etc/mysql/conf.d
# - ./init.d/mysql/:/docker-entrypoint-initdb.d
- ./sql:/docker-entrypoint-initdb.d/sql
- ./prepare-database.sh:/docker-entrypoint-initdb.d/init.sh
ports:
- 3306:3306
- 3306
networks:
- cnpm
# database explorer
phpmyadmin:
image: phpmyadmin
adminer:
image: adminer
restart: always
environment:
MYSQL_ROOT_PASSWORD:
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
MYSQL_USER: user
MYSQL_PASSWORD: pass
PMA_HOST: 'mysql'
ports:
- 8080:80
networks:
- cnpm
ADMINER_DEFAULT_DB_HOST: 'mysql'
depends_on:
- mysql
ports:
- 8080
networks:
- cnpm
volumes:
cnpm-redis:
cnpm-mysql:
networks:
cnpm:
name: cnpm

View File

@@ -1,6 +1,6 @@
{
"name": "cnpmcore",
"version": "3.43.4",
"version": "3.48.3",
"description": "npm core",
"files": [
"dist/**/*"
@@ -97,13 +97,13 @@
"eggjs-elasticsearch": "^0.0.6",
"fs-cnpm": "^2.4.0",
"ioredis": "^5.3.1",
"leoric": "^2.6.2",
"leoric": "^2.11.5",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"mysql": "^2.18.1",
"mysql2": "^2.3.0",
"npm-package-arg": "^10.1.0",
"oss-cnpm": "^4.0.0",
"oss-cnpm": "^5.0.1",
"p-map": "^4.0.0",
"s3-cnpmcore": "^1.1.2",
"semver": "^7.3.5",
@@ -120,22 +120,22 @@
"@cnpmjs/npm-cli-login": "^1.1.0",
"@elastic/elasticsearch-mock": "^2.0.0",
"@simplewebauthn/typescript-types": "^7.0.0",
"@types/lodash": "^4.14.196",
"@types/mime-types": "^2.1.1",
"@types/mocha": "^10.0.1",
"@types/mysql": "^2.15.21",
"@types/npm-package-arg": "^6.1.1",
"@types/semver": "^7.3.12",
"@types/tar": "^6.1.4",
"@types/ua-parser-js": "^0.7.36",
"@types/validate-npm-package-name": "^4.0.0",
"coffee": "^5.4.0",
"egg-bin": "^6.0.0",
"egg-mock": "^5.10.4",
"eslint": "^8.29.0",
"eslint-config-egg": "^12.1.0",
"eslint-config-egg": "^13.0.0",
"git-contributor": "2",
"typescript": "^5.0.4",
"@types/ua-parser-js": "^0.7.36",
"@types/lodash": "^4.14.196",
"@types/npm-package-arg": "^6.1.1",
"@types/validate-npm-package-name": "^4.0.0"
"typescript": "^5.2.2"
},
"author": "killagu",
"license": "MIT",

1
sql/3.46.0.sql Normal file
View File

@@ -0,0 +1 @@
ALTER TABLE `registries` ADD COLUMN `auth_token` varchar(256) DEFAULT NULL COMMENT 'registry auth token';

View File

@@ -49,7 +49,7 @@ export class TestUtil {
host: process.env.MYSQL_HOST || '127.0.0.1',
port: process.env.MYSQL_PORT || 3306,
user: process.env.MYSQL_USER || 'root',
password: process.env.MYSQL_PASSWORD,
password: process.env.MYSQL_PASSWORD || '',
multipleStatements: true,
};
}

View File

@@ -1,6 +1,7 @@
import assert from 'assert';
import { app } from 'egg-mock/bootstrap';
import { ChromeForTestingBinary } from '../../../../app/common/adapter/binary/ChromeForTestingBinary';
import { TestUtil } from '../../../../test/TestUtil';
describe('test/common/adapter/binary/ChromeForTestingBinary.test.ts', () => {
let binary: ChromeForTestingBinary;
@@ -9,21 +10,43 @@ describe('test/common/adapter/binary/ChromeForTestingBinary.test.ts', () => {
});
describe('fetch()', () => {
it('should work for chrome binary', async () => {
assert.equal(ChromeForTestingBinary.lastTimestamp, '');
app.mockHttpclient('https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json', 'GET', {
data: await TestUtil.readFixturesFile('chrome-for-testing/known-good-versions-with-downloads.json'),
persist: false,
});
const result = await binary.fetch('/');
const latestVersion = result?.items?.[0].name;
const latestVersion = result!.items![result!.items.length - 1].name;
assert(latestVersion);
assert.equal(latestVersion, '119.0.6008.0/');
const platformRes = await binary.fetch(`/${latestVersion}`);
const platforms = platformRes?.items.map(item => item.name);
assert(platforms);
assert.deepEqual(platforms, [ 'linux64/', 'mac-arm64/', 'mac-x64/', 'win32/', 'win64/' ]);
for (const platform of platforms) {
const versionRes = await binary.fetch(`/${latestVersion}${platform}`);
const versions = versionRes?.items.map(item => item.name);
assert.equal(versions?.length, 1);
assert.equal(versions?.length, 3);
assert(versionRes?.items[0].name);
assert.equal(versionRes?.items[0].isDir, false);
assert.match(versionRes?.items[0].name, /^chrome\-/);
assert.match(versionRes?.items[1].name, /^chromedriver\-/);
assert.match(versionRes?.items[2].name, /^chrome\-headless\-shell\-/);
}
assert(ChromeForTestingBinary.lastTimestamp);
});
it('should return empty when timestamp is not changed', async () => {
assert(ChromeForTestingBinary.lastTimestamp);
await binary.initFetch();
app.mockHttpclient('https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json', 'GET', {
data: await TestUtil.readFixturesFile('chrome-for-testing/known-good-versions-with-downloads.json'),
persist: false,
});
const result = await binary.fetch('/');
assert.equal(result?.items.length, 0);
});
});
});

View File

@@ -428,8 +428,10 @@ describe('test/core/service/PackageSyncerService/executeTask.test.ts', () => {
assert(log.includes('] 📦 Add dependency "@resvg/resvg-js-win32-x64-msvc" sync task: '));
});
it('should bring auth token when set remoteAuthToken', async () => {
it('should bring auth token which in registry database.', async () => {
const testToken = 'test-auth-token';
const registry = await registryManagerService.ensureDefaultRegistry();
await registryManagerService.updateRegistry(registry.registryId, { ...registry, authToken: testToken });
const fullManifests = await TestUtil.readFixturesFile('registry.npmjs.org/foobar.json');
const tgzBuffer1_0_0 = await TestUtil.readFixturesFile('registry.npmjs.org/foobar/-/foobar-1.0.0.tgz');
const tgzBuffer1_1_0 = await TestUtil.readFixturesFile('registry.npmjs.org/foobar/-/foobar-1.1.0.tgz');
@@ -459,7 +461,7 @@ describe('test/core/service/PackageSyncerService/executeTask.test.ts', () => {
persist: false,
};
});
await packageSyncerService.createTask('foobar', { skipDependencies: true, remoteAuthToken: testToken });
await packageSyncerService.createTask('foobar', { skipDependencies: true });
const task = await packageSyncerService.findExecuteTask();
assert(task);
await packageSyncerService.executeTask(task);

View File

@@ -74,7 +74,7 @@ describe('test/core/service/RegistryManagerService/index.test.ts', () => {
let queryRes = await registryManagerService.listRegistries({});
const [ registry ] = queryRes.data;
await registryManagerService.updateRegistry({
await registryManagerService.updateRegistry(registry.registryId, {
...registry,
name: 'custom3',
});
@@ -89,9 +89,8 @@ describe('test/core/service/RegistryManagerService/index.test.ts', () => {
assert(queryRes.count === 1);
const [ registry ] = queryRes.data;
await assert.rejects(
registryManagerService.updateRegistry({
registryManagerService.updateRegistry('not_exist', {
...registry,
registryId: 'not_exist',
name: 'boo',
}),
/not found/,

File diff suppressed because it is too large Load Diff

View File

@@ -7,11 +7,12 @@ describe('test/port/controller/HomeController/cors.test.ts', () => {
const res = await app.httpRequest()
.get('/-/ping')
.set('origin', 'https://www.test-cors.org');
assert(res.status === 200);
assert(res.body.pong === true);
assert(res.headers.vary === 'Origin');
assert(res.headers['access-control-allow-origin'] === 'https://www.test-cors.org');
assert(res.headers['access-control-allow-credentials'] === 'true');
assert.equal(res.status, 200);
assert.equal(res.body.pong, true);
assert.equal(res.headers.vary, 'Origin');
assert.equal(res.headers['access-control-allow-origin'], 'https://www.test-cors.org');
assert.equal(res.headers['access-control-allow-credentials'], 'true');
assert(!res.headers['access-control-allow-methods']);
});
it('should OPTIONS work', async () => {
@@ -20,11 +21,12 @@ describe('test/port/controller/HomeController/cors.test.ts', () => {
.set('origin', 'https://www.test-cors.org/foo')
.set('Access-Control-Request-Method', 'OPTIONS')
.set('Access-Control-Request-Headers', 'authorization');
assert(res.status === 204);
assert(res.headers.vary === 'Origin');
assert(res.headers['access-control-allow-origin'] === 'https://www.test-cors.org/foo');
assert(res.headers['access-control-allow-credentials'] === 'true');
assert(res.headers['access-control-allow-headers'] === 'authorization');
assert.equal(res.status, 204);
assert.equal(res.headers.vary, 'Origin');
assert.equal(res.headers['access-control-allow-origin'], 'https://www.test-cors.org/foo');
assert.equal(res.headers['access-control-allow-credentials'], 'true');
assert.equal(res.headers['access-control-allow-headers'], 'authorization');
assert.equal(res.headers['access-control-allow-methods'], 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS');
});
});
});

View File

@@ -245,4 +245,36 @@ describe('test/port/controller/RegistryController/index.test.ts', () => {
});
});
describe('[PATCH /-/registry/:id] updateRegistry()', () => {
it('should 403', async () => {
await app.httpRequest()
.patch(`/-/registry/${registry.registryId}`)
.expect(403);
});
it('should 404 when not found', async () => {
await app.httpRequest()
.patch('/-/registry/registry-not-exists')
.set('authorization', adminUser.authorization)
.expect(404);
});
it('should update auth token success', async () => {
await app.httpRequest()
.patch(`/-/registry/${registry.registryId}`)
.set('authorization', adminUser.authorization)
.send({
authToken: 'testAuthToekn',
})
.expect(200);
const registList = await app.httpRequest()
.get('/-/registry')
.expect(200);
const latestToken = await registList.body.data[0].authToken;
assert.equal(latestToken, 'testAuthToekn');
});
});
});

View File

@@ -53,6 +53,23 @@ describe('test/port/controller/package/DownloadPackageVersionTarController.test.
assert(res.headers.location === `https://cdn.mock.com/packages/${scopedName}/1.0.0/${name}-1.0.0.tgz`);
});
it('should support cors OPTIONS Request', async () => {
mock(nfsClientAdapter, 'url', async (storeKey: string) => {
return `https://cdn.mock.com${storeKey}`;
});
let res = await app.httpRequest()
.options(`/${name}/-/testmodule-download-version-tar-1.0.0.tgz`);
assert.equal(res.status, 204);
assert.equal(res.headers['access-control-allow-origin'], '*');
assert.equal(res.headers['access-control-allow-methods'], 'GET,HEAD');
res = await app.httpRequest()
.options(`/${scopedName}/-/testmodule-download-version-tar-1.0.0.tgz`);
assert.equal(res.status, 204);
assert.equal(res.headers['access-control-allow-origin'], '*');
assert.equal(res.headers['access-control-allow-methods'], 'GET,HEAD');
});
if (process.env.CNPMCORE_NFS_TYPE !== 'oss') {
it('should download a version tar redirect to mock cdn success with url function is not async function', async () => {
mock(nfsClientAdapter, 'url', (storeKey: string) => {

View File

@@ -1,5 +1,6 @@
import assert from 'assert';
import { app, mock } from 'egg-mock/bootstrap';
import { errors } from '@elastic/elasticsearch';
import { mockES } from '../../../../config/config.unittest';
import { TestUtil } from '../../../TestUtil';
@@ -123,5 +124,42 @@ describe('test/port/controller/package/SearchPackageController.test.ts', () => {
.set('authorization', admin.authorization);
assert.equal(res.body.package, name);
});
it('should delete a non existent package', async () => {
const name = 'non-existent-search-package';
mockES.add({
method: 'DELETE',
path: `/${app.config.cnpmcore.elasticsearchIndex}/_doc/:id`,
}, () => {
return new errors.ResponseError({
body: { errors: {}, status: 404 },
statusCode: 404,
warnings: null,
meta: {
name: '',
context: '',
request: {
params: {
method: 'delete',
path: `/${app.config.cnpmcore.elasticsearchIndex}/_doc/:id`,
},
options: {},
id: '',
},
connection: null,
attempts: 1,
aborted: true,
},
});
});
mock(app.config.cnpmcore, 'allowPublishNonScopePackage', true);
mock(app.config.cnpmcore, 'enableElasticsearch', true);
mock(app.config.cnpmcore, 'registry', 'https://registry.example.com');
const res = await app.httpRequest()
.delete(`/-/v1/search/sync/${name}`)
.set('authorization', admin.authorization);
assert.equal(res.body.package, name);
});
});
});

View File

@@ -16,6 +16,7 @@ describe('test/repository/RegistryRepository.test.ts', () => {
changeStream: 'https://r.npmjs.com/_changes',
host: 'https://registry.npmjs.org',
type: 'cnpmcore' as RegistryType,
authToken: '',
})) as Registry;
});
@@ -27,6 +28,7 @@ describe('test/repository/RegistryRepository.test.ts', () => {
changeStream: 'https://ra.npmjs.com/_changes',
host: 'https://registry.npmjs.org',
type: 'cnpmcore' as RegistryType,
authToken: '',
})) as Registry;
assert(newRegistry);
assert(newRegistry.type === 'cnpmcore');

View File

@@ -10,7 +10,6 @@
"useUnknownInCatchVariables": false,
},
"exclude": [
"test",
"node_modules"
],
}