├── .gitignore ├── README.md ├── .travis.yml ├── package.json ├── resolverintercept.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # truffle-migrate 2 | On-chain migrations management 3 | 4 | ----------------------- 5 | 6 | ### :warning: This repo is deprecated :warning: 7 | **Truffle has moved all modules to a monorepo at [trufflesuite/truffle](https://github.com/trufflesuite/truffle). See you over there!** 8 | 9 | ----------------------- 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: generic 4 | 5 | services: 6 | - docker 7 | 8 | before_install: 9 | - docker pull truffle/ci 10 | 11 | env: 12 | - TEST=repo 13 | - TEST=upstream 14 | - TEST=scenario 15 | 16 | script: 17 | - > 18 | docker run -it --rm --name ${TEST} \ 19 | -e TRAVIS_REPO_SLUG \ 20 | -e TRAVIS_PULL_REQUEST \ 21 | -e TRAVIS_PULL_REQUEST_SLUG \ 22 | -e TRAVIS_PULL_REQUEST_BRANCH \ 23 | -e TRAVIS_BRANCH \ 24 | -e TEST \ 25 | truffle/ci:latest run_tests 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "truffle-migrate", 3 | "version": "2.0.6", 4 | "description": "On-chain migrations management", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/trufflesuite/truffle-migrate.git" 12 | }, 13 | "keywords": [ 14 | "ethereum", 15 | "truffle", 16 | "migrations", 17 | "deployment" 18 | ], 19 | "author": "Tim Coulter ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/trufflesuite/truffle-migrate/issues" 23 | }, 24 | "homepage": "https://github.com/trufflesuite/truffle-migrate#readme", 25 | "dependencies": { 26 | "async": "^2.1.4", 27 | "node-dir": "^0.1.16", 28 | "truffle-deployer": "^2.0.5", 29 | "truffle-expect": "^0.0.3", 30 | "truffle-require": "^1.0.5", 31 | "web3": "^0.20.1" 32 | }, 33 | "devDependencies": { 34 | "mocha": "^3.2.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /resolverintercept.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | function ResolverIntercept(resolver) { 4 | this.resolver = resolver; 5 | this.cache = {}; 6 | }; 7 | 8 | ResolverIntercept.prototype.require = function(import_path) { 9 | // Modify import_path so the cache key is consistently the same irrespective 10 | // of whether a user explicated .sol extension 11 | import_path = import_path.replace(/\.sol$/i, ''); 12 | 13 | // TODO: Using the import path for relative files may result in multiple 14 | // paths for the same file. This could return different objects since it won't be a cache hit. 15 | if (this.cache[import_path]) { 16 | return this.cache[import_path]; 17 | } 18 | 19 | // Note, will error if nothing is found. 20 | var resolved = this.resolver.require(import_path); 21 | 22 | this.cache[import_path] = resolved; 23 | 24 | // During migrations, we could be on a network that takes a long time to accept 25 | // transactions (i.e., contract deployment close to block size). Because successful 26 | // migration is more important than wait time in those cases, we'll synchronize "forever". 27 | resolved.synchronization_timeout = 0; 28 | 29 | return resolved; 30 | }; 31 | 32 | ResolverIntercept.prototype.contracts = function() { 33 | var self = this; 34 | return Object.keys(this.cache).map(function(key) { 35 | return self.cache[key]; 36 | }); 37 | }; 38 | 39 | module.exports = ResolverIntercept; 40 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var dir = require("node-dir"); 3 | var path = require("path"); 4 | var ResolverIntercept = require("./resolverintercept"); 5 | var Require = require("truffle-require"); 6 | var async = require("async"); 7 | var Web3 = require("web3"); 8 | var expect = require("truffle-expect"); 9 | var Deployer = require("truffle-deployer"); 10 | 11 | function Migration(file) { 12 | this.file = path.resolve(file); 13 | this.number = parseInt(path.basename(file)); 14 | }; 15 | 16 | Migration.prototype.run = function(options, callback) { 17 | var self = this; 18 | var logger = options.logger; 19 | 20 | var web3 = new Web3(); 21 | web3.setProvider(options.provider); 22 | 23 | logger.log("Running migration: " + path.relative(options.migrations_directory, this.file)); 24 | 25 | var resolver = new ResolverIntercept(options.resolver); 26 | 27 | // Initial context. 28 | var context = { 29 | web3: web3 30 | }; 31 | 32 | var deployer = new Deployer({ 33 | logger: { 34 | log: function(msg) { 35 | logger.log(" " + msg); 36 | } 37 | }, 38 | network: options.network, 39 | network_id: options.network_id, 40 | provider: options.provider, 41 | basePath: path.dirname(this.file) 42 | }); 43 | 44 | var finish = function(err) { 45 | if (err) return callback(err); 46 | deployer.start().then(function() { 47 | if (options.save === false) return; 48 | 49 | var Migrations = resolver.require("./Migrations.sol"); 50 | 51 | if (Migrations && Migrations.isDeployed()) { 52 | logger.log("Saving successful migration to network..."); 53 | return Migrations.deployed().then(function(migrations) { 54 | return migrations.setCompleted(self.number); 55 | }); 56 | } 57 | }).then(function() { 58 | if (options.save === false) return; 59 | logger.log("Saving artifacts..."); 60 | return options.artifactor.saveAll(resolver.contracts()); 61 | }).then(function() { 62 | // Use process.nextTicK() to prevent errors thrown in the callback from triggering the below catch() 63 | process.nextTick(callback); 64 | }).catch(function(e) { 65 | logger.log("Error encountered, bailing. Network state unknown. Review successful transactions manually."); 66 | callback(e); 67 | }); 68 | }; 69 | 70 | web3.eth.getAccounts(function(err, accounts) { 71 | if (err) return callback(err); 72 | 73 | Require.file({ 74 | file: self.file, 75 | context: context, 76 | resolver: resolver, 77 | args: [deployer], 78 | }, function(err, fn) { 79 | if (!fn || !fn.length || fn.length == 0) { 80 | return callback(new Error("Migration " + self.file + " invalid or does not take any parameters")); 81 | } 82 | fn(deployer, options.network, accounts); 83 | finish(); 84 | }); 85 | }); 86 | }; 87 | 88 | var Migrate = { 89 | Migration: Migration, 90 | 91 | assemble: function(options, callback) { 92 | dir.files(options.migrations_directory, function(err, files) { 93 | if (err) return callback(err); 94 | 95 | options.allowed_extensions = options.allowed_extensions || /^\.(js|es6?)$/; 96 | 97 | var migrations = files.filter(function(file) { 98 | return isNaN(parseInt(path.basename(file))) == false; 99 | }).filter(function(file) { 100 | return path.extname(file).match(options.allowed_extensions) != null; 101 | }).map(function(file) { 102 | return new Migration(file, options.network); 103 | }); 104 | 105 | // Make sure to sort the prefixes as numbers and not strings. 106 | migrations = migrations.sort(function(a, b) { 107 | if (a.number > b.number) { 108 | return 1; 109 | } else if (a.number < b.number) { 110 | return -1; 111 | } 112 | return 0; 113 | }); 114 | 115 | callback(null, migrations); 116 | }); 117 | }, 118 | 119 | run: function(options, callback) { 120 | var self = this; 121 | 122 | expect.options(options, [ 123 | "working_directory", 124 | "migrations_directory", 125 | "contracts_build_directory", 126 | "provider", 127 | "artifactor", 128 | "resolver", 129 | "network", 130 | "network_id", 131 | "logger", 132 | "from", // address doing deployment 133 | ]); 134 | 135 | if (options.reset == true) { 136 | return this.runAll(options, callback); 137 | } 138 | 139 | self.lastCompletedMigration(options, function(err, last_migration) { 140 | if (err) return callback(err); 141 | 142 | // Don't rerun the last completed migration. 143 | self.runFrom(last_migration + 1, options, callback); 144 | }); 145 | }, 146 | 147 | runFrom: function(number, options, callback) { 148 | var self = this; 149 | 150 | this.assemble(options, function(err, migrations) { 151 | if (err) return callback(err); 152 | 153 | while (migrations.length > 0) { 154 | if (migrations[0].number >= number) { 155 | break; 156 | } 157 | 158 | migrations.shift(); 159 | } 160 | 161 | if (options.to) { 162 | migrations = migrations.filter(function(migration) { 163 | return migration.number <= options.to; 164 | }); 165 | } 166 | 167 | self.runMigrations(migrations, options, callback); 168 | }); 169 | }, 170 | 171 | runAll: function(options, callback) { 172 | this.runFrom(0, options, callback); 173 | }, 174 | 175 | runMigrations: function(migrations, options, callback) { 176 | // Perform a shallow clone of the options object 177 | // so that we can override the provider option without 178 | // changing the original options object passed in. 179 | var clone = {}; 180 | 181 | Object.keys(options).forEach(function(key) { 182 | clone[key] = options[key]; 183 | }); 184 | 185 | if (options.quiet) { 186 | clone.logger = { 187 | log: function() {} 188 | } 189 | }; 190 | 191 | clone.provider = this.wrapProvider(options.provider, clone.logger); 192 | clone.resolver = this.wrapResolver(options.resolver, clone.provider); 193 | 194 | async.eachSeries(migrations, function(migration, finished) { 195 | migration.run(clone, function(err) { 196 | if (err) return finished(err); 197 | finished(); 198 | }); 199 | }, callback); 200 | }, 201 | 202 | wrapProvider: function(provider, logger) { 203 | var printTransaction = function(tx_hash) { 204 | logger.log(" ... " + tx_hash); 205 | }; 206 | 207 | return { 208 | send: function(payload) { 209 | var result = provider.send(payload); 210 | 211 | if (payload.method == "eth_sendTransaction") { 212 | printTransaction(result.result); 213 | } 214 | 215 | return result; 216 | }, 217 | sendAsync: function(payload, callback) { 218 | provider.sendAsync(payload, function(err, result) { 219 | if (err) return callback(err); 220 | 221 | if (payload.method == "eth_sendTransaction") { 222 | printTransaction(result.result); 223 | } 224 | 225 | callback(err, result); 226 | }); 227 | } 228 | }; 229 | }, 230 | 231 | wrapResolver: function(resolver, provider) { 232 | return { 233 | require: function(import_path, search_path) { 234 | var abstraction = resolver.require(import_path, search_path); 235 | 236 | abstraction.setProvider(provider); 237 | 238 | return abstraction; 239 | }, 240 | resolve: resolver.resolve 241 | } 242 | }, 243 | 244 | lastCompletedMigration: function(options, callback) { 245 | var Migrations; 246 | 247 | try { 248 | Migrations = options.resolver.require("Migrations"); 249 | } catch (e) { 250 | return callback(new Error("Could not find built Migrations contract: " + e.message)); 251 | } 252 | 253 | if (Migrations.isDeployed() == false) { 254 | return callback(null, 0); 255 | } 256 | 257 | var migrations = Migrations.deployed(); 258 | 259 | Migrations.deployed().then(function(migrations) { 260 | // Two possible Migrations.sol's (lintable/unlintable) 261 | return (migrations.last_completed_migration) 262 | ? migrations.last_completed_migration.call() 263 | : migrations.lastCompletedMigration.call(); 264 | 265 | }).then(function(completed_migration) { 266 | callback(null, completed_migration.toNumber()); 267 | }).catch(callback); 268 | }, 269 | 270 | needsMigrating: function(options, callback) { 271 | var self = this; 272 | 273 | if (options.reset == true) { 274 | return callback(null, true); 275 | } 276 | 277 | this.lastCompletedMigration(options, function(err, number) { 278 | if (err) return callback(err); 279 | 280 | self.assemble(options, function(err, migrations) { 281 | if (err) return callback(err); 282 | 283 | while (migrations.length > 0) { 284 | if (migrations[0].number >= number) { 285 | break; 286 | } 287 | 288 | migrations.shift(); 289 | } 290 | 291 | callback(null, migrations.length > 1); 292 | }); 293 | }); 294 | } 295 | }; 296 | 297 | module.exports = Migrate; 298 | --------------------------------------------------------------------------------