mirror of https://github.com/embarklabs/embark.git
feat(scaffold): allow association/file
- Refactor everything to TS - Add missing types - Declare __ everywhere
This commit is contained in:
parent
5b3d8943cd
commit
f68f1fc9b6
21
package.json
21
package.json
|
@ -72,6 +72,7 @@
|
|||
"@babel/preset-react": "7.0.0",
|
||||
"@babel/preset-typescript": "7.1.0",
|
||||
"@babel/runtime-corejs2": "7.1.2",
|
||||
"ajv": "6.5.5",
|
||||
"ascii-table": "0.0.9",
|
||||
"async": "2.6.1",
|
||||
"babel-loader": "8.0.4",
|
||||
|
@ -164,6 +165,25 @@
|
|||
"uuid": "3.3.2",
|
||||
"viz.js": "1.8.2",
|
||||
"web3": "1.0.0-beta.34",
|
||||
"web3-bzz": "1.0.0-beta.34",
|
||||
"web3-core-helpers": "1.0.0-beta.34",
|
||||
"web3-core-method": "1.0.0-beta.34",
|
||||
"web3-core-promievent": "1.0.0-beta.34",
|
||||
"web3-core-requestmanager": "1.0.0-beta.34",
|
||||
"web3-core-subscriptions": "1.0.0-beta.34",
|
||||
"web3-core": "1.0.0-beta.34",
|
||||
"web3-eth-abi": "1.0.0-beta.34",
|
||||
"web3-eth-accounts": "1.0.0-beta.34",
|
||||
"web3-eth-contract": "1.0.0-beta.34",
|
||||
"web3-eth-iban": "1.0.0-beta.34",
|
||||
"web3-eth-personal": "1.0.0-beta.34",
|
||||
"web3-eth": "1.0.0-beta.34",
|
||||
"web3-net": "1.0.0-beta.34",
|
||||
"web3-providers-http": "1.0.0-beta.34",
|
||||
"web3-providers-ipc": "1.0.0-beta.34",
|
||||
"web3-providers-ws": "1.0.0-beta.34",
|
||||
"web3-shh": "1.0.0-beta.34",
|
||||
"web3-utils": "1.0.0-beta.34",
|
||||
"webpack": "4.19.0",
|
||||
"webpack-bundle-analyzer": "2.13.1",
|
||||
"websocket": "1.0.28",
|
||||
|
@ -174,6 +194,7 @@
|
|||
"@babel/cli": "7.1.2",
|
||||
"@babel/plugin-proposal-optional-chaining": "7.0.0",
|
||||
"@types/async": "2.0.50",
|
||||
"@types/handlebars": "4.0.39",
|
||||
"@types/i18n": "0.8.3",
|
||||
"@types/node": "10.11.7",
|
||||
"@types/os-locale": "2.1.0",
|
||||
|
|
|
@ -311,39 +311,12 @@ class Cmd {
|
|||
|
||||
scaffold() {
|
||||
program
|
||||
.command('scaffold [contract] [fields...]')
|
||||
.command('scaffold [contractOrFile] [fields...]')
|
||||
.option('--framework <framework>', 'UI framework to use. (default: react)')
|
||||
.option('--contract-language <language>', 'Language used for the smart contract generation (default: solidity)')
|
||||
.option('--overwrite', 'Overwrite existing files. (default: false)')
|
||||
.description(__('Generates a contract and a function tester for you\nExample: ContractName field1:uint field2:address --contract-language solidity --framework react'))
|
||||
.action(function(contract, fields, options) {
|
||||
if (contract === undefined) {
|
||||
console.log("contract name is required");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let fieldMapping = {};
|
||||
if (fields.length > 0) {
|
||||
const typeRegex = /^(u?int[0-9]{0,3}(\[\])?|string|bool|address|bytes[0-9]{0,3})(\[\])?$/;
|
||||
const varRegex = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
||||
fieldMapping = fields.reduce((acc, curr) => {
|
||||
const c = curr.split(':');
|
||||
|
||||
if (!varRegex.test(c[0])) {
|
||||
console.log("Invalid variable name: " + c[0]);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!typeRegex.test(c[1])) {
|
||||
console.log("Invalid datatype: " + c[1] + " - The dApp generator might not support this type at the moment");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
acc[c[0]] = c[1];
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
.action(function(contractOrFile, fields, options) {
|
||||
i18n.setOrDetectLocale(options.locale);
|
||||
options.env = 'development';
|
||||
options.logFile = options.logfile; // fix casing
|
||||
|
@ -351,11 +324,8 @@ class Cmd {
|
|||
options.onlyCompile = options.contracts;
|
||||
options.client = options.client || 'geth';
|
||||
options.webpackConfigName = options.pipeline || 'development';
|
||||
options.contract = contract;
|
||||
options.framework = options.framework || 'react';
|
||||
options.contractLanguage = options.contractLanguage || 'solidity';
|
||||
options.overwrite = options.overwrite || false;
|
||||
options.fields = fieldMapping;
|
||||
options.contractOrFile = contractOrFile;
|
||||
options.fields = fields;
|
||||
|
||||
embark.scaffold(options);
|
||||
});
|
||||
|
|
|
@ -471,9 +471,8 @@ class EmbarkController {
|
|||
callback();
|
||||
},
|
||||
function generateContract(callback) {
|
||||
engine.events.request('scaffolding:generate:contract', options, function(err, file) {
|
||||
// Add contract file to the manager
|
||||
engine.events.request('config:contractsFiles:add', file);
|
||||
engine.events.request('scaffolding:generate:contract', options, function(files) {
|
||||
files.forEach(file => engine.events.request('config:contractsFiles:add', file));
|
||||
callback();
|
||||
});
|
||||
},
|
||||
|
|
|
@ -9,8 +9,6 @@ import Web3 from "web3";
|
|||
import { Embark, Events } from "../../../typings/embark";
|
||||
import Suggestions from "./suggestions";
|
||||
|
||||
declare const __: any;
|
||||
|
||||
class Console {
|
||||
private embark: Embark;
|
||||
private events: Events;
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
const Handlebars = require('handlebars');
|
||||
const fs = require('../../core/fs');
|
||||
let utils = require('../../utils/utils.js');
|
||||
|
||||
|
||||
Handlebars.registerHelper('capitalize', function(word) {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('ifview', function(stateMutability, options) {
|
||||
let result = stateMutability === 'view' || stateMutability === 'pure' || stateMutability === 'constant';
|
||||
if (result) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('ifeq', function(elem, value, options) {
|
||||
if (elem === value) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('ifarr', function(elem, options) {
|
||||
if (elem.indexOf('[]') > -1) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
|
||||
Handlebars.registerHelper('iflengthgt', function(arr, val, options) {
|
||||
if (arr.length > val) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('emptyname', function(name, index) {
|
||||
return name ? name : 'output' + index;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('trim', function(name) {
|
||||
return name.replace('[]', '');
|
||||
});
|
||||
|
||||
|
||||
Handlebars.registerHelper('methodname', function(abiDefinition, functionName, inputs) {
|
||||
let funCount = abiDefinition.filter(x => x.name === functionName).length;
|
||||
if (funCount === 1) {
|
||||
return '.' + functionName;
|
||||
}
|
||||
return new Handlebars.SafeString(`['${functionName}(${inputs !== null ? inputs.map(input => input.type).join(',') : ''})']`);
|
||||
});
|
||||
|
||||
class ScaffoldingReact {
|
||||
constructor(embark, options) {
|
||||
this.embark = embark;
|
||||
this.options = options;
|
||||
this.embark.registerDappGenerator('react', this.build.bind(this));
|
||||
}
|
||||
|
||||
_generateFile(contract, templateFilename, extension, data, overwrite) {
|
||||
const filename = contract.className.toLowerCase() + '.' + extension;
|
||||
const filePath = './app/' + filename;
|
||||
if (!overwrite && fs.existsSync(filePath)) {
|
||||
throw new Error("file '" + filePath + "' already exists");
|
||||
}
|
||||
|
||||
const templatePath = utils.joinPath(__dirname, 'templates/' + templateFilename);
|
||||
const source = fs.readFileSync(templatePath).toString();
|
||||
const template = Handlebars.compile(source);
|
||||
|
||||
// Write template
|
||||
const result = template(data);
|
||||
fs.writeFileSync(filePath, result);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
build(contract, overwrite, cb) {
|
||||
const packageInstallCmd = 'npm install react react-bootstrap react-dom';
|
||||
utils.runCmd(packageInstallCmd, null, (err) => {
|
||||
if (err) {
|
||||
this.embark.logger.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const filename = contract.className.toLowerCase();
|
||||
|
||||
this._generateFile(contract, 'index.html.hbs', 'html',
|
||||
{
|
||||
'title': contract.className,
|
||||
filename
|
||||
}, overwrite);
|
||||
|
||||
const filePath = this._generateFile(contract, 'dapp.js.hbs', 'js',
|
||||
{
|
||||
'title': contract.className,
|
||||
'contractName': contract.className,
|
||||
'functions': contract.abiDefinition.filter(x => x.type === 'function')
|
||||
}, overwrite);
|
||||
|
||||
// Update config
|
||||
const contents = fs.readFileSync("./embark.json");
|
||||
let embarkJson = JSON.parse(contents);
|
||||
embarkJson.app["js/" + filename + ".js"] = "app/" + filename + '.js';
|
||||
embarkJson.app[filename + ".html"] = "app/" + filename + '.html';
|
||||
|
||||
fs.writeFileSync("./embark.json", JSON.stringify(embarkJson, null, 4));
|
||||
|
||||
this.embark.logger.info('app/' + filename + ".html generated");
|
||||
this.embark.logger.info('app/' + filename + ".js generated");
|
||||
|
||||
cb(null, filePath);
|
||||
} catch (error) {
|
||||
this.embark.logger.error(error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ScaffoldingReact;
|
|
@ -1,57 +0,0 @@
|
|||
|
||||
const Handlebars = require('handlebars');
|
||||
const fs = require('../../core/fs');
|
||||
const utils = require('../../utils/utils');
|
||||
|
||||
const capitalize = string => string.charAt(0).toUpperCase() + string.slice(1);
|
||||
|
||||
class ScaffoldingSolidity {
|
||||
constructor(embark, options){
|
||||
this.embark = embark;
|
||||
this.options = options;
|
||||
this.embark.registerDappGenerator('solidity', this.build.bind(this));
|
||||
}
|
||||
|
||||
_generateFile(contract, templateFilename, extension, data, overwrite){
|
||||
const filename = capitalize(contract.className.toLowerCase()) + '.' + extension;
|
||||
const contractDirs = this.embark.config.embarkConfig.contracts;
|
||||
const contractDir = Array.isArray(contractDirs) ? contractDirs[0] : contractDirs;
|
||||
const filePath = fs.dappPath(contractDir.replace(/\*/g, ''), filename);
|
||||
if (!overwrite && fs.existsSync(filePath)){
|
||||
throw new Error("file '" + filePath + "' already exists");
|
||||
}
|
||||
|
||||
const templatePath = utils.joinPath(__dirname, 'templates/' + templateFilename);
|
||||
const source = fs.readFileSync(templatePath).toString();
|
||||
const template = Handlebars.compile(source);
|
||||
|
||||
// Write template
|
||||
const result = template(data);
|
||||
fs.writeFileSync(filePath, result);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
build(contract, overwrite, cb){
|
||||
try {
|
||||
contract.className = capitalize(contract.className);
|
||||
|
||||
const filename = contract.className;
|
||||
|
||||
const filePath = this._generateFile(contract, 'contract.sol.hbs', 'sol', {
|
||||
'contractName': contract.className,
|
||||
'structName': contract.className + "Struct",
|
||||
'fields': Object.keys(contract.fields).map(f => {
|
||||
return {name:f, type: contract.fields[f]};
|
||||
})
|
||||
}, overwrite);
|
||||
this.embark.logger.info("contracts/" + filename + ".sol generated");
|
||||
|
||||
cb(null, filePath);
|
||||
} catch(error) {
|
||||
this.embark.logger.error(error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ScaffoldingSolidity;
|
|
@ -1,50 +0,0 @@
|
|||
pragma solidity ^0.5.0;
|
||||
|
||||
contract {{contractName}} {
|
||||
|
||||
struct {{structName}} {
|
||||
{{#each fields}}
|
||||
{{type}} {{name}};
|
||||
{{/each}}
|
||||
}
|
||||
|
||||
{{structName}}[] items;
|
||||
|
||||
event ItemCreated(uint id, address createdBy);
|
||||
event ItemDeleted(uint id, address deletedBy);
|
||||
event ItemUpdated(uint id, address updatedBy);
|
||||
|
||||
function add({{#each fields}}{{type}} _{{name}}{{#unless @last}}, {{/unless}}{{/each}}) public {
|
||||
uint id = items.length++;
|
||||
items[id] = {{structName}}({
|
||||
{{#each fields}}
|
||||
{{name}}: _{{name}}{{#unless @last}},{{/unless}}
|
||||
{{/each}}
|
||||
});
|
||||
|
||||
emit ItemCreated(id, msg.sender);
|
||||
}
|
||||
|
||||
function edit(uint _id, {{#each fields}}{{type}} _{{name}}{{#unless @last}}, {{/unless}}{{/each}}) public {
|
||||
require(_id < items.length, "Invalid {{structName}} id");
|
||||
|
||||
{{#each fields}}
|
||||
items[_id].{{name}} = _{{name}};
|
||||
{{/each}}
|
||||
|
||||
emit ItemUpdated(_id, msg.sender);
|
||||
}
|
||||
|
||||
function remove(uint _id) public {
|
||||
require(_id < items.length, "Invalid {{structName}} id");
|
||||
|
||||
delete items[_id];
|
||||
emit ItemDeleted(_id, msg.sender);
|
||||
}
|
||||
|
||||
function get(uint _id) public view returns ({{#each fields}}{{type}}{{#unless @last}},{{/unless}}{{/each}}) {
|
||||
require(_id < items.length, "Invalid ArrayContractStruct id");
|
||||
return ({{#each fields}}items[_id].{{name}}{{#unless @last}},{{/unless}}{{/each}});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface Builder {
|
||||
build(): Promise<string[]>;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import Ajv from "ajv";
|
||||
|
||||
import { Logger } from "../../../typings/logger";
|
||||
|
||||
export enum Framework {
|
||||
React = "react",
|
||||
}
|
||||
|
||||
export enum ContractLanguage {
|
||||
Solidity = "solidity",
|
||||
}
|
||||
|
||||
export class CommandOptions {
|
||||
constructor(private readonly logger: Logger,
|
||||
public readonly framework: Framework = Framework.React,
|
||||
public readonly contractLanguage: ContractLanguage = ContractLanguage.Solidity,
|
||||
public readonly overwrite: boolean = false,
|
||||
) {
|
||||
}
|
||||
|
||||
public validate() {
|
||||
if (!Object.values(Framework).includes(this.framework)) {
|
||||
this.logger.error(__("Selected framework not supported"));
|
||||
this.logger.error(__("Supported Frameworks are: %s", Object.values(Framework).join(", ")));
|
||||
process.exit(1);
|
||||
}
|
||||
if (!Object.values(ContractLanguage).includes(this.contractLanguage)) {
|
||||
this.logger.error(__("Selected contract language not supported"));
|
||||
this.logger.error(__("Supported Contract Languages are: %s", Object.values(ContractLanguage).join(", ")));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import Handlebars from "handlebars";
|
||||
import * as path from "path";
|
||||
|
||||
import { Embark } from "../../../../../typings/embark";
|
||||
import { Builder } from "../../builder";
|
||||
import { CommandOptions } from "../../commandOptions";
|
||||
import { SmartContractsRecipe } from "../../smartContractsRecipe";
|
||||
|
||||
const fs = require("../../../../core/fs");
|
||||
require("../../handlebarHelpers");
|
||||
|
||||
const templatePath = path.join(__dirname, "templates", "contract.sol.hbs");
|
||||
|
||||
export class SolidityBuilder implements Builder {
|
||||
constructor(private embark: Embark,
|
||||
private description: SmartContractsRecipe,
|
||||
private options: CommandOptions) {
|
||||
}
|
||||
|
||||
public async build() {
|
||||
return Object.keys(this.description.data).map((contractName) => {
|
||||
const code = this.generateCode(contractName);
|
||||
const file = this.saveFile(contractName, code);
|
||||
this.printInstructions(contractName);
|
||||
return file;
|
||||
});
|
||||
}
|
||||
|
||||
private generateCode(contractName: string) {
|
||||
const source = fs.readFileSync(templatePath, "utf-8");
|
||||
const template = Handlebars.compile(source);
|
||||
|
||||
const attributes = this.description.standardAttributes(contractName);
|
||||
const ipfs = this.description.ipfsAttributes(contractName);
|
||||
const associations = this.description.associationAttributes(contractName);
|
||||
|
||||
const data = {
|
||||
associations,
|
||||
attributes,
|
||||
contractName,
|
||||
ipfs,
|
||||
structName: `${contractName}Struct`,
|
||||
};
|
||||
|
||||
return template(data);
|
||||
}
|
||||
|
||||
private saveFile(contractName: string, code: string) {
|
||||
const filename = `${contractName}.sol`;
|
||||
const contractDirs = this.embark.config.embarkConfig.contracts;
|
||||
const contractDir = Array.isArray(contractDirs) ? contractDirs[0] : contractDirs;
|
||||
const filePath = fs.dappPath(contractDir.replace(/\*/g, ""), filename);
|
||||
if (!this.options.overwrite && fs.existsSync(filePath)) {
|
||||
this.embark.logger.error(__(`The contract ${contractName} already exists, skipping.`));
|
||||
return;
|
||||
}
|
||||
|
||||
fs.writeFileSync(filePath, code);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
private printInstructions(contractName: string) {
|
||||
const associations = Object.keys(this.description.associationAttributes(contractName));
|
||||
if (!associations.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const args = associations.map((name) => `"$${name}"`).join(", ");
|
||||
this.embark.logger.info(`In order to deploy your contracts, you will have to specify the dependencies.`);
|
||||
this.embark.logger.info(`You can do it by adding to your contracts config the following snippets:`);
|
||||
this.embark.logger.info(`${contractName}: { args: [${args}] }`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
pragma solidity ^0.5.0;
|
||||
|
||||
{{#each associations}}
|
||||
import "./{{@key}}.sol";
|
||||
{{/each}}
|
||||
|
||||
contract {{contractName}} {
|
||||
|
||||
uint constant IMPOSSIBLE_INDEX = 99999999999;
|
||||
|
||||
struct {{structName}} {
|
||||
{{#each attributes}}
|
||||
{{this}} {{@key}};
|
||||
{{/each}}
|
||||
}
|
||||
|
||||
{{structName}}[] items;
|
||||
|
||||
{{#each associations}}
|
||||
{{#ifeq this 'hasMany'}}
|
||||
mapping(uint => uint[]) {{lowercase @key}}Mapping;
|
||||
{{/ifeq}}
|
||||
{{#ifeq this 'belongsTo'}}
|
||||
mapping(uint => uint) {{lowercase @key}}Mapping;
|
||||
{{/ifeq}}
|
||||
{{/each}}
|
||||
|
||||
{{#each ipfs}}
|
||||
mapping(uint => string) {{@key}};
|
||||
{{/each}}
|
||||
|
||||
{{#each associations}}
|
||||
{{@key}} {{lowercase @key}};
|
||||
{{/each}}
|
||||
|
||||
event ItemCreated(uint id, address createdBy);
|
||||
event ItemDeleted(uint id, address deletedBy);
|
||||
event ItemUpdated(uint id, address updatedBy);
|
||||
|
||||
constructor({{#each associations}}address _{{@key}}{{#unless @last}}, {{/unless}}{{/each}}) public {
|
||||
{{#each associations}}
|
||||
{{lowercase @key}} = {{@key}}(_{{@key}});
|
||||
{{/each}}
|
||||
}
|
||||
|
||||
function getLength() public view returns(uint count) {
|
||||
return items.length;
|
||||
}
|
||||
|
||||
function add({{#each attributes}}{{this}}{{#ifstring this}} memory{{/ifstring}} _{{@key}}{{#unless @last}}, {{/unless}}{{/each}}) public {
|
||||
uint id = items.length++;
|
||||
items[id] = {{structName}}({
|
||||
{{#each attributes}}
|
||||
{{@key}}: _{{@key}}{{#unless @last}},{{/unless}}
|
||||
{{/each}}
|
||||
});
|
||||
|
||||
{{#each associations}}
|
||||
{{#ifeq this 'hasMany'}}
|
||||
{{lowercase @key}}Mapping[id] = new uint[](0);
|
||||
{{/ifeq}}
|
||||
{{/each}}
|
||||
|
||||
emit ItemCreated(id, msg.sender);
|
||||
}
|
||||
|
||||
function edit(uint _id, {{#each attributes}}{{this}}{{#ifstring this}} memory{{/ifstring}} _{{@key}}{{#unless @last}}, {{/unless}}{{/each}}) public {
|
||||
require(_id < items.length, "Invalid {{structName}} id");
|
||||
|
||||
{{#each attributes}}
|
||||
items[_id].{{@key}} = _{{@key}};
|
||||
{{/each}}
|
||||
|
||||
emit ItemUpdated(_id, msg.sender);
|
||||
}
|
||||
|
||||
function remove(uint _id) public {
|
||||
require(_id < items.length, "Invalid {{structName}} id");
|
||||
|
||||
delete items[_id];
|
||||
emit ItemDeleted(_id, msg.sender);
|
||||
}
|
||||
|
||||
function get(uint _id) public view returns ({{#each attributes}}{{this}}{{#ifstring this}} memory{{/ifstring}}{{#unless @last}}, {{/unless}}{{/each}}) {
|
||||
require(_id < items.length, "Invalid {{structName}} id");
|
||||
return ({{#each attributes}}items[_id].{{@key}}{{#unless @last}},{{/unless}}{{/each}});
|
||||
}
|
||||
|
||||
{{#each ipfs}}
|
||||
function add{{capitalize @key}}(uint _id, string memory _{{@key}}) public {
|
||||
require(_id < items.length, "Invalid {{structName}} id");
|
||||
|
||||
{{@key}}[_id] = _{{@key}};
|
||||
}
|
||||
|
||||
function remove{{capitalize @key}}(uint _id) public {
|
||||
require(_id < items.length, "Invalid {{structName}} id");
|
||||
|
||||
{{@key}}[_id] = "";
|
||||
}
|
||||
|
||||
function get{{capitalize @key}}(uint _id) public view returns(string memory) {
|
||||
require(_id < items.length, "Invalid {{structName}} id");
|
||||
|
||||
return {{@key}}[_id];
|
||||
}
|
||||
|
||||
{{/each}}
|
||||
{{#each associations}}
|
||||
function add{{capitalize @key}}(uint _id, uint _{{@key}}Id) public {
|
||||
require(_id < items.length, "Invalid {{structName}} id");
|
||||
require(_{{@key}}Id < {{lowercase @key}}.getLength(), "Invalid {{@key}} id");
|
||||
|
||||
{{#ifeq this 'hasMany'}}
|
||||
uint index = indexOf({{lowercase @key}}Mapping[_id], _{{@key}}Id);
|
||||
require(index == IMPOSSIBLE_INDEX, "_{{@key}}Id already added");
|
||||
|
||||
{{lowercase @key}}Mapping[_id].push(_{{@key}}Id);
|
||||
{{/ifeq}}
|
||||
{{#ifeq this 'belongsTo'}}
|
||||
{{lowercase @key}}Mapping[_id] = _{{@key}}Id;
|
||||
{{/ifeq}}
|
||||
|
||||
}
|
||||
|
||||
function remove{{capitalize @key}}(uint _id, uint _{{@key}}Id) public {
|
||||
require(_id < items.length, "Invalid {{structName}} id");
|
||||
require(_{{@key}}Id < {{lowercase @key}}.getLength(), "Invalid {{@key}} id");
|
||||
|
||||
{{#ifeq this 'hasMany'}}
|
||||
uint index = indexOf({{lowercase @key}}Mapping[_id], _{{@key}}Id);
|
||||
require(index != IMPOSSIBLE_INDEX, "_{{@key}}Id not found");
|
||||
|
||||
delete {{lowercase @key}}Mapping[_id][index];
|
||||
{{/ifeq}}
|
||||
{{#ifeq this 'belongsTo'}}
|
||||
{{lowercase @key}}Mapping[_id] = 0;
|
||||
{{/ifeq}}
|
||||
}
|
||||
|
||||
function get{{capitalize @key}}(uint _id) public view returns(uint{{#ifeq this 'hasMany'}}[] memory{{/ifeq}}) {
|
||||
require(_id < items.length, "Invalid {{structName}} id");
|
||||
|
||||
return {{lowercase @key}}Mapping[_id];
|
||||
}
|
||||
|
||||
{{/each}}
|
||||
|
||||
function indexOf(uint[] storage values, uint value) private view returns(uint) {
|
||||
for (uint i = 0; i < values.length; i++) {
|
||||
if (values[i] == value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return IMPOSSIBLE_INDEX;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
import Handlebars from "handlebars";
|
||||
import * as path from "path";
|
||||
|
||||
import { Contract } from "../../../../../typings/contract";
|
||||
import { Embark } from "../../../../../typings/embark";
|
||||
import { Builder } from "../../builder";
|
||||
import { CommandOptions } from "../../commandOptions";
|
||||
import { SmartContractsRecipe } from "../../smartContractsRecipe";
|
||||
|
||||
const fs = require("../../../../core/fs");
|
||||
const utils = require("../../../../utils/utils");
|
||||
require("../../handlebarHelpers");
|
||||
|
||||
const indexTemplatePath = path.join(__dirname, "templates", "index.html.hbs");
|
||||
const dappTemplatePath = path.join(__dirname, "templates", "dapp.js.hbs");
|
||||
|
||||
export class ReactBuilder implements Builder {
|
||||
constructor(private embark: Embark,
|
||||
private description: SmartContractsRecipe,
|
||||
private contracts: Contract[],
|
||||
private options: CommandOptions) {
|
||||
}
|
||||
|
||||
public async build() {
|
||||
await this.installDependencies();
|
||||
|
||||
return [].concat.apply([], Object.keys(this.description.data).map((contractName) => {
|
||||
const [indexCode, dappCode] = this.generateCodes(contractName);
|
||||
if (indexCode && dappCode) {
|
||||
const files = this.saveFiles(contractName, indexCode, dappCode);
|
||||
this.updateEmbarkJson(contractName, files);
|
||||
return files;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private updateEmbarkJson(contractName: string, files: string[]) {
|
||||
const embarkJsonPath = path.join(fs.dappPath(), "embark.json");
|
||||
const embarkJson = fs.readJSONSync(embarkJsonPath);
|
||||
embarkJson.app[`js/${contractName}.js`] = `app/${contractName}.js`;
|
||||
embarkJson.app[`${contractName}.html`] = `app/${contractName}.html`;
|
||||
|
||||
fs.writeFileSync(embarkJsonPath, JSON.stringify(embarkJson, null, 2));
|
||||
}
|
||||
|
||||
private generateCodes(contractName: string) {
|
||||
const indexSource = fs.readFileSync(indexTemplatePath, "utf-8");
|
||||
const dappSource = fs.readFileSync(dappTemplatePath, "utf-8");
|
||||
|
||||
const indexTemplate = Handlebars.compile(indexSource);
|
||||
const dappTemplate = Handlebars.compile(dappSource);
|
||||
|
||||
const indexData = {
|
||||
filename: contractName.toLowerCase(),
|
||||
title: contractName,
|
||||
};
|
||||
|
||||
const contract = this.contracts.find((c) => c.className === contractName);
|
||||
if (!contract) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dappData = {
|
||||
contractName,
|
||||
functions: contract.abiDefinition.filter((entry) => entry.type === "function"),
|
||||
};
|
||||
|
||||
return [indexTemplate(indexData), dappTemplate(dappData)];
|
||||
}
|
||||
|
||||
private installDependencies() {
|
||||
const cmd = "npm install react react-bootstrap react-dom";
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
utils.runCmd(cmd, null, (error: string) => {
|
||||
if (error) {
|
||||
return reject(new Error(error));
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private saveFiles(contractName: string, indexCode: string, dappCode: string) {
|
||||
const indexFilePath = path.join(fs.dappPath(), "app", `${contractName}.html`);
|
||||
const dappFilePath = path.join(fs.dappPath(), "app", `${contractName}.js`);
|
||||
|
||||
if (!this.options.overwrite && (fs.existsSync(indexFilePath) || fs.existsSync(dappFilePath))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
fs.writeFileSync(indexFilePath, indexCode);
|
||||
fs.writeFileSync(dappFilePath, dappCode);
|
||||
|
||||
this.embark.logger.info(__(`${indexFilePath} generated`));
|
||||
this.embark.logger.info(__(`${dappFilePath} generated`));
|
||||
return [indexFilePath, dappFilePath];
|
||||
}
|
||||
}
|
|
@ -171,7 +171,7 @@ class {{capitalize name}}Form{{@index}} extends Component {
|
|||
|
||||
function {{contractName}}UI(props) {
|
||||
return (<div>
|
||||
<h1>{{title}}</h1>
|
||||
<h1>{{contractName}}</h1>
|
||||
{{#each functions}}
|
||||
<{{capitalize name}}Form{{@index}} />
|
||||
{{/each}}
|
|
@ -0,0 +1,62 @@
|
|||
import Handlebars from "handlebars";
|
||||
import { ABIDefinition } from "web3/eth/abi";
|
||||
|
||||
Handlebars.registerHelper("capitalize", (word: string) => {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("lowercase", (word: string) => {
|
||||
return word.toLowerCase();
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("ifview", function(stateMutability: string, options: Handlebars.HelperOptions) {
|
||||
const isView = stateMutability === "view" || stateMutability === "pure" || stateMutability === "constant";
|
||||
if (isView) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("ifstring", function(value: string, options: Handlebars.HelperOptions) {
|
||||
if (value === "string") {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("ifeq", function(elem: string, value: string, options: Handlebars.HelperOptions) {
|
||||
if (elem === value) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("ifarr", function(elem: string, options: Handlebars.HelperOptions) {
|
||||
if (elem.indexOf("[]") > -1) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("iflengthgt", function(arr, val, options: Handlebars.HelperOptions) {
|
||||
if (arr.length > val) {
|
||||
return options.fn(this);
|
||||
}
|
||||
return options.inverse(this);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("emptyname", (name: string, index: string) => {
|
||||
return name ? name : "output" + index;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("trim", (name: string) => {
|
||||
return name.replace("[]", "");
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("methodname", (abiDefinition: ABIDefinition[], functionName: string, inputs: any[]) => {
|
||||
const funCount = abiDefinition.filter((x) => x.name === functionName).length;
|
||||
if (funCount === 1) {
|
||||
return "." + functionName;
|
||||
}
|
||||
return new Handlebars.SafeString(`["${functionName}(${inputs !== null ? inputs.map((input) => input.type).join(",") : ""})"]`);
|
||||
});
|
|
@ -1,96 +0,0 @@
|
|||
class Scaffolding {
|
||||
constructor(embark, _options) {
|
||||
this.embark = embark;
|
||||
this.options = _options;
|
||||
this.plugins = _options.plugins;
|
||||
|
||||
embark.events.setCommandHandler("scaffolding:generate:contract", (options, cb) => {
|
||||
this.framework = options.contractLanguage;
|
||||
this.fields = options.fields;
|
||||
this.generate(options.contract, options.overwrite, true, cb);
|
||||
});
|
||||
|
||||
embark.events.setCommandHandler("scaffolding:generate:ui", (options, cb) => {
|
||||
this.framework = options.framework;
|
||||
this.fields = options.fields;
|
||||
this.generate(options.contract, options.overwrite, false, cb);
|
||||
});
|
||||
}
|
||||
|
||||
getScaffoldPlugin(framework) {
|
||||
let dappGenerators = this.plugins.getPluginsFor('dappGenerator');
|
||||
let builder = null;
|
||||
dappGenerators.forEach((plugin) => {
|
||||
plugin.dappGenerators.forEach((d) => {
|
||||
if (d.framework === framework) {
|
||||
builder = d.cb;
|
||||
}
|
||||
});
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
loadFrameworkModule() {
|
||||
switch (this.framework) {
|
||||
case 'react':
|
||||
this.plugins.loadInternalPlugin('scaffolding-react', this.options);
|
||||
break;
|
||||
case 'solidity':
|
||||
this.plugins.loadInternalPlugin('scaffolding-solidity', this.options);
|
||||
break;
|
||||
default:
|
||||
this.embark.logger.error(__('Selected framework not supported'));
|
||||
this.embark.logger.error(__('Supported Frameworks are: %s', 'react, solidity'));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
generate(contractName, overwrite, isContractGeneration, cb) {
|
||||
this.loadFrameworkModule();
|
||||
|
||||
const build = this.getScaffoldPlugin(this.framework);
|
||||
if (!build) {
|
||||
this.embark.logger.error("Could not find plugin for framework '" + this.framework + "'");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hasFields = Object.getOwnPropertyNames(this.fields).length !== 0;
|
||||
|
||||
if (isContractGeneration && !hasFields) {
|
||||
// This happens when you execute "scaffold ContractName",
|
||||
// assuming the contract already exists in a .sol file
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
|
||||
let contract;
|
||||
if (isContractGeneration && hasFields) {
|
||||
contract = {className: contractName, fields: this.fields};
|
||||
try {
|
||||
build(contract, overwrite, cb);
|
||||
} catch (err) {
|
||||
this.embark.logger.error(err.message);
|
||||
}
|
||||
} else {
|
||||
// Contract already exists
|
||||
this.embark.events.request("contracts:list", (_err, contractsList) => {
|
||||
if (_err) throw new Error(_err);
|
||||
const contract = contractsList.find(x => x.className === contractName);
|
||||
if (!contract) {
|
||||
this.embark.logger.error("contract '" + contractName + "' does not exist");
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
build(contract, overwrite, cb);
|
||||
} catch (err) {
|
||||
this.embark.logger.error(err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = Scaffolding;
|
|
@ -0,0 +1,63 @@
|
|||
import { Contract } from "../../../typings/contract";
|
||||
import { Embark } from "../../../typings/embark";
|
||||
import { CommandOptions, ContractLanguage, Framework } from "./commandOptions";
|
||||
import { SolidityBuilder } from "./contractLanguage/solidityBuilder";
|
||||
import { ReactBuilder } from "./framework/reactBuilder";
|
||||
import { SmartContractsRecipe } from "./smartContractsRecipe";
|
||||
|
||||
export default class Scaffolding {
|
||||
|
||||
constructor(private embark: Embark, private options: any) {
|
||||
this.embark.events.setCommandHandler("scaffolding:generate:contract", (cmdLineOptions: any, cb: (files: string[]) => void) => {
|
||||
this.generateContract(cmdLineOptions).then(cb);
|
||||
});
|
||||
|
||||
this.embark.events.setCommandHandler("scaffolding:generate:ui", (cmdLineOptions: any, cb: (files: string[]) => void) => {
|
||||
this.generateUi(cmdLineOptions).then(cb);
|
||||
});
|
||||
}
|
||||
|
||||
private contractLanguageStrategy(recipe: SmartContractsRecipe, options: CommandOptions) {
|
||||
switch (options.contractLanguage) {
|
||||
case ContractLanguage.Solidity: {
|
||||
return new SolidityBuilder(this.embark, recipe, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private frameworkStrategy(recipe: SmartContractsRecipe, contracts: Contract[], options: CommandOptions) {
|
||||
switch (options.framework) {
|
||||
case Framework.React: {
|
||||
return new ReactBuilder(this.embark, recipe, contracts, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private parseAndValidate(cmdLineOptions: any) {
|
||||
const options = new CommandOptions(this.embark.logger, cmdLineOptions.framework, cmdLineOptions.contractLanguage, cmdLineOptions.overwrite);
|
||||
const recipe = new SmartContractsRecipe(this.embark.logger, cmdLineOptions.contractOrFile, cmdLineOptions.fields);
|
||||
options.validate();
|
||||
recipe.validate();
|
||||
|
||||
return {recipe, options};
|
||||
}
|
||||
|
||||
private async generateContract(cmdLineOptions: any) {
|
||||
const {recipe, options} = this.parseAndValidate(cmdLineOptions);
|
||||
return await this.contractLanguageStrategy(recipe, options).build();
|
||||
}
|
||||
|
||||
private async generateUi(cmdLineOptions: any) {
|
||||
const {recipe, options} = this.parseAndValidate(cmdLineOptions);
|
||||
const contracts = await this.getContracts();
|
||||
return await this.frameworkStrategy(recipe, contracts, options).build();
|
||||
}
|
||||
|
||||
private getContracts() {
|
||||
return new Promise<Contract[]>((resolve) => {
|
||||
this.embark.events.request("contracts:list", (_: null, contracts: Contract[]) => {
|
||||
resolve(contracts);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
export const schema = {
|
||||
minProperties: 1,
|
||||
patternProperties: {
|
||||
".*": {
|
||||
minProperties: 1,
|
||||
patternProperties: {
|
||||
"^[a-zA-Z][a-zA-Z0-9_]*$": {
|
||||
pattern: "^(u?int[0-9]{0,3}(\[\])?|string|bool|address|belongsTo|hasMany|ipfsText|ipfsImage|bytes[0-9]{0,3})(\[\])?$",
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
type: ["object"],
|
||||
},
|
||||
},
|
||||
title: "Scafold Schema",
|
||||
type: "object",
|
||||
};
|
|
@ -0,0 +1,106 @@
|
|||
import Ajv from "ajv";
|
||||
|
||||
import { Logger } from "../../../typings/logger";
|
||||
import { schema } from "./schema";
|
||||
|
||||
const fs = require("../../core/fs");
|
||||
|
||||
const ajv = new Ajv();
|
||||
const scaffoldingSchema = ajv.compile(schema);
|
||||
|
||||
interface Properties {
|
||||
[propertyName: string]: string;
|
||||
}
|
||||
|
||||
interface Data {
|
||||
[contractName: string]: Properties;
|
||||
}
|
||||
|
||||
const ASSOCIATIONS = ["belongsTo", "hasMany"];
|
||||
const IPFS = ["ipfsText", "ipfsImage"];
|
||||
|
||||
export class SmartContractsRecipe {
|
||||
public data: Data;
|
||||
constructor(private readonly logger: Logger,
|
||||
private readonly contractOrFile: string,
|
||||
private readonly fields: string[],
|
||||
) {
|
||||
if (fs.existsSync(contractOrFile)) {
|
||||
this.data = fs.readJSONSync(contractOrFile);
|
||||
} else {
|
||||
this.data = this.build();
|
||||
}
|
||||
}
|
||||
|
||||
public standardAttributes(contractName: string): Properties {
|
||||
return Object.keys(this.data[contractName]).reduce((acc: Properties, propertyName: string) => {
|
||||
const type = this.data[contractName][propertyName];
|
||||
if (!ASSOCIATIONS.includes(type) && !IPFS.includes(type)) {
|
||||
acc[propertyName] = type;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
public ipfsAttributes(contractName: string): Properties {
|
||||
return Object.keys(this.data[contractName]).reduce((acc: Properties, propertyName: string) => {
|
||||
const type = this.data[contractName][propertyName];
|
||||
if (IPFS.includes(type)) {
|
||||
acc[propertyName] = type;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
public associationAttributes(contractName: string): Properties {
|
||||
return Object.keys(this.data[contractName]).reduce((acc: Properties, propertyName: string) => {
|
||||
const type = this.data[contractName][propertyName];
|
||||
if (ASSOCIATIONS.includes(type)) {
|
||||
acc[propertyName] = type;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
public validate() {
|
||||
if (!scaffoldingSchema(this.data)) {
|
||||
this.logger.error(__("The scaffolding schema is not valid:"));
|
||||
this.logger.error(ajv.errorsText(scaffoldingSchema.errors));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const contractNames = Object.keys(this.data);
|
||||
contractNames.forEach((contractName) => {
|
||||
if (contractName[0] !== contractName[0].toUpperCase()) {
|
||||
this.logger.error(__(`${contractName} must be capitalized.`));
|
||||
process.exit(1);
|
||||
}
|
||||
Object.keys(this.associationAttributes(contractName)).forEach((associationName) => {
|
||||
if (associationName === contractName) {
|
||||
this.logger.error(__(`${contractName} is referring to himself.`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!contractNames.includes(associationName)) {
|
||||
this.logger.error(__(`${contractName} not found. Please make sure it is in the description.`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (Object.keys(this.data[associationName]).includes(contractName)) {
|
||||
this.logger.error(__(`${associationName} has a cyclic dependencies with ${contractName}.`));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private build() {
|
||||
return {
|
||||
[this.contractOrFile]: this.fields.reduce((acc: Properties, property) => {
|
||||
const [name, value] = property.split(":");
|
||||
acc[name] = value;
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { ABIDefinition } from "web3/eth/abi";
|
||||
|
||||
export interface Contract {
|
||||
abiDefinition: ABIDefinition[];
|
||||
className: string;
|
||||
}
|
|
@ -5,7 +5,7 @@ export interface Events {
|
|||
request: any;
|
||||
emit: any;
|
||||
once: any;
|
||||
setCommandHandler: any;
|
||||
setCommandHandler(name: string, callback: (options: any, cb: () => void) => void): void;
|
||||
}
|
||||
|
||||
export interface Embark {
|
||||
|
@ -13,6 +13,13 @@ export interface Embark {
|
|||
registerAPICall: any;
|
||||
registerConsoleCommand: any;
|
||||
logger: Logger;
|
||||
config: {};
|
||||
config: {
|
||||
embarkConfig: {
|
||||
contracts: string[] | string;
|
||||
config: {
|
||||
contracts: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
registerActionForEvent(name: string, action: (callback: () => void) => void): void;
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
declare function __(...values: string[]): string;
|
|
@ -0,0 +1,8 @@
|
|||
export interface Plugin {
|
||||
dappGenerators: any;
|
||||
}
|
||||
|
||||
export interface Plugins {
|
||||
getPluginsFor(name: string): [Plugin];
|
||||
loadInternalPlugin(name: string, options: any): void;
|
||||
}
|
|
@ -13,7 +13,8 @@
|
|||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": ["./typings/*"]
|
||||
}
|
||||
},
|
||||
"noImplicitThis": false
|
||||
},
|
||||
"include": ["./src/**/*"]
|
||||
}
|
25
yarn.lock
25
yarn.lock
|
@ -825,6 +825,11 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/handlebars@4.0.39":
|
||||
version "4.0.39"
|
||||
resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.39.tgz#961fb54db68030890942e6aeffe9f93a957807bd"
|
||||
integrity sha512-vjaS7Q0dVqFp85QhyPSZqDKnTTCemcSHNHFvDdalO1s0Ifz5KuE64jQD5xoUkfdWwF4WpqdJEl7LsWH8rzhKJA==
|
||||
|
||||
"@types/i18n@0.8.3":
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/i18n/-/i18n-0.8.3.tgz#f602164f2fae486ea87590f6be5d6dd5db1664e6"
|
||||
|
@ -1187,6 +1192,16 @@ ajv-keywords@^3.0.0, ajv-keywords@^3.1.0:
|
|||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
|
||||
integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=
|
||||
|
||||
ajv@6.5.5, ajv@^6.0.1, ajv@^6.1.0, ajv@^6.5.3, ajv@^6.5.5:
|
||||
version "6.5.5"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.5.tgz#cf97cdade71c6399a92c6d6c4177381291b781a1"
|
||||
integrity sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==
|
||||
dependencies:
|
||||
fast-deep-equal "^2.0.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^4.7.0:
|
||||
version "4.11.8"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
|
||||
|
@ -1205,16 +1220,6 @@ ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5, ajv@^5.2.0:
|
|||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.3.0"
|
||||
|
||||
ajv@^6.0.1, ajv@^6.1.0, ajv@^6.5.3, ajv@^6.5.5:
|
||||
version "6.5.5"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.5.tgz#cf97cdade71c6399a92c6d6c4177381291b781a1"
|
||||
integrity sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==
|
||||
dependencies:
|
||||
fast-deep-equal "^2.0.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
align-text@^0.1.1, align-text@^0.1.3:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
|
||||
|
|
Loading…
Reference in New Issue