fix: add new http parser

This commit is contained in:
tsukino
2025-02-19 21:50:08 +08:00
parent 14eee4a2e3
commit fbe692c840
10 changed files with 194 additions and 471 deletions

15
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "0.1.0-alpha.7.1",
"license": "ISC",
"dependencies": {
"http-parser-js": "^0.5.9",
"tlsn-wasm": "^0.1.0-alpha.7.2"
},
"devDependencies": {
@@ -6801,10 +6802,9 @@
}
},
"node_modules/http-parser-js": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
"integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
"dev": true
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz",
"integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw=="
},
"node_modules/http-proxy": {
"version": "1.18.1",
@@ -19773,10 +19773,9 @@
}
},
"http-parser-js": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
"integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
"dev": true
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz",
"integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw=="
},
"http-proxy": {
"version": "1.18.1",

View File

@@ -23,9 +23,10 @@
"lint:eslint": "eslint . --fix",
"lint:tsc": "tsc --noEmit",
"lint": "concurrently npm:lint:tsc npm:lint:eslint",
"run:test": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha -r ts-node/register 'test/testRunner.ts'",
"test": "npm run build:tlsn-binaries && npm run build:test && npm run run:test",
"test:only": "npm run build:test && npm run run:test"
"run:spec": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha -r ts-node/register 'test/specs/*.ts'",
"run:e2e": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha -r ts-node/register 'test/testRunner.ts'",
"test": "npm run build:tlsn-binaries && npm run build:test && npm run run:e2e",
"test:only": "npm run build:test && npm run run:e2e"
},
"devDependencies": {
"@types/mocha": "^10.0.6",
@@ -69,6 +70,7 @@
"node": ">= 16.20.2"
},
"dependencies": {
"http-parser-js": "^0.5.9",
"tlsn-wasm": "^0.1.0-alpha.7.2"
}
}
}

View File

@@ -19,14 +19,10 @@ import initWasm, {
ConnectionInfo,
PartialTranscript,
} from 'tlsn-wasm';
import {
arrayToHex,
processTranscript,
expect,
headerToMap,
hexToArray,
} from './utils';
import { arrayToHex, expect, headerToMap, hexToArray } from './utils';
import { ParsedTranscriptData, PresentationJSON } from './types';
import { Buffer } from 'buffer';
import { Transcript } from './transcript';
let LOGGING_LEVEL: LoggingLevel = 'Info';
@@ -166,22 +162,9 @@ export class Prover {
return this.#prover.setup(verifierUrl);
}
async transcript(): Promise<{
sent: string;
recv: string;
ranges: { recv: ParsedTranscriptData; sent: ParsedTranscriptData };
}> {
async transcript(): Promise<Transcript> {
const transcript = this.#prover.transcript();
const recv = Buffer.from(transcript.recv).toString();
const sent = Buffer.from(transcript.sent).toString();
return {
recv,
sent,
ranges: {
recv: processTranscript(recv),
sent: processTranscript(sent),
},
};
return new Transcript({ sent: transcript.sent, recv: transcript.recv });
}
static getHeaderMap(
@@ -273,7 +256,6 @@ export class Prover {
async reveal(reveal: Reveal) {
return this.#prover.reveal(reveal);
}
}
export class Verifier {
@@ -482,43 +464,6 @@ export class NotaryServer {
}
}
export class Transcript {
#sent: number[];
#recv: number[];
constructor(params: { sent: number[]; recv: number[] }) {
this.#recv = params.recv;
this.#sent = params.sent;
}
static processRanges(text: string) {
return processTranscript(text);
}
recv(redactedSymbol = '*') {
return this.#recv.reduce((recv: string, num) => {
recv =
recv + (num === 0 ? redactedSymbol : Buffer.from([num]).toString());
return recv;
}, '');
}
sent(redactedSymbol = '*') {
return this.#sent.reduce((sent: string, num) => {
sent =
sent + (num === 0 ? redactedSymbol : Buffer.from([num]).toString());
return sent;
}, '');
}
text = (redactedSymbol = '*') => {
return {
sent: this.sent(redactedSymbol),
recv: this.recv(redactedSymbol),
};
};
}
export {
type ParsedTranscriptData,
type LoggingLevel,

74
src/transcript.ts Normal file
View File

@@ -0,0 +1,74 @@
import { Buffer } from 'buffer';
import { HTTPParser } from 'http-parser-js';
export class Transcript {
#sent: number[];
#recv: number[];
constructor(params: { sent: number[]; recv: number[] }) {
this.#recv = params.recv;
this.#sent = params.sent;
}
get raw() {
return {
recv: this.#recv,
sent: this.#sent,
};
}
parseRecv() {}
parseSent() {
const parser = new HTTPParser(HTTPParser.REQUEST);
const sent = Buffer.from(this.#sent);
const body: Buffer[] = [];
let complete = false;
let headers: string[] = [];
parser.onBody = (t) => {
body.push(t);
};
parser.onHeadersComplete = (res) => {
headers = res.headers;
};
parser.onMessageComplete = () => {
complete = true;
};
parser.execute(sent);
parser.finish();
if (!complete) throw new Error('Could not parse REQUEST');
return {
headers,
body,
};
}
recv(redactedSymbol = '*') {
return this.#recv.reduce((recv: string, num) => {
recv =
recv + (num === 0 ? redactedSymbol : Buffer.from([num]).toString());
return recv;
}, '');
}
sent(redactedSymbol = '*') {
return this.#sent.reduce((sent: string, num) => {
sent =
sent + (num === 0 ? redactedSymbol : Buffer.from([num]).toString());
return sent;
}, '');
}
text = (redactedSymbol = '*') => {
return {
sent: this.sent(redactedSymbol),
recv: this.recv(redactedSymbol),
};
};
}

View File

@@ -1,401 +1,5 @@
import { ParsedTranscriptData } from './types';
import { Buffer } from 'buffer';
type Stack =
| {
type: 'object';
symbol: string;
range: [number, number];
id: number;
}
| {
type: 'array';
symbol: string;
range: [number, number];
data: string;
id: number;
}
| {
type: 'object_key';
symbol: string;
range: [number, number];
data: string;
path: string;
id: number;
objectId: number;
}
| {
type: 'object_value';
symbol: string;
range: [number, number];
data: string;
id: number;
keyId: number;
objectId: number;
}
| {
type: 'object_value_string';
symbol: string;
range: [number, number];
data: string;
path: string;
id: number;
objectId: number;
valueId: number;
}
| {
type: 'object_value_number';
symbol: string;
range: [number, number];
data: string;
path: string;
id: number;
objectId: number;
valueId: number;
};
type Commitment = {
name?: string;
path?: string;
start: number;
end: number;
};
export function processJSON(str: string): Commitment[] {
const json = JSON.parse(str);
expect(typeof json === 'object', 'json string must be an object');
const stack: Stack[] = [];
const values: Stack[] = [];
const keys: string[] = [];
let nonce = 0,
keyId = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charAt(i);
let last = stack[stack.length - 1];
if (last?.type === 'object_key') {
if (char === '"') {
last.range[1] = i;
const key = stack.pop();
expect(key!.type === 'object_key');
if (key?.type === 'object_key') {
keys.push(key.data);
key.path = keys.join('.');
values.push(key);
keyId = last.id;
}
} else {
last.data = last.data + char;
}
continue;
}
if (last?.type === 'object_value_string') {
if (char === '"') {
last.range[1] = i;
values.push(stack.pop()!);
const objectValue = stack.pop();
expect(
objectValue?.type === 'object_value',
'expect stack to be object_value',
);
objectValue!.range[1] = i;
values.push(objectValue!);
keys.pop();
} else {
last.data = last.data + char;
}
continue;
}
if (last?.type === 'array') {
if (char === ']') {
last.range[1] = i;
values.push(stack.pop()!);
} else if (char === '[') {
stack.push({
symbol: '[',
type: 'array',
id: nonce++,
range: [i, -1],
data: '',
});
} else {
last.data = last.data + char;
}
continue;
}
if (last?.type === 'object_value_number') {
if (char === ',' || char === '}') {
last.range[1] = i - 1;
values.push(stack.pop()!);
const objectValue = stack.pop();
expect(
objectValue?.type === 'object_value',
'expect stack to be object_value',
);
objectValue!.range[1] = i - 1;
values.push(objectValue!);
last = stack[stack.length - 1];
} else {
last.data = last.data + char;
continue;
}
}
if (last?.type === 'object_value') {
if (char === '}') {
last.range[1] = i - 1;
values.push(stack.pop()!);
const object = stack.pop();
expect(object?.type === 'object_value', 'expect stack to be object');
object!.range[1] = i;
values.push(object!);
keys.pop();
} else if (char === ',') {
last.range[1] = i - 1;
values.push(stack.pop()!);
keys.pop();
} else if (char === '{') {
stack.push({
symbol: '{',
type: 'object',
id: nonce++,
range: [i, -1],
});
} else if (char === '[') {
stack.push({
symbol: '[',
type: 'array',
id: nonce++,
range: [i, -1],
data: '',
});
} else if (char === '"') {
stack.push({
symbol: '"',
type: 'object_value_string',
objectId: last.objectId,
valueId: last.id,
id: nonce++,
data: '',
range: [i, -1],
path: '',
});
} else if (/^\d$/.test(char)) {
stack.push({
symbol: '"',
type: 'object_value_number',
objectId: last.objectId,
valueId: last.id,
id: nonce++,
data: '',
range: [i, -1],
path: '',
});
}
continue;
}
if (last?.type === 'object') {
switch (char) {
case '}':
last.range[1] = i;
values.push(stack.pop()!);
continue;
case '"':
stack.push({
symbol: '"',
type: 'object_key',
objectId: last.id,
id: nonce++,
data: '',
range: [i, -1],
path: '',
});
continue;
case ':':
stack.push({
symbol: ':',
type: 'object_value',
objectId: last.id,
keyId: keyId,
id: nonce++,
range: [i, -1],
data: '',
});
continue;
default:
continue;
}
}
switch (char) {
case '{':
stack.push({
symbol: '{',
type: 'object',
id: nonce++,
range: [i, -1],
});
break;
case '[':
stack.push({
symbol: '[',
type: 'array',
id: nonce++,
range: [i, -1],
data: '',
});
break;
}
}
expect(!stack.length, 'invalid stack length');
const commitments: {
[key: string]: Commitment;
} = {};
for (const value of values) {
if (value.type === 'object_key') {
commitments[value.id] = {
...(commitments[value.id] || {}),
path: value.path,
start: value.range[0],
};
} else if (value.type === 'object_value') {
commitments[value.keyId] = {
...(commitments[value.keyId] || {}),
end: value.range[1] + 1,
};
} else if (value.type === 'object') {
commitments[value.id] = {
start: value.range[0],
end: value.range[1] + 1,
};
} else if (value.type === 'array') {
commitments[value.id] = {
start: value.range[0],
end: value.range[1] + 1,
};
}
}
return Object.values(commitments).map(({ path, start, end }) => ({
path,
start,
end,
}));
}
export function processTranscript(transcript: string): ParsedTranscriptData {
// const commitments: Commitment[] = [];
const returnVal: ParsedTranscriptData = {
all: {
start: 0,
end: transcript.length,
},
info: {
start: 0,
end: transcript.indexOf('\n') + 1,
},
headers: {},
lineBreaks: [],
};
let text = '',
ptr = -1,
lineIndex = 0,
isBody = false;
for (let i = 0; i < transcript.length; i++) {
const char = transcript.charAt(i);
if (char === '\r') {
_processEOL(text, i, lineIndex++);
returnVal.lineBreaks.push({
start: i,
end: i + 1,
});
continue;
}
if (char === '\n') {
text = '';
ptr = -1;
returnVal.lineBreaks.push({
start: i,
end: i + 1,
});
continue;
}
if (ptr === -1) {
ptr = i;
}
text = text + char;
}
_processEOL(text, transcript.length - 1, lineIndex++);
return returnVal;
function _processEOL(txt: string, index: number, lineIndex: number) {
try {
if (!txt) return;
if (!isNaN(Number(txt))) {
isBody = true;
return;
}
const json = JSON.parse(txt);
returnVal.body = {
start: ptr,
end: index,
};
if (typeof json === 'object') {
const jsonCommits = processJSON(txt);
jsonCommits.forEach((commit) => {
if (commit.path) {
returnVal.json = returnVal.json || {};
returnVal.json[commit.path] = {
start: commit.start + ptr,
end: commit.end + ptr,
};
}
});
}
} catch (e) {
const [name, value] = txt.split(': ');
if (lineIndex === 0) {
returnVal.info = {
start: ptr,
end: index,
};
} else if (!isBody && value) {
returnVal.headers = returnVal.headers || {};
returnVal.headers[name.toLowerCase()] = {
start: ptr,
end: index,
};
} else if (isBody) {
returnVal.body = {
// value: txt,
start: ptr,
end: index,
};
}
}
}
}
export function expect(cond: any, msg = 'invalid expression') {
if (!cond) throw new Error(msg);
}

99
test/specs/transcript.ts Normal file
View File

@@ -0,0 +1,99 @@
import { describe, it } from 'mocha';
import * as assert from 'assert';
import Transcript from '../../src/transcript';
import { HTTPParser } from 'http-parser-js';
describe('Transcript parsing', () => {
it('should parse transcript correctly', async () => {
const transcript = new Transcript({ sent: swapiSent, recv: swapiRecv });
const parser = new HTTPParser(HTTPParser.REQUEST);
const bodyChunks: Buffer[] = [];
let headerInfo = null;
parser.onBody = (t) => {
bodyChunks.push(t);
};
parser.onHeadersComplete = (req) => {
headerInfo = req;
};
parser.execute(Buffer.from(swapiSent));
parser.finish();
console.log(bodyChunks, headerInfo);
console.log(transcript.sent());
assert.strictEqual(true, true, 'test');
});
});
const swapiRecv = [
72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75, 13, 10, 83, 101,
114, 118, 101, 114, 58, 32, 110, 103, 105, 110, 120, 47, 49, 46, 49, 54, 46,
49, 13, 10, 68, 97, 116, 101, 58, 32, 70, 114, 105, 44, 32, 48, 55, 32, 70,
101, 98, 32, 50, 48, 50, 53, 32, 48, 55, 58, 51, 55, 58, 49, 49, 32, 71, 77,
84, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 32,
97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110,
13, 10, 84, 114, 97, 110, 115, 102, 101, 114, 45, 69, 110, 99, 111, 100, 105,
110, 103, 58, 32, 99, 104, 117, 110, 107, 101, 100, 13, 10, 67, 111, 110, 110,
101, 99, 116, 105, 111, 110, 58, 32, 99, 108, 111, 115, 101, 13, 10, 86, 97,
114, 121, 58, 32, 65, 99, 99, 101, 112, 116, 44, 32, 67, 111, 111, 107, 105,
101, 13, 10, 88, 45, 70, 114, 97, 109, 101, 45, 79, 112, 116, 105, 111, 110,
115, 58, 32, 83, 65, 77, 69, 79, 82, 73, 71, 73, 78, 13, 10, 69, 84, 97, 103,
58, 32, 34, 101, 101, 51, 57, 56, 54, 49, 48, 52, 51, 53, 99, 51, 50, 56, 102,
52, 100, 48, 97, 52, 101, 49, 98, 48, 100, 50, 102, 55, 98, 98, 99, 34, 13,
10, 65, 108, 108, 111, 119, 58, 32, 71, 69, 84, 44, 32, 72, 69, 65, 68, 44,
32, 79, 80, 84, 73, 79, 78, 83, 13, 10, 83, 116, 114, 105, 99, 116, 45, 84,
114, 97, 110, 115, 112, 111, 114, 116, 45, 83, 101, 99, 117, 114, 105, 116,
121, 58, 32, 109, 97, 120, 45, 97, 103, 101, 61, 49, 53, 55, 54, 56, 48, 48,
48, 13, 10, 13, 10, 50, 56, 55, 13, 10, 123, 34, 110, 97, 109, 101, 34, 58,
34, 76, 117, 107, 101, 32, 83, 107, 121, 119, 97, 108, 107, 101, 114, 34, 44,
34, 104, 101, 105, 103, 104, 116, 34, 58, 34, 49, 55, 50, 34, 44, 34, 109, 97,
115, 115, 34, 58, 34, 55, 55, 34, 44, 34, 104, 97, 105, 114, 95, 99, 111, 108,
111, 114, 34, 58, 34, 98, 108, 111, 110, 100, 34, 44, 34, 115, 107, 105, 110,
95, 99, 111, 108, 111, 114, 34, 58, 34, 102, 97, 105, 114, 34, 44, 34, 101,
121, 101, 95, 99, 111, 108, 111, 114, 34, 58, 34, 98, 108, 117, 101, 34, 44,
34, 98, 105, 114, 116, 104, 95, 121, 101, 97, 114, 34, 58, 34, 49, 57, 66, 66,
89, 34, 44, 34, 103, 101, 110, 100, 101, 114, 34, 58, 34, 109, 97, 108, 101,
34, 44, 34, 104, 111, 109, 101, 119, 111, 114, 108, 100, 34, 58, 34, 104, 116,
116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105, 46, 100, 101, 118, 47, 97,
112, 105, 47, 112, 108, 97, 110, 101, 116, 115, 47, 49, 47, 34, 44, 34, 102,
105, 108, 109, 115, 34, 58, 91, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
109, 115, 47, 49, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
109, 115, 47, 50, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
109, 115, 47, 51, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
109, 115, 47, 54, 47, 34, 93, 44, 34, 115, 112, 101, 99, 105, 101, 115, 34,
58, 91, 93, 44, 34, 118, 101, 104, 105, 99, 108, 101, 115, 34, 58, 91, 34,
104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105, 46, 100, 101,
118, 47, 97, 112, 105, 47, 118, 101, 104, 105, 99, 108, 101, 115, 47, 49, 52,
47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105,
46, 100, 101, 118, 47, 97, 112, 105, 47, 118, 101, 104, 105, 99, 108, 101,
115, 47, 51, 48, 47, 34, 93, 44, 34, 115, 116, 97, 114, 115, 104, 105, 112,
115, 34, 58, 91, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112,
105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 115, 116, 97, 114, 115, 104,
105, 112, 115, 47, 49, 50, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47,
47, 115, 119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 115, 116,
97, 114, 115, 104, 105, 112, 115, 47, 50, 50, 47, 34, 93, 44, 34, 99, 114,
101, 97, 116, 101, 100, 34, 58, 34, 50, 48, 49, 52, 45, 49, 50, 45, 48, 57,
84, 49, 51, 58, 53, 48, 58, 53, 49, 46, 54, 52, 52, 48, 48, 48, 90, 34, 44,
34, 101, 100, 105, 116, 101, 100, 34, 58, 34, 50, 48, 49, 52, 45, 49, 50, 45,
50, 48, 84, 50, 49, 58, 49, 55, 58, 53, 54, 46, 56, 57, 49, 48, 48, 48, 90,
34, 44, 34, 117, 114, 108, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47,
115, 119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 112, 101,
111, 112, 108, 101, 47, 49, 47, 34, 125, 13, 10, 48, 13, 10, 13, 10,
];
const swapiSent = [
71, 69, 84, 32, 104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105,
46, 100, 101, 118, 47, 97, 112, 105, 47, 112, 101, 111, 112, 108, 101, 47, 49,
32, 72, 84, 84, 80, 47, 49, 46, 49, 13, 10, 99, 111, 110, 110, 101, 99, 116,
105, 111, 110, 58, 32, 99, 108, 111, 115, 101, 13, 10, 99, 111, 110, 116, 101,
110, 116, 45, 108, 101, 110, 103, 116, 104, 58, 32, 50, 53, 13, 10, 99, 111,
110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 32, 97, 112, 112, 108,
105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 13, 10, 104, 111,
115, 116, 58, 32, 115, 119, 97, 112, 105, 46, 100, 101, 118, 13, 10, 13, 10,
123, 34, 104, 101, 108, 108, 111, 34, 58, 34, 119, 111, 114, 108, 100, 34, 44,
34, 111, 110, 101, 34, 58, 49, 125,
];

View File

@@ -145,7 +145,7 @@ after(async function () {
});
describe('tlsn-js test suite', function () {
fs.readdirSync(path.join(__dirname, 'specs')).forEach((file) => {
fs.readdirSync(path.join(__dirname, 'e2e')).forEach((file) => {
const [id] = file.split('.');
it(`Test ID: ${id}`, async function () {
const content = await check(id);

View File

@@ -37,8 +37,8 @@ module.exports = [
target: 'web',
mode: isProd ? 'production' : 'development',
entry: {
'full-integration-swapi.spec': path.join(__dirname, 'test', 'specs', 'full-integration-swapi.spec.ts'),
'simple-verify': path.join(__dirname, 'test', 'specs', 'simple-verify.spec.ts'),
'full-integration-swapi.spec': path.join(__dirname, 'test', 'e2e', 'full-integration-swapi.spec.ts'),
'simple-verify': path.join(__dirname, 'test', 'e2e', 'simple-verify.spec.ts'),
},
output: {
path: __dirname + '/test-build',