Skip to content

Commit 695163e

Browse files
author
github-actions
committed
all exchanges
[TRANSFER][BUILD]
1 parent 63a0f0b commit 695163e

File tree

10 files changed

+365
-96
lines changed

10 files changed

+365
-96
lines changed

.github/scripts/generate-exchange.sh

Lines changed: 0 additions & 22 deletions
This file was deleted.

.github/scripts/push-to-repo.sh

Lines changed: 0 additions & 20 deletions
This file was deleted.

.github/scripts/pushback.sh

Lines changed: 0 additions & 7 deletions
This file was deleted.

.github/workflows/build-single-exchange.yml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,20 @@ jobs:
2323
npm install
2424
npm install typescript
2525
26-
- name: Build
26+
- name: Build single exchange
2727
run: |
2828
cd build
2929
# get repository name and it's first word before hyphen and pass that as argument
30-
REPO_NAME=$(echo ${{ github.repository }} | cut -d'/' -f2 | cut -d'-' -f1)
31-
npm run build -- $REPO_NAME
30+
EXCHANGE_NAME=$(echo ${{ github.repository }} | cut -d'/' -f2 | cut -d'-' -f1)
31+
npm run build-single-exchange -- $EXCHANGE_NAME
3232
3333
- name: Commit and push back changes
3434
run: |
35-
chmod +x .github/scripts/pushback.sh
36-
.github/scripts/pushback.sh
35+
git config --local user.email "[email protected]"
36+
git config --local user.name "GitHub Action"
37+
git add README.md
38+
git commit -m "pushback" || echo "No changes to commit"
39+
git push
3740
3841
- name: Set up Python
3942
if: contains(github.event.head_commit.message, '[PUBLISH]')
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: build
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
transfer-step:
10+
strategy:
11+
matrix:
12+
exchange_name: ['binance', 'bingx', 'bitget', 'bitmart', 'bitmex', 'bybit', 'coinex', 'cryptocom', 'gate', 'hashkey', 'htx', 'hyperliquid', 'kucoin', 'kucoinfutures', 'mexc', 'okx', 'woo', 'woofipro']
13+
runs-on: ubuntu-latest
14+
if: contains(github.event.head_commit.message, '[TRANSFER]')
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
with:
19+
ref: ${{ github.ref }}
20+
persist-credentials: false
21+
22+
- name: Push to another repo
23+
run: |
24+
cd .github/scripts/
25+
chmod +x ./send-exchange-skeleton-to-repo.sh
26+
./send-exchange-skeleton-to-repo.sh "${{ matrix.exchange_name }}" "${{ github.event.head_commit.message }}"
27+
env:
28+
GITHUB_API_TOKEN: ${{ secrets.API_TOKEN_FOR_CCXT_SINGLE_EXCHANGES }}

build/build.ts

Lines changed: 129 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ import path from 'path'
44
import { argvs, sanitizePackageName, exchangeArgv, execSync, cp, capitalize, regexAll } from './utils';
55

66
import { fileURLToPath } from 'url';
7+
// @ts-expect-error
78
const __dirname = path.dirname(fileURLToPath(import.meta.url));
89

910

10-
11-
1211
class build {
1312

1413
language:string = 'python';
@@ -18,12 +17,11 @@ class build {
1817
destinationFolder:string;
1918
downloadAndDelete:boolean;
2019

21-
constructor(exchange: string, downloadAndDelete: boolean) {
20+
constructor(exchange: string) {
2221
this.exchange = exchange;
2322
this.sourceFolder = __dirname + `/ccxt/`;
2423
this.destinationFolder = __dirname + `/../${exchange}/ccxt/`;
25-
this.downloadAndDelete = downloadAndDelete;
26-
this.init(exchange);
24+
this.downloadAndDelete = !argvs.includes('--nodownload');
2725
}
2826

2927
async downloadCcxtRepo() {
@@ -139,54 +137,60 @@ class build {
139137
fs.writeFileSync (__dirname + '/../meta.json', stringified);
140138
}
141139

142-
replaceGlobalRegexes (text: string, array: any[]) {
140+
replaceGlobalRegexes (text: string, array: any[] = []) {
143141
let newText = text;
144142
newText = regexAll (newText, [
145143
['__exchangeName__', this.exchange],
146144
['__ExchangeName__', capitalize(this.exchange)],
147145
]);
148-
const otherStrings = {
146+
const defaults: any = {
149147
'__LINK_TO_OFFICIAL_EXCHANGE_DOCS__': 'https://ccxt.com',
150-
'__PYTHON_PACKAGE_NAME__': undefined,
151148
'__EXAMPLE_SYMBOL__': 'BTC/USDC',
152149
};
153150
const exchangeConfig = this.globalConfigs['exchanges'][this.exchange];
154-
for (const key in otherStrings) {
155-
const defaultValue = otherStrings[key];
156-
let value = exchangeConfig[key] || defaultValue; // at first, read from config, if not, use default
151+
for (const key in defaults) {
152+
const defaultValue = defaults[key];
153+
let value = exchangeConfig[key] || defaultValue; // use default if value not set
157154
newText = newText.replace(new RegExp(`${key}`, 'g'), value);
158155
}
159-
const sanitized = sanitizePackageName (exchangeConfig['__PYTHON_PACKAGE_NAME__']);
160-
newText = newText.replace(new RegExp(`__PYTHON_PACKAGE_KEY__`, 'g'), sanitized);
156+
// newText = newText.replace(/__PYTHON_PACKAGE_KEY__/g, sanitizePackageName (exchangeConfig['__PYTHON_PACKAGE_NAME__']));
161157
return newText;
162158
}
163159

160+
commonContentReplace (filePath: string) {
161+
let fileContent = fs.readFileSync(filePath, 'utf8');
162+
fileContent = this.replaceGlobalRegexes(fileContent);
163+
fs.writeFileSync(filePath, fileContent);
164+
}
165+
164166
generateExamples () {
165167
const destinationDir = __dirname + `/../examples/`;
166168
cp (__dirname + '/templates/examples/', destinationDir);
167169
// iterate through files and make replacements
168170
const files = fs.readdirSync(destinationDir);
169171
for (const file of files) {
170-
const filePath = destinationDir + file;
171-
let fileContent = fs.readFileSync(filePath, 'utf8');
172-
fileContent = this.replaceGlobalRegexes(fileContent, []);
173-
fs.writeFileSync(filePath, fileContent);
172+
this.commonContentReplace (destinationDir + file);
174173
}
175174
}
176175

177176
generateReadme () {
178-
const destinationDir = __dirname + `/../README.md`;
179-
cp (__dirname + '/templates/README.md', destinationDir);
180-
let fileContent = fs.readFileSync(destinationDir, 'utf8');
181-
fileContent = this.replaceGlobalRegexes(fileContent, []);
182-
fs.writeFileSync(destinationDir, fileContent);
177+
const target = __dirname + `/../README.md`;
178+
cp (__dirname + '/templates/README.md', target);
179+
this.commonContentReplace (target);
180+
this.updateReadmeWithMethods()
183181
}
184182

185-
async init (exchange:string) {
183+
generatePyprojectToml () {
184+
const target = __dirname + `/../pyproject.toml`;
185+
cp (__dirname + '/templates/pyproject.toml', target);
186+
this.commonContentReplace (target);
187+
}
188+
189+
async init () {
186190
if (this.downloadAndDelete) {
187191
await this.downloadCcxtRepo ();
188192
}
189-
this.copyCcxtFiles (exchange);
193+
this.copyCcxtFiles (this.exchange);
190194
await this.setAllExchangesList ();
191195
await this.creataPackageInitFile ();
192196

@@ -204,12 +208,110 @@ class build {
204208
}
205209
console.log ("Done!");
206210
}
211+
212+
213+
214+
sortMethods(methods: string[]) {
215+
return methods.sort((a, b) => {
216+
const aPriority = a.startsWith('fetch') || a.startsWith('create') ? 0 : 1;
217+
const bPriority = b.startsWith('fetch') || b.startsWith('create') ? 0 : 1;
218+
return aPriority - bPriority || a.localeCompare(b);
219+
});
220+
}
221+
222+
223+
updateReadme(methods: string[], rawMethods: string[], wsMethods: string[], readmePath: string) {
224+
let readmeContent = fs.readFileSync(readmePath, 'utf8');
225+
226+
const methodsFormatted = methods.map(method => `- \`${method}\``).join('\n');
227+
const rawMethodsFormatted = rawMethods.map(method => `- \`${method}\``).join('\n');
228+
const wsMethodsFormatted = wsMethods.map(method => `- \`${method}\``).join('\n');
229+
230+
const newMethodsSection = `### REST Unified\n\n${methodsFormatted}\n`;
231+
232+
const newWsMethodsSection = `### WS Unified\n\n${wsMethodsFormatted}\n`;
233+
234+
const newRawMethodsSection = `### REST Raw\n\n${rawMethodsFormatted}\n`;
235+
236+
// Replace the existing #Methods section
237+
const regex = /### REST Unified\n[\s\S]*?(?=\n#|$)/;
238+
if (regex.test(readmeContent)) {
239+
readmeContent = readmeContent.replace(regex, newMethodsSection);
240+
} else {
241+
readmeContent += `\n${newMethodsSection}`;
242+
}
243+
244+
// handleRestRaw
245+
const rawMethodRegex = /### REST Raw\n[\s\S]*?(?=\n#|$)/
246+
if (rawMethodRegex.test(readmeContent)) {
247+
readmeContent = readmeContent.replace(rawMethodRegex, newRawMethodsSection);
248+
} else {
249+
readmeContent += `\n${newRawMethodsSection}`;
250+
}
251+
252+
// handleWs
253+
const wsRegex = /### WS Unified\n[\s\S]*?(?=\n#|$)/;
254+
if (wsRegex.test(readmeContent)) {
255+
readmeContent = readmeContent.replace(wsRegex, newWsMethodsSection);
256+
} else {
257+
readmeContent += `\n${newWsMethodsSection}`;
258+
}
259+
260+
fs.writeFileSync(readmePath, readmeContent, 'utf8');
261+
}
262+
263+
async updateReadmeWithMethods() {
264+
const filePath = this.destinationFolder + '/' + this.exchange + '.py';
265+
const wsFilePath = this.destinationFolder + '/pro/' + this.exchange + '.py';
266+
const abstractFile = this.destinationFolder + '/abstract/' + this.exchange + '.py';
267+
const readme = __dirname + '/../README.md';
268+
269+
270+
const content = fs.readFileSync(filePath, 'utf8');
271+
const wsContent = fs.readFileSync(wsFilePath, 'utf8');
272+
const abstractContent = fs.readFileSync(abstractFile, 'utf8');
273+
const methodRegex = /def\s+([a-zA-Z_][a-zA-Z0-9_]*)\(([^)]*)\)/g;
274+
const abstractRegex = /\s+(\w+)\s=\s\w+\s=\s/g
275+
let restMethods: string[] = [];
276+
let wsMethods: string[] = [];
277+
let rawMethods: string[] = [];
278+
let match;
279+
280+
while ((match = methodRegex.exec(content)) !== null) {
281+
const name = match[1];
282+
if (name.startsWith('parse') || name.startsWith('sign') || name.startsWith('handle') || name.startsWith('load')) {
283+
continue;
284+
}
285+
restMethods.push(`${name}(${match[2]})`);
286+
}
287+
288+
while ((match = methodRegex.exec(wsContent)) !== null) {
289+
const name = match[1];
290+
if (name.startsWith('handle') || name.startsWith('parse') || name.startsWith('request') || name.startsWith('ping')) {
291+
continue;
292+
}
293+
wsMethods.push(`${name}(${match[2]})`);
294+
}
295+
296+
while ((match = abstractRegex.exec(abstractContent)) !== null) {
297+
const name = match[1];
298+
rawMethods.push(`${name}(request)`);
299+
}
300+
301+
302+
// console.log(this.sortMethods(re))
303+
this.updateReadme(this.sortMethods(restMethods), rawMethods, wsMethods, readme);
304+
return restMethods;
305+
}
207306
}
208307

209308

210309
// -------------------------------------------------------------------------------- //
211310

212311

213-
214-
const donwloadAndDelete = !argvs.includes('--nodownload');
215-
new build(exchangeArgv, donwloadAndDelete);
312+
const builder = new build(exchangeArgv);
313+
if (argvs[1] === '--methods') {
314+
builder.updateReadmeWithMethods()
315+
} else {
316+
builder.init()
317+
}

build/package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
"description": "",
55
"main": "index.js",
66
"scripts": {
7-
"test": "echo \"Error: no test specified\" && exit 1",
8-
"build": "tsx build.ts",
9-
"pypi-packager": "tsx pypi-packager.ts"
7+
"build-single-exchange": "tsx build.ts",
8+
"pypi-packager": "tsx pypi-packager.ts",
9+
10+
"sample-generate": "cd ../.github/scripts/ && bash generate-exchange-skeleton.sh ../../../tmp_folder kucoin",
11+
"sample-build": "cd ../../tmp_folder/build && npm run build-single-exchange",
12+
"sample-pypi": "cd ../../tmp_folder/build && npm run pypi-packager",
13+
"sample-all": "npm run sample-generate && cd ../../tmp_folder/build && npm run build-single-exchange && npm run pypi-packager"
1014
},
1115
"author": "",
1216
"license": "ISC",

build/pypi-packager.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,24 @@ import * as semver from 'semver';
55
import { argvs, sanitizePackageName, mkdir, jsonFromFile, exchangeArgv, execSync, cp, capitalize, regexAll } from './utils';
66

77
import { fileURLToPath } from 'url';
8+
// @ts-expect-error
89
const __dirname = path.dirname(fileURLToPath(import.meta.url));
910

1011

1112
class pypi {
1213

1314
exchange:string;
1415
exchangeConfigs:any;
15-
pypiApiSecret:any;
1616
rootDir:string = __dirname + `/../`;
1717
tempPyDir:string = this.rootDir + `/temp_pypi/`;
1818

19-
constructor(exchange: string, pypiApiSecret: string) {
19+
constructor(exchange: string) {
2020
this.exchange = exchange;
2121
this.exchangeConfigs = jsonFromFile(__dirname + `/global-configs.json`)['exchanges'];
22-
this.pypiApiSecret = pypiApiSecret;
2322
this.init(exchange);
2423
}
2524

26-
init(exchange) {
25+
init(exchange: string) {
2726
// create skeleton dirs
2827
mkdir (this.tempPyDir);
2928
mkdir (this.tempPyDir + '/tests/'); // just empty folder
@@ -100,10 +99,7 @@ class pypi {
10099

101100
}
102101

103-
// check if environment variabele exist
104-
const pypiApiSecret = process.env.PYPI_API_SECRET_SP;
105-
if (!pypiApiSecret) {
106-
console.error('Please set environment variable PYPI_API_SECRET_SP');
107-
process.exit(1);
108-
}
109-
new pypi(exchangeArgv, pypiApiSecret);
102+
103+
// if (! process.env.PYPI_API_SECRET_SP)
104+
105+
new pypi(exchangeArgv);

0 commit comments

Comments
 (0)