embark/packages/core/utils/src/file.ts

234 lines
7.1 KiB
TypeScript

import { Logger } from 'embark-logger';
import { __ } from 'embark-i18n';
import * as fs from 'fs-extra';
import * as path from 'path';
import { downloadFile } from './network';
import { dappPath, embarkPath } from './pathUtils';
import { ImportRemapping, prepareForCompilation } from './solidity/remapImports';
import { cargo } from 'async';
const HTTP_CONTRACTS_DIRECTORY = '.embark/contracts/';
export enum Types {
embarkInternal = 'embark_internal',
dappFile = 'dapp_file',
custom = 'custom',
http = 'http',
}
export class File {
public type: Types;
public externalUrl: string = '';
public path = '';
public basedir: string;
public resolver: (callback: (content: string) => void) => void;
public pluginPath: string;
public storageConfig: any;
public providerUrl: string;
public importRemappings: ImportRemapping[] = [];
public originalPath: string;
constructor(options: any) {
this.type = options.type;
this.basedir = options.basedir || '';
this.resolver = options.resolver;
this.pluginPath = options.pluginPath ? options.pluginPath : '';
this.storageConfig = options.storageConfig;
this.providerUrl = '';
this.originalPath = options.originalPath || '';
if (this.type === Types.custom && this.pluginPath) {
this.path = path.join(this.pluginPath, options.path).replace(dappPath(), '');
if (this.path.startsWith('/')) {
this.path = this.path.substring(1);
}
} else if (this.type === Types.http) {
const external = getExternalContractUrl(options.externalUrl, this.providerUrl);
if (external !== null) {
this.externalUrl = external.url;
this.path = path.normalize(dappPath(external.filePath));
}
} else {
this.path = path.normalize(options.path);
}
}
public async prepareForCompilation(isCoverage = false) {
if (!this.path.endsWith('.sol')) {
return Promise.reject(__('This method is only supported for Solidity files'));
}
return prepareForCompilation(this, isCoverage);
}
public get content(): Promise<string> {
return new Promise<string>((resolve, reject) => {
switch (this.type) {
case Types.embarkInternal: {
const content = fs.readFileSync(embarkPath(path.join('dist', this.path)), 'utf-8');
return resolve(content);
}
case Types.dappFile: {
const content = fs.readFileSync(this.path, 'utf-8').toString();
return resolve(content);
}
case Types.custom: {
return this.resolver((content: string) => {
resolve(content);
});
}
case Types.http: {
fs.ensureFileSync(this.path);
return downloadFile(this.externalUrl, this.path, err => {
if (err) {
reject(err);
}
const content = fs.readFileSync(this.path, 'utf-8');
resolve(content);
});
}
}
});
}
}
export function getExternalContractUrl(file: string, providerUrl: string) {
let url;
const RAW_URL = 'https://raw.githubusercontent.com/';
const DEFAULT_SWARM_GATEWAY = 'https://swarm-gateways.net/';
const MALFORMED_SWARM_ERROR = 'Malformed Swarm gateway URL for ';
const MALFORMED_ERROR = 'Malformed Github URL for ';
const MALFORMED_IPFS_ERROR = 'Malformed IPFS URL for ';
const IPFS_GETURL_NOTAVAILABLE = 'IPFS getUrl is not available. Please set it in your storage config. For more info: https://framework.embarklabs.io/docs/storage_configuration.html';
if (file.startsWith('https://github')) {
const file_path = file.match(/https:\/\/github\.[a-z]+\/(.*)/);
if (!file_path) {
console.error(MALFORMED_ERROR + file);
return null;
}
url = `${RAW_URL}${file_path[1].replace('blob/', '')}`;
} else if (file.startsWith('ipfs')) {
if (!providerUrl) {
console.error(IPFS_GETURL_NOTAVAILABLE);
return null;
}
let file_path = file.match(/ipfs:\/\/([-a-zA-Z0-9]+)\/(.*)/);
if (!file_path) {
file_path = file.match(/ipfs:\/\/([-a-zA-Z0-9]+)/);
if (!file_path) {
console.error(MALFORMED_IPFS_ERROR + file);
return null;
}
}
let matchResult = file_path[1];
if (file_path[2]) {
matchResult += '/' + file_path[2];
}
url = `${providerUrl}${matchResult}`;
return {
filePath: HTTP_CONTRACTS_DIRECTORY + matchResult,
url,
};
} else if (file.startsWith('git')) {
// Match values
// [0] entire input
// [1] git://
// [2] user
// [3] repository
// [4] path
// [5] branch
const file_path = file.match(
/(git:\/\/)?github\.[a-z]+\/([-a-zA-Z0-9@:%_+.~#?&=]+)\/([-a-zA-Z0-9@:%_+.~#?&=]+)\/([-a-zA-Z0-9@:%_+.~?\/&=]+)#?([a-zA-Z0-9\/_.-]*)?/,
);
if (!file_path) {
console.error(MALFORMED_ERROR + file);
return null;
}
let branch = file_path[5];
if (!branch) {
branch = 'master';
}
url = `${RAW_URL}${file_path[2]}/${file_path[3]}/${branch}/${file_path[4]}`;
} else if (file.startsWith('http')) {
url = file;
} else if (file.startsWith('bzz')) {
if (!providerUrl) {
url = DEFAULT_SWARM_GATEWAY + file;
} else {
let file_path = file.match(/bzz:\/([-a-zA-Z0-9]+)\/(.*)/);
if (!file_path) {
file_path = file.match(/bzz:\/([-a-zA-Z0-9]+)/);
if (!file_path) {
console.log(MALFORMED_SWARM_ERROR + file);
return null;
}
}
url = providerUrl + '/' + file;
}
} else {
return null;
}
const urlToMatch = providerUrl && providerUrl.includes("localhost") ? url.replace(providerUrl, "") : url;
const match = urlToMatch.match(
/(?:\.[a-z]+|localhost:[0-9]+)\/([-a-zA-Z0-9@:%_+.~#?&\/=]+)/,
);
return {
filePath: HTTP_CONTRACTS_DIRECTORY + (match !== null ? match[1] : ''),
url,
};
}
export function getCircularReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
}
export function getAppendLogFileCargo(logFilePath: string, logger: Logger) {
return cargo((tasks, callback) => {
let appendThis = '';
tasks.forEach(task => {
// Write each line to a JSON string. The replacer is to avoid circular dependencies
// Add a comma at the end to be able to make an array off of it when reading
appendThis += `${JSON.stringify(task, getCircularReplacer())},\n`;
});
fs.appendFile(logFilePath, appendThis, (err) => {
if (err) {
logger.error('Error writing to the log file', err.message);
logger.trace(err);
}
callback();
});
});
}
export async function readAppendedLogs(logFile: string, asString: boolean = false) {
await fs.ensureFile(logFile);
const data = await fs.readFile(logFile);
let stringData = data.toString();
if (!stringData) {
return asString ? '[]' : [];
}
// remove last comma and add brackets around to make it an array of object logs
stringData = `[${stringData.substring(0, stringData.length - 2)}]`;
if (asString) {
return stringData;
}
return JSON.parse(stringData);
}