Commit ec0e667b authored by 神楽坂玲奈's avatar 神楽坂玲奈

init

parent 205b8fd2
FROM node
RUN apt-get update
RUN apt-get install aria2 -y
RUN apt-get install curl -y
RUN curl --location --retry 5 --output ossutil 'https://github.com/mycard/ossutil/releases/download/1.0.0.Beta2/ossutil'
RUN chmod +x ossutil && mv ossutil /usr/local/bin/ossutil
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json /usr/src/app
RUN npm install
COPY . /usr/src/app
EXPOSE 8080
ENTRYPOINT "./entrypoint.sh"
#允许rpc
enable-rpc=true
#允许非外部访问
rpc-listen-all=true
#RPC端口, 仅当默认端口被占用时修改
rpc-listen-port=6800
#最大同时下载数(任务数), 路由建议值: 3
max-concurrent-downloads=10
#断点续传
continue=true
#同服务器连接数
max-connection-per-server=10
#最小文件分片大小, 下载线程数上限取决于能分出多少片, 对于小文件重要
min-split-size=10M
#单文件最大线程数, 路由建议值: 5
split=10
#下载速度限制
max-overall-download-limit=0
#单文件速度限制
max-download-limit=0
#上传速度限制
max-overall-upload-limit=0
#单文件速度限制
max-upload-limit=0
#!/usr/bin/env node
const {bundle} = require('../package/main')
bundle(process.argv[2], process.argv[3])
import * as path from 'path';
let baseUrl = 'http://127.0.01:8001'
let testUrl = 'http://114.215.243.95:8001'
export default {
upload_path: path.join(__dirname, './test/upload'),
download_path: path.join(__dirname, './test/release/downloads'),
new_apps_json: `${baseUrl}/v2/apps`,
upload_url: `${testUrl}/v1/upload/packageUrl`,
old_apps_json: 'https://api.moecube.com/apps.json',
new_package: `${baseUrl}/v1/package/`,
new_app: (appId) => `${baseUrl}/v1/app/${appId}`,
old_metalinks: (package_id) => `https://cdn01.moecube.com/release/metalinks/${package_id}.meta4`,
new_metalinks: (package_id) => `${baseUrl}/v2/package-legacy/${package_id}/meta`,
old_checksums: (package_id) => `https://cdn01.moecube.com/release/checksums/${package_id}`,
new_checksums: (package_id) => `${baseUrl}/v2/package-legacy/${package_id}/checksum`,
};
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
# ConsoleWeb
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.1.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"console-web": {
"projectType": "application",
"schematics": {
"@schematics/angular:class": {
"skipTests": true
},
"@schematics/angular:component": {
"skipTests": true
},
"@schematics/angular:directive": {
"skipTests": true
},
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:interceptor": {
"skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
},
"@schematics/angular:application": {
"strict": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/console-web",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "console-web:build:production"
},
"development": {
"browserTarget": "console-web:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "console-web:build"
}
}
}
}
},
"defaultProject": "console-web"
}
This diff is collapsed.
{
"name": "console-web",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
"private": true,
"dependencies": {
"@angular/animations": "~12.2.0",
"@angular/common": "~12.2.0",
"@angular/compiler": "~12.2.0",
"@angular/core": "~12.2.0",
"@angular/forms": "~12.2.0",
"@angular/platform-browser": "~12.2.0",
"@angular/platform-browser-dynamic": "~12.2.0",
"@angular/router": "~12.2.0",
"rxjs": "~6.6.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~12.2.1",
"@angular/cli": "~12.2.1",
"@angular/compiler-cli": "~12.2.0",
"@types/node": "^12.11.1",
"typescript": "~4.3.5"
}
}
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
This diff is collapsed.
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'console-web';
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
export const environment = {
production: true
};
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ConsoleWeb</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* IE11 requires the following for NgClass support on SVG elements
*/
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
/* You can add global styles to this file, and also import other style files */
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
{ teardown: { destroyAfterEach: true }},
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2017",
"module": "es2020",
"lib": [
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
This diff is collapsed.
This diff is collapsed.
aria2c --conf-path=/usr/src/app/aria2.conf -D;
ossutil config --endpoint oss-cn-hangzhou.aliyuncs.com --access-key-id $OSS_ACCESS_ID --access-key-secret $OSS_ACCESS_KEY;
npm start
import axios from 'axios';
import config from './config';
import * as uuid from 'uuid';
import { XmlDocument } from 'xmldoc';
let apps = {};
const locales = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP'];
const languagePack = ['zh-CN', 'en-US'];
const platforms = ['win32', 'darwin'];
const ygoproPlatforms = ['linux', 'osx', 'win32'];
const ygoproLocales = ['en-US', 'ja-JP', 'zh-CN'];
let wait = ms => new Promise(resolve => setTimeout(resolve, ms));
const lang = {
'en-US': {
'en-US': 'English',
'zh-CN': 'Simplified Chinese',
'zh-TW': 'Traditional Chinese',
'language_pack': 'Language Pack'
},
'zh-CN': {
'en-US': '英文',
'zh-CN': '简体中文',
'zh-TW': '繁体中文',
'language_pack': '语言包'
}
};
async function createPackage(app) {
return await axios.post(config.new_package, {
id: uuid.v1(),
appId: app.id,
locales: locales,
platforms: platforms,
version: '0.0.1',
});
}
async function createCustomPackage(app) {
return await axios.post(config.new_package, {
id: uuid.v1(),
appId: app.id,
locales: app.locales,
platforms: app.platforms,
version: '0.0.1',
});
}
async function updatePackage(app, pack) {
let { data } = await axios.get(config.old_metalinks(app.id));
const xml = new XmlDocument(data);
const rawUrl = xml.valueWithPath('file.url');
const url = rawUrl.replace('https://r.my-card.in/dist/', 'https://r.my-card.in/release/dist/');
console.log(pack._id, url);
return await axios.post(config.upload_url, {
_id: pack._id,
url
});
}
async function updateCustomPackage(app, pack) {
let metalink = `${app.id}-${pack.platforms[0]}`.replace('osx', 'darwin');
console.log(config.old_metalinks(metalink));
let { data } = await axios.get(config.old_metalinks(metalink));
const xml = new XmlDocument(data);
const rawUrl = xml.valueWithPath('file.url');
const url = rawUrl.replace('https://r.my-card.in/dist/', 'https://r.my-card.in/release/dist/');
console.log(pack._id, url);
return await axios.post(config.upload_url, {
_id: pack._id,
url
});
}
async function handleYgopro(app) {
console.log(ygoproPlatforms, ygoproLocales);
for (let platform of ygoproPlatforms) {
for (let locale of ygoproLocales) {
try {
app.platforms = [platform];
app.locales = [locale];
console.log('正在处理yogopro', app.platforms, app.locales);
let { data } = await createCustomPackage(app);
await updateCustomPackage(app, data);
// await wait(180000);
} catch (e) {
console.log(e);
}
}
}
}
async function handleDesume(app) {
for (let platform of platforms) {
app.platforms = [platform];
app.locales = locales;
console.log('正在处理desume', app.platforms, app.locales);
let { data } = await createCustomPackage(app);
await updateCustomPackage(app, data);
}
}
async function createApp(app) {
return await axios.post(config.new_app(app.id), {
id: app.id,
name: app.name,
author: '1',
});
}
function handleName(app) {
if (!app.parent) {
console.log('parent 不存在', app.parent);
}
return Object.assign({}, ...languagePack.map(language => ({
/* tslint:disable */
[language]: `${apps[app.parent]['name'][language]} ${lang[language]['language_pack']} (${app.locales.map(locale => lang[language][locale])})`
/* tslint:enable */
})));
}
async function updateApp(app) {
const {
id, name, description, developers, publishers, released_at, category, tags, dependencies, references,
homepage, actions, version, conference, icon, cover, background, locales, author, news, ...other
} = app;
return await
axios.patch(config.new_app(app.id), {
id,
name: name || handleName(app),
description,
developers,
publishers,
released_at,
category,
tags,
dependencies,
references,
homepage,
actions,
version,
conference,
icon,
cover,
background,
locales: locales || [],
news: {},
...other,
});
}
async function main() {
let { data } = await axios.get(config.old_apps_json);
let newApps = await axios.get(config.new_apps_json);
newApps.data.map(app => {
apps[app['id']] = app;
});
for (let app of data) {
if (!['test'].includes(app['id']) && !apps[app['id']]) {
await createApp(app);
}
}
for (let i = 0, t = 0, w = true; i <= data.length; i++ , t = 180000) {
try {
let app = data[i];
// if (w) {
// await wait(t);
// w = true;
// }
// if (!['ygopro', 'desmume', 'test'].includes(app['id'])) {
// console.log(`正在处理${app['id']}`);
// await updateApp(app);
// let { data } = await createPackage(app);
// await updatePackage(app, data);
// }
// if (app['id'] == 'ygopro') {
// await updateApp(app);
// await handleYgopro(app);
// }
if (app['id'] == 'desmume') {
await updateApp(app);
await handleDesume(app);
}
} catch (e) {
console.log(e.response.data);
w = false;
continue;
}
}
}
main();
nohup: 1: No such file or directory
{
"name": "application-name",
"version": "0.0.1",
"scripts": {
"start": "node server.js",
"server": "node server.js",
"tsc": "tsc",
"prestart": "tsc",
"test": "node test.js",
"pretest": "tsc"
},
"dependencies": {
"@types/joi": "^10.3.0",
"aliyun-oss-upload-stream": "^1.3.0",
"aliyun-sdk": "^1.9.22",
"aria2": "^3.0.0",
"async-busboy": "^0.4.0",
"axios": "^0.16.1",
"bluebird": "^3.5.0",
"dotenv": "^4.0.0",
"fs-extra-promise": "^1.0.1",
"inversify": "^3.3.0",
"iridium": "^7.1.6",
"joi": "^10.4.1",
"koa": "^2.0.0",
"koa-bodyparser": "^4.2.0",
"koa-hbs": "next",
"koa-log4": "^2.1.0",
"koa-router": "^7.0.1",
"mime": "^1.3.4",
"tmp": "0.0.31",
"uuid": "^3.0.1",
"vercomp": "^1.0.2"
},
"devDependencies": {
"@types/bluebird": "^3.5.0",
"@types/busboy": "^0.2.3",
"@types/fs-extra-promise": "^0.0.32",
"@types/isomorphic-fetch": "0.0.34",
"@types/koa": "^2.0.37",
"@types/koa-bodyparser": "^3.0.22",
"@types/koa-router": "^7.0.22",
"@types/lodash": "^4.14.63",
"@types/log4js": "0.0.32",
"@types/node": "^7.0.13",
"@types/pluralize": "0.0.27",
"@types/tmp": "0.0.32",
"@types/uuid": "^2.0.29",
"@types/xmldoc": "^0.5.0",
"isomorphic-fetch": "latest",
"lodash": "latest",
"nodemon": "^1.11.0",
"tslint": "^5.1.0",
"typescript": "^2.3.4",
"xmldoc": "^1.0.0"
}
}
import * as path from 'path';
import * as fs from 'fs-extra-promise';
import {archive, archiveSingle, caculateSHA256, crawlPath, untar} from './utils';
import {Archive, File} from '../src/models/Package';
const upload_path = path.join(__dirname, '../test/upload');
const release_path = path.join(__dirname, '../test/release');
const app_path = path.join(__dirname, '../test/apps');
export async function bundle(...args) {
const [package_id] = args;
console.log(`package ${package_id}`);
await fs.ensureDirAsync(release_path);
await fs.ensureDirAsync(app_path);
await fs.ensureDirAsync(upload_path);
const archive_path = path.join(release_path, 'downloads', package_id);
const package_path = path.join(app_path, package_id);
const uploadFile_path = path.join(upload_path, package_id);
// const full_path = path.join(archive_path, 'full');
// const sand_path = path.join(archive_path, 'sand');
const dist_path = path.join(archive_path, 'dist');
await fs.ensureDirAsync(archive_path);
await fs.ensureDirAsync(package_path);
// await fs.ensureDirAsync(full_path);
// await fs.ensureDirAsync(sand_path);
await fs.ensureDirAsync(dist_path);
// untar upload package
await untar(uploadFile_path, package_path);
let files = new Map<string, File>();
let archives = new Map<string, Archive>();
// let files = {}
await crawlPath(package_path, {
onFile: async (file) => {
let file_hash = await caculateSHA256(file);
files.set(file, {
path: path.relative(package_path, file),
hash: file_hash,
size: (await fs.statAsync(file)).size
});
let sand_file = path.join(dist_path, `${file_hash}.tar.gz`);
await archiveSingle(sand_file, [file], package_path);
let sand_hash = await caculateSHA256(sand_file);
archives.set(sand_file, {
path: path.relative(dist_path, sand_file),
hash: sand_hash,
size: (await fs.statAsync(sand_file)).size
});
await fs.renameAsync(sand_file, path.join(path.dirname(sand_file), `${sand_hash}.tar.gz`));
},
onDir: async (_files, _path, depth) => {
files.set(_path, {
path: path.relative(package_path, _path) || '.',
});
},
});
let filePath = path.join(dist_path, `${package_id}.tar.gz`);
await archive(filePath, await fs.readdirAsync(package_path), package_path);
const fullHash = await caculateSHA256(filePath);
const fullSize = (await fs.statAsync(filePath)).size;
let fullPath = path.join(dist_path, `${fullHash}.tar.gz`);
await fs.renameAsync(filePath, fullPath);
await fs.removeAsync(uploadFile_path);
await fs.removeAsync(package_path);
return {
archivePath: archive_path,
distPath: dist_path,
files: Array.from(files.values()),
archives: Array.from(archives.values()),
fullSize,
fullHash
};
}
import * as fs from 'fs-extra-promise';
import * as _fs from 'fs';
import * as crypto from 'crypto';
import * as child_process from 'child_process';
interface CrawOptions {
onDir: (files: string | string[], _path: string, depth: number) => Promise<void>;
onFile: (file: string) => Promise<void>;
}
export async function crawlPath(_path, options: CrawOptions, depth = 0) {
if (await isDir(_path)) {
depth += 1;
const files = await fs.readdirAsync(_path);
await options.onDir(files, _path, depth);
if (files) {
for (let fileName of files) {
const file = `${_path}/${fileName}`;
if (await isDir(file)) {
await crawlPath(file, options, depth);
} else if (await isFile(file)) {
await options.onFile(file);
}
}
}
} else if (await isFile(_path)) {
await options.onFile(_path);
}
}
export async function isDir(path) {
return (await fs.lstatAsync(path)).isDirectory();
}
export async function isFile(path) {
return (await fs.lstatAsync(path)).isFile();
}
export function archiveSingle(archive: string, files: string[], directory: string): Promise<void> {
// const dir = fs.createWriteStream(archive)
// const pack = tar.Pack()
// .on('error', () => console.log('error'))
// .on('end',() => {})
// fstream.Reader({type: 'File', path: file})
// .pipe(pack)
// .pipe(dir)
// return tar.pack(file).pipe(_fs.createWriteStream(archive))
return new Promise<void>((resolve, reject) => {
let child = child_process.spawn('tar', ['-czf', archive, '-P', '-C', directory].concat(files), {stdio: 'inherit'});
child.on('exit', (code) => {
if (code == 0) {
resolve();
} else {
reject(code);
}
});
child.on('error', (error) => {
reject(error);
});
});
}
export function archive(archive: string, files: string[], directory: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
let child = child_process.spawn('tar', ['-czf', archive, '-C', directory].concat(files), {stdio: 'inherit'});
child.on('exit', (code) => {
if (code == 0) {
resolve();
} else {
reject(code);
}
});
child.on('error', (error) => {
reject(error);
});
});
}
export function untar(archive: string, directory: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
let child = child_process.spawn('tar', ['-xf', archive, '-C', directory], {stdio: 'inherit'});
child.on('exit', (code) => {
if (code == 0) {
resolve();
} else {
reject(code);
}
});
child.on('error', (error) => {
reject(error);
});
});
}
export function caculateSHA256(file: string): Promise<string> {
return new Promise((resolve, reject) => {
let input = _fs.createReadStream(file);
const hash = crypto.createHash('sha256');
hash.on('error', (error: Error) => {
reject(error);
});
input.on('error', (error: Error) => {
reject(error);
});
hash.on('readable', () => {
let data = hash.read();
if (data) {
resolve((<Buffer>data).toString('hex'));
}
});
input.pipe(hash);
});
}
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
import * as Koa from 'koa';
import * as log4js from 'log4js';
import * as bodyParser from 'koa-bodyparser';
import * as hbs from 'koa-hbs';
import {mongodb} from './src/models/Iridium';
// import index from './routes/index';
import upload from './src/routes/upload';
// import users from './src/routes/users';
import pack from './src/routes/package';
import apps from './src/routes/app';
// import packages from './routes/packages';
const logger = log4js.getLogger();
const app = new Koa();
app.use(hbs.middleware({
viewPath: __dirname + '/views',
}));
app.use(async (ctx, next) => {
const start = new Date();
await next();
const ms = Date.now() - start.getTime();
ctx.set('X-Response-Time', `${ms}ms`);
});
// 错误处理`
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
// will only respond with JSON
console.log(err);
ctx.status = err.status || 500;
ctx.body = {
message: err.message,
};
if (err.errCode) {
ctx.body['errCode'] = err.errCode;
}
if (ctx.response.status >= 500) {
logger.error(err);
} else if (ctx.response.status >= 400) {
logger.warn(err);
}
}
});
// 跨域
app.use(async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', '*');
ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, X-Requested-With',);
if (ctx.method === 'OPTIONS') {
ctx.status = 204;
} else {
await next();
}
});
app.use(bodyParser());
// app.use(index.routes());
// app.use(users.routes());
app.use(apps.routes());
app.use(upload.routes());
app.use(pack.routes());
// app.use(packages.routes());
mongodb.connect().then(() => {
app.listen(8001, () => {
console.log('app listening port 8001');
});
});
import {Container} from 'inversify';
const container = new Container();
export default container;
import {Collection, Index, Instance, Property} from 'iridium';
import {handleImg} from '../utils';
interface I18n<T> {
[locale: string]: T;
}
interface Platform<T> {
[platform: string]: T;
}
// interface Package {
// id: string;
// name: string;
// platforms: Platform<string[]>;
// locales: I18n<string[]>;
// files: File[];
// }
//
// interface File {
// path: string;
// size: number;
// hash: string;
// }
interface Trailer {
type: string;
url: string;
poster: string;
}
interface Achievement {
name: string;
description: string;
image: string;
progress_max: number;
}
export interface App {
id: string;
status: string;
author: string;
name?: I18n<string>;
description?: I18n<string>;
developers?: I18n<[{ name: string, url: string }]>;
publishers?: I18n<[{ name: string, url: string }]>;
released_at?: string;
category?: string;
parent?: string;
tags?: string[];
dependencies?: Platform<string[]>;
references?: Platform<string[]>;
homepage?: string;
locales?: string[];
actions?: Platform<{ [key: string]: { execuate: string, args: string[], env: { [key: string]: string } } }>;
files?: { [key: string]: { sync: boolean, ignore: boolean } };
version?: Platform<string>;
news?: I18n<{ title: string, url: string, image: string }[]>;
conference?: string;
data?: any;
icon?: string;
cover?: string;
background?: string;
created_at?: Date;
trailer?: Trailer[];
achievements?: Achievement[];
}
@Collection('apps')
@Index({id: 1}, {unique: true})
export class AppSchema extends Instance<App, AppSchema> implements App {
@Property(String, true)
id: string;
@Property(String, true)
status: string;
@Property(String, true)
author: string;
@Property(Object, false)
name?: I18n<string>;
@Property(Object, false)
description?: I18n<string>;
@Property(Object, false)
developers?: I18n<[{ name: string, url: string }]>;
@Property(Object, false)
publishers?: I18n<[{ name: string, url: string }]>;
@Property(String, false)
released_at?: string;
@Property(String, false)
category?: string;
@Property(String, false)
parent?: string;
@Property(Array, false)
tags?: string[];
@Property(Object, false)
dependencies?: Platform<string[]>;
@Property(Object, false)
references?: Platform<string[]>;
@Property(String, false)
homepage?: string;
@Property(Array, false)
locales?: string[];
@Property(Object, false)
actions?: Platform<{ [key: string]: { execuate: string, args: string[], env: { [key: string]: string } } }>;
@Property(Object, false)
files?: { [key: string]: { sync: boolean, ignore: boolean } };
@Property(Object, false)
version?: Platform<string>;
@Property(Object, false)
news?: I18n<{ title: string, url: string, image: string }[]>;
@Property(String, false)
conference?: string;
@Property(Object, false)
data?: any;
@Property(String, false)
icon?: string;
@Property(String, false)
cover?: string;
@Property(String, false)
background?: string;
@Property(Date, false)
created_at?: Date;
@Property(Array, false)
trailer?: Trailer[];
@Property(Array, false)
achievements?: Achievement[];
static onCreating(app: App) {
app.created_at = new Date();
}
handleUpdate(data: App) {
Object.assign(this, data);
}
toJSON() {
this.Convert();
// hack
return JSON.parse(this.toString());
}
Convert() {
this.icon = handleImg(this.icon),
this.cover = handleImg(this.cover),
this.background = handleImg(this.background);
}
}
import {Core, Model} from 'iridium';
import {App, AppSchema} from './App';
import {Package, PackageSchema} from './Package';
export class MongoDB extends Core {
Apps = new Model<App, AppSchema>(this, AppSchema);
Packages = new Model<Package, PackageSchema>(this, PackageSchema);
}
export const mongodb = new MongoDB(process.env['MONGODB']);
import {Collection, Index, Instance, Property} from 'iridium';
type Locale = 'zh-CN' | 'en-US' | 'ja-JP';
type Platform = 'win32' | 'linux' | 'darwin';
export interface Action {
execute: string;
args: string[];
env: {};
open?: string;
}
export interface File {
path: string;
size?: number;
hash?: string;
}
export interface Archive {
path: string;
size: number;
hash: string;
}
export interface Package {
id: string;
name: string;
appId: string;
fullSize?: number;
fullHash?: string;
version: string;
status: string;
type: string;
locales: Locale[];
platforms: Platform[];
files?: File[];
archives?: Archive[];
}
@Collection('packages')
@Index({id: 1}, {unique: true})
export class PackageSchema extends Instance<Package, PackageSchema> implements Package {
@Property(String, true)
id: string;
@Property(String, false)
name: string;
@Property(String, false)
appId: string;
@Property(Number, false)
fullSize: number;
@Property(String, false)
fullHash: string;
@Property(String, true)
type: string;
@Property(String, true)
status: string;
@Property(String, false)
version: string;
@Property(Array, false)
locales: Locale[];
@Property(Array, false)
platforms: Platform[];
@Property(Array, false)
files: File[];
@Property(Array, false)
archives: Archive[];
static onCreating(pack: Package) {
pack.status = pack.status || 'init';
}
handleUpdate(data: Package) {
Object.assign(this, data);
}
}
import Router = require('koa-router');
import {mongodb} from '../models/Iridium';
import {App} from '../models/App';
import {Context} from 'koa';
import * as joi from 'joi';
import {promisify as py} from 'bluebird';
import {dot} from '../utils';
const router = new Router();
const isTest = process.env['ENV'] !== 'production';
let validate: any = py(joi.validate);
router.get('/v2/apps', async (ctx: Context, next) => {
ctx.body = await mongodb.Apps.find({}).toArray();
});
router.get('/v1/apps', async (ctx: Context, next) => {
let payload = ctx.request.query;
if ((!payload.author && !payload.admin)) {
ctx.throw(400, 'params error');
}
let apps = {};
if (isTest || payload.admin == 'true') {
apps = await mongodb.Apps.find({}).map(app => {
if (app.files) {
app.files = Object.assign({}, ...Object.keys(app.files).map(key => ({[key.replace(new RegExp(dot, 'g'), '.')]: app.files![key]})));
}
return app;
});
} else {
apps = await mongodb.Apps.find({author: payload.author}).map(app => {
if (app.files) {
app.files = Object.assign({}, ...Object.keys(app.files).map(key => ({[key.replace(new RegExp(dot, 'g'), '.')]: app.files![key]})));
}
return app;
});
}
ctx.body = apps;
});
router.post('/v1/app/:id', async (ctx: Context, next) => {
let payload: App = {
id: ctx.request.body.id,
name: ctx.request.body.name,
author: ctx.request.body.author,
status: 'editing',
};
if (!payload.id) {
ctx.throw(400, 'params error');
}
if (ctx.params.id !== payload.id) {
ctx.throw(400, 'App is not same');
}
let exists = await mongodb.Apps.findOne({id: payload.id});
if (exists) {
ctx.throw(400, 'App is exists');
}
try {
ctx.body = await mongodb.Apps.create(payload);
} catch (e) {
ctx.throw(400, e);
}
});
router.patch('/v1/app/:id', async (ctx: Context, next) => {
let _app = ctx.request.body;
let app = await mongodb.Apps.findOne({id: ctx.params.id});
if (!app) {
return ctx.throw(400, `App ${ctx.params.id} Not Found `);
}
if (!_app.id || _app.id !== app.id) {
ctx.throw(400, `Can not change AppID`);
}
if (_app.status == 'ready') {
try {
await validate(_app, joi.object().keys({
action: joi.object().required(),
}).required());
} catch (e) {
e.message = '资料尚未填写完毕或格式有误';
return ctx.throw(e);
}
}
if (_app.files && Object.keys(_app.files).length > 0) {
_app.files = Object.assign({}, ...Object.keys(_app.files).map(key => ({[key.replace(new RegExp('\\.', 'g'), dot)]: _app.files[key]})));
}
app.handleUpdate(_app);
ctx.body = await app.save();
});
export default router;
import Router = require('koa-router');
import {toObjectID} from 'iridium';
import {mongodb} from '../models/Iridium';
import {Context} from 'koa';
import {Archive, Package} from '../models/Package';
import {renderChecksum} from '../utils';
const router = new Router();
router.get('/v2/packages', async (ctx: Context, next) => {
if (!ctx.request.query.appId) {
ctx.throw(400, 'appId must be required!');
}
let packs = await mongodb.Packages.find({
appId: ctx.request.query.appId,
status: 'uploaded'
}).toArray();
ctx.body = packs;
});
router.get('/v2/package-legacy/:id/checksum', async (ctx: Context, next) => {
let pack = await mongodb.Packages.findOne({appId: ctx.params.id, status: 'uploaded'});
if (!pack) {
return ctx.throw(400, 'pack error');
}
ctx.body = renderChecksum(pack.files);
});
router.get('/v2/package/:id/checksum', async (ctx: Context, next) => {
let pack = await mongodb.Packages.findOne({id: ctx.params.id, status: 'uploaded'});
if (!pack) {
return ctx.throw(400, 'pack error');
}
ctx.body = renderChecksum(pack.files);
});
router.get('/v2/package/:id/meta', async (ctx: Context, next) => {
let pack = await mongodb.Packages.findOne({id: ctx.params.id, status: 'uploaded'});
if (!pack) {
return ctx.throw(400, 'pack error');
}
await ctx['render']('update', {
files: {
name: pack.id,
size: pack.fullSize,
hash: pack.fullHash
}
});
});
router.get('/v2/package-legacy/:id/meta', async (ctx: Context, next) => {
let pack = await mongodb.Packages.findOne({appId: ctx.params.id, status: 'uploaded'});
if (!pack) {
return ctx.throw(400, 'pack error');
}
await ctx['render']('update', {
files: {
name: pack.id,
size: pack.fullSize,
hash: pack.fullHash
}
});
});
router.post('/v2/package/:id/update', async (ctx: Context, next) => {
const package_id = ctx.params.id;
const request_overhead = 1024 * 1024;
let sandSize = ctx.request.body.length * request_overhead;
let pack = await mongodb.Packages.findOne({id: package_id, status: 'uploaded'});
if (!pack) {
return ctx.throw(400, 'pack not exists');
}
let {fullSize} = pack;
let files;
let fullFiles = new Map<string, Archive>();
pack.archives.map((f) => {
fullFiles.set(f.path, f);
});
if (fullSize > sandSize) {
files = ctx.request.body.map((_file) => {
const file: Archive | undefined = fullFiles.get(_file);
if (!file) {
return ctx.throw(400, '');
}
sandSize += file.size;
return {
path: file.path,
size: file.size,
hash: file.hash
};
});
}
if (sandSize <= fullSize) {
files = [{
path: pack.id,
size: pack.fullSize,
hash: pack.fullHash
}];
}
await ctx['render']('update', {files});
});
router.get('/v1/packages', async (ctx: Context, next) => {
if (!ctx.request.query.appId) {
ctx.throw(400, 'appId must be required!');
}
let packs = await mongodb.Packages.find({
appId: ctx.request.query.appId,
type: 'editing'
}).toArray();
ctx.body = {
[ctx.request.query.appId]: packs
};
});
router.post('/v1/package', async (ctx: Context, next) => {
const _p: Package = ctx.request.body;
if (!_p.id) {
ctx.throw(400, `id 参数缺失:${_p.id}`);
}
if (!_p.platforms || _p.platforms.length == 0) {
ctx.throw(400, `请填写支持的平台:${_p.id}`);
} else if (!_p.locales || _p.locales.length == 0) {
ctx.throw(400, `请填写支持的语言:${_p.id}`);
} else if (!_p.version) {
ctx.throw(400, `请填写版本号:${_p.id}`);
}
let existsPlatform = await mongodb.Packages.findOne({
id: {$ne: _p.id},
appId: _p.appId,
$and: [{
platforms: {$in: _p.platforms},
locales: {$in: _p.locales}
}],
type: 'editing'
});
if (existsPlatform) {
console.log(existsPlatform);
ctx.throw(400, '平台语言已存在');
}
await mongodb.Packages.update({id: _p.id}, {$set: {type: 'edited'}}, {multi: true});
let _pack: Package = {
id: _p.id,
name: _p.name,
version: _p.version,
appId: _p.appId,
locales: _p.locales,
platforms: _p.platforms,
status: 'init',
type: 'editing'
};
ctx.body = await mongodb.Packages.insert(_pack);
});
router.patch('/v1/package', async (ctx: Context, next) => {
const _p = ctx.request.body;
const p = await mongodb.Packages.findOne({_id: toObjectID(_p._id)});
if (!p) {
return ctx.throw(400, 'pack not exists');
}
if (!_p.id) {
ctx.throw(400, `id 参数缺失:${_p.id}`);
}
if (!_p.platforms || _p.platforms.length == 0) {
ctx.throw(400, `请填写支持的平台:${_p.id}`);
} else if (!_p.locales || _p.locales.length == 0) {
ctx.throw(400, `请填写支持的语言:${_p.id}`);
} else if (!_p.version) {
ctx.throw(400, `请填写版本号:${_p.id}`);
}
let existsPlatform = await mongodb.Packages.find({
id: {$ne: _p.id},
appId: _p.appId,
platforms: {$in: _p.platforms},
type: 'editing'
}).count();
if (existsPlatform) {
console.log(existsPlatform);
ctx.throw(400, '平台已存在');
}
let existsLocales = await mongodb.Packages.find({
id: {$ne: _p.id},
appId: _p.appId,
locales: {$in: _p.locales},
type: 'editing'
}).count();
if (existsLocales) {
console.log(existsLocales);
ctx.throw(400, '语言已存在');
}
if (p.status == 'init') {
p.handleUpdate(_p);
ctx.body = await p.save();
} else {
ctx.throw(400, `非法操作:${_p.id}`);
}
});
router.delete('/v1/package', async (ctx: Context, next) => {
const _p = ctx.request.body;
const p = await mongodb.Packages.findOne({_id: toObjectID(_p._id)});
if (!p) {
return ctx.throw(400, 'pack not exists');
}
p.type = 'edited';
p.status = 'delete';
await p.save();
ctx.body = {
message: 'delete successful'
};
});
export default router;
import { Context } from 'koa';
import { OSS } from 'aliyun-sdk';
import * as busboy from 'async-busboy';
import * as mime from 'mime';
import * as uuid from 'uuid';
import * as Client from 'aliyun-oss-upload-stream';
import * as fs from 'fs-extra-promise';
import * as path from 'path';
import * as Aria2 from 'aria2';
import { bundle } from '../../package/main';
import { mongodb } from '../models/Iridium';
import { toObjectID } from 'iridium';
import config from '../../config';
import { UploadOSS } from '../utils';
import Router = require('koa-router');
import { Queue } from '../utils';
const queue = new Queue({
concurrency: 1
});
const checkFilePath = async (file) => {
if (['.gz', '.rar', '.zip', '.7z'].indexOf(path.extname(file.path)) === -1) {
console.log(file);
throw new Error(`Unsupported file type: ${path.extname(file.path)}`);
}
};
const checkPackage = async (file) => {
if (['application/zip', 'application/gz', 'application/rar', 'application/7z', 'application/x-gzip'].indexOf(file.mime) === -1) {
console.log(file.mime);
throw new Error(`Unsupported file type: ${file.mime}`);
}
};
const checkImage = async (file) => {
const ext = mime.extension(file.mime);
if (['png', 'jpg', 'jpeg', 'gif', 'webp'].indexOf(ext) === -1) {
throw new Error('Unsupported file type');
}
};
const ossStream = Client(new OSS({
accessKeyId: process.env['OSS_ACCESS_ID'],
secretAccessKey: process.env['OSS_ACCESS_KEY'],
endpoint: process.env['OSS_ENDPOINT'],
apiVersion: '2013-10-15'
}));
const router = new Router();
const UploadImage = async (ctx: Context) => {
try {
const { files } = await busboy(ctx.req);
ctx.body = await Promise.all(files.map(async file => {
await checkImage(file);
const filename = `test/${uuid.v1()}`;
const upload = ossStream.upload({
Bucket: process.env['OSS_BUCKET'],
Key: filename,
ContentType: file.mimeType
});
file.pipe(upload);
return await new Promise((resolve, reject) => {
upload.on('error', reject);
upload.on('uploaded', resolve);
});
}));
} catch (err) {
ctx.throw(403, err);
}
};
export const UploadPackage = async (ctx: Context) => {
try {
const { files } = await busboy(ctx.req);
ctx.body = await Promise.all(files.map(async file => {
await checkPackage(file);
const filename = uuid.v1();
const archive_path = path.join(__dirname, '../../test/upload');
await fs.ensureDirAsync(archive_path);
const archive = fs.createWriteStream(path.join(archive_path, filename));
let pack = await mongodb.Packages.findOne({ _id: toObjectID(ctx.params.id) });
if (!pack) {
return ctx.throw(400, 'pack not exists');
}
return await new Promise((resolve, reject) => {
file.pipe(archive);
file.on('close', async () => {
await queue.run(async (ctx, next) => {
try {
pack!.status = 'uploading';
await pack!.save();
resolve(pack!);
// 上传完, 打包
let bundled;
bundled = await bundle(filename);
// 打包完,上传阿里云
await UploadOSS(bundled.distPath);
Object.assign(pack, bundled);
pack!.status = 'uploaded';
await mongodb.Packages.update({ id: pack!.id }, { $set: { status: 'deprecated' } }, { multi: true });
await pack!.save();
// 上传完,干掉本地目录
await fs.removeAsync(bundled.archivePath);
} catch (e) {
pack!.status = 'failed';
await pack!.save();
console.log(e);
}
next();
});
});
file.on('error', async (error) => {
pack!.status = 'failed';
await pack!.save();
reject(error);
});
});
}));
} catch (err) {
ctx.throw(403, err);
}
};
const uploadPackageUrl = async (ctx: Context) => {
if (!ctx.request.body.url) {
ctx.throw(400, 'params error');
}
// testUrl: https://r.my-card.in/release/dist/0c16a3ecb115fd7cf575ccdd64f62a8f3edc635b087950e4ed4f3f781972bbfd.tar.gz
const downloader = new Aria2;
await downloader.open();
let pack = await mongodb.Packages.findOne({ _id: toObjectID(ctx.request.body._id) });
if (!pack) {
return ctx.throw(400, 'pack not exists');
}
downloader.onDownloadStart = async ({ gid }) => {
const { files } = await downloader.send('tellStatus', gid);
const [file] = files;
const [url] = file.uris;
if (ctx.request.body.url == url.uri) {
pack!.status = 'uploading';
await pack!.save();
}
};
downloader.onDownloadComplete = async ({ gid }) => {
const { files } = await downloader.send('tellStatus', gid);
const [file] = files;
const [url] = file.uris;
if (ctx.request.body.url == url.uri) {
await queue.run(async (ctx, next) => {
try {
await checkFilePath(file);
// 打包
const bundled = await bundle(path.basename(file.path));
await UploadOSS(bundled.distPath);
Object.assign(pack, bundled);
pack!.status = 'uploaded';
await mongodb.Packages.update({ id: pack!.id }, { $set: { status: 'deprecated' } }, { multi: true });
await pack!.save();
// 上传完,干掉本地目录
await fs.removeAsync(bundled.archivePath);
} catch (e) {
console.trace(e);
pack!.status = 'failed';
await pack!.save();
}
next();
});
// 打包完, 上传阿里云
await downloader.close();
}
};
downloader.onDownloadError = async (err) => {
const { files } = await downloader.send('tellStatus', err.gid);
const [file] = files;
const [url] = file.uris;
if (ctx.request.body.url == url.uri) {
pack!.status = 'failed';
await pack!.save();
await downloader.close();
console.log(err);
}
};
ctx.body = await new Promise((resolve, reject) => {
downloader.onmessage = m => {
if (m['error']) {
reject(m['error']);
} else {
resolve(m);
}
};
downloader.send('addUri', [ctx.request.body.url], { dir: config.upload_path });
});
};
router.post('/v1/upload/image', UploadImage);
router.post('/v1/upload/package/:id', UploadPackage);
router.post('/v1/upload/packageUrl', uploadPackageUrl);
export default router;
const TYPES = {};
export default TYPES;
import { URL } from 'url';
import * as child_process from 'child_process';
export const dot = '__<DOT>__';
export const handleImg = (img) => {
if (img) {
let url: URL;
if (img.substring(0, 16) == '/uploads/default') {
url = new URL(img, 'https://ygobbs.com');
} else {
url = new URL(img, 'https://cdn01.moecube.com');
}
return url.toString();
} else {
return 'https://cdn01.moecube.com/accounts/default_avatar.jpg';
}
};
export function renderChecksum(files: { path: string, hash?: string }[]) {
return files.map(({ path, hash }) => `${hash || ''} ${path}`).join('\n');
}
export function UploadOSS(dist: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
let child = child_process.spawn('ossutil', ['cp', '--recursive', dist, 'oss://mycard/test-release'], { stdio: 'inherit' });
child.on('exit', (code) => {
if (code == 0) {
resolve();
} else {
reject(code);
}
});
child.on('error', (error) => {
reject(error);
});
});
}
type QueueParams = {
concurrency: number;
};
export class Queue {
concurrency: number;
running: number;
queue: Array<Function>;
constructor(params: QueueParams) {
Object.assign(this, params);
this.running = 0;
this.queue = [];
}
set(args: QueueParams): Queue {
Object.assign(this, args);
return this;
}
push(task: Function): Queue {
this.queue.push(task);
return this;
}
async run(task: Function) {
this.queue.push(task);
await this.next();
}
async next() {
while (this.running < this.concurrency && this.queue.length) {
let task: Function | undefined = this.queue.shift();
if (!task) {
return;
}
this.running++;
await task(this, () => {
this.running--;
this.next();
});
}
}
}
/**
* Created by zh99998 on 2017/4/27.
*/
import * as fetch from 'isomorphic-fetch';
import * as _ from 'lodash';
import { XmlDocument } from 'xmldoc';
import config from './config';
async function test_checksums() {
const apps: any[] = (await (await fetch(config.new_apps_json)).json())
// .filter(i => !['ygopro', 'desmume'].includes(i.id)); // 排除 ygopro 和 desmume
let oldMaps = new Set();
for (let app of apps) {
console.log(`正在测试 ${app.id} 的 checksum`);
const old_checksum = await (await fetch(config.old_checksums(app.id))).text();
const new_checksum = await (await fetch(config.new_checksums(app.id))).text();
new_checksum.split('\n').forEach(line => {
oldMaps.add(line);
});
old_checksum.split('\n').forEach(line => {
if (!oldMaps.has(line)) {
console.log('', old_checksum);
console.log('', new_checksum);
throw `应用 ${app.id} 的 checksum 不一致`;
}
});
}
}
async function test_download() {
const apps: any[] = (await (await fetch(config.new_apps_json)).json())
.filter(i => !['ygopro', 'desmume'].includes(i.id)); // 排除 ygopro 和 desmume
const app: any = _.sample(apps);
console.log(`正在测试 ${app.id} 的 下载`);
const metalink = await (await fetch(config.new_metalinks(app.id))).text();
const xml = new XmlDocument(metalink);
const url = xml.valueWithPath('file.url');
const response = await fetch(url, { method: 'HEAD' });
if (!response.ok) {
throw `${app.id} 的 下载地址 ${url} 返回 ${response.statusText}`;
}
}
async function test_update() {
// TODO
}
async function test_apps_json() {
const old_apps = await (await fetch(config.old_apps_json)).json();
const new_apps = await (await fetch(config.new_apps_json)).json();
for (let new_app of new_apps) {
let old_app = old_apps.find(i => i.id == new_app.id);
delete old_app.author;
delete new_app.author;
delete old_app.news;
delete new_app.news;
if (!old_app) {
throw `应用 ${new_app.id} 在旧的列表不存在`;
}
for (let [key, value] of Object.entries(old_app)) {
if (!new_app[key]) {
throw `应用 ${new_app.id}${key} 字段在的新的列表中不存在`;
}
if (!_.isEqual(new_app[key], value)) {
console.log('', JSON.stringify(value, null, 2));
console.log('', JSON.stringify(new_app[key], null, 2));
throw `应用 ${new_app.id}${key} 字段跟旧的不同`;
}
}
}
}
async function main() {
// await test_apps_json();
try {
await test_checksums();
await test_download();
await test_update();
} catch (e) {
console.log(e);
}
console.log('ok');
}
main();
process.on('unhandledRejection', (reason, p) => {
console.error('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strictNullChecks": true,
"sourceMap": true,
"moduleResolution": "node",
"lib": [
"es2017",
"dom"
],
"noImplicitAny": false,
"suppressImplicitAnyIndexErrors": true,
"skipLibCheck": true
}
}
\ No newline at end of file
{
"rules": {
"max-line-length": [true, 140],
"no-inferrable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"indent": [
true,
"spaces"
],
"eofline": true,
"no-duplicate-variable": true,
"no-eval": true,
"no-arg": true,
"no-internal-module": true,
"no-trailing-whitespace": true,
"no-bitwise": true,
"no-unused-expression": true,
"no-var-keyword": true,
"one-line": [
true,
"check-catch",
"check-else",
"check-open-brace",
"check-whitespace"
],
"quotemark": [
true,
"single",
"avoid-escape"
],
"semicolon": [true, "always"],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"curly": true,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}
<?xml version="1.0" encoding="UTF-8"?>
<metalink xmlns="urn:ietf:params:xml:ns:metalink">
{{#files}}
<file name="{{name}}">
<size>{{size}}</size>
<hash type="sha-256">{{hash}}</hash>
<url priority="1">https://cdn01.moecube.com/test-release/{{hash}}.tar.gz</url>
<url priority="1">https://cdn01.moecube.com/test-release/{{hash}}.tar.gz</url>
</file>
{{/files}}
</metalink>
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment