├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── disclose.gemspec ├── exe └── disclose ├── lib ├── disclose.rb └── disclose │ ├── c.rb │ ├── gzip_exe.h │ ├── libiconv_2_dll.h │ ├── libintl_2_dll.h │ ├── tar_exe.h │ ├── version.rb │ └── windows.rb └── spec ├── disclose_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.3.0 5 | before_install: gem install bundler -v 1.12.5 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in disclose.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Minqi Pan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | __Try Node.js Compiler__ 2 | 3 | https://github.com/pmq20/node-compiler 4 | 5 | I have made a new project called node-compiler to compile your Node.js project into one single executable. 6 | 7 | It is better than disclose in that it never runs slowly for the first time, since your source code is compiled together with the Node.js interpreter, just like the standard Node.js libraries. 8 | 9 | Additionally, it redirects file/directory requests transparently to the memory instead of to the file system at runtime. So that no source code is required to run the compiled product. 10 | 11 | # Disclose 12 | 13 | Pack your Node.js project into an executable without recompiling Node.js. 14 | 15 | [![Gem Version](https://badge.fury.io/rb/disclose.svg)](https://badge.fury.io/rb/disclose) 16 | [![Build Status](https://travis-ci.org/pmq20/disclose.svg)](https://travis-ci.org/pmq20/disclose) 17 | [![Code Climate](https://codeclimate.com/github/pmq20/disclose/badges/gpa.svg)](https://codeclimate.com/github/pmq20/disclose) 18 | [![codecov.io](https://codecov.io/github/pmq20/disclose/coverage.svg?branch=master)](https://codecov.io/github/pmq20/disclose?branch=master) 19 | [![Inch CI](http://inch-ci.org/github/pmq20/disclose.svg?branch=master)](http://inch-ci.org/github/pmq20/disclose?branch=master) 20 | 21 | ## Installation 22 | 23 | $ gem install disclose 24 | 25 | ## Dependencies 26 | 27 | Make sure that your system has the following components, 28 | 29 | - tar 30 | - gzip 31 | - xxd 32 | - gcc 33 | 34 | or on Windows, 35 | 36 | - `tar.exe`, which could be installed by [gnuwin32](http://gnuwin32.sourceforge.net) 37 | - `gzip.exe`, which could be installed by [gnuwin32](http://gnuwin32.sourceforge.net) 38 | - `xxd.exe`, which could be installed by [gvim](http://www.vim.org/download.php#pc) 39 | - `gcc.exe`, which could be installed by [mingw-w64](https://sourceforge.net/projects/mingw-w64) 40 | 41 | The generated executable file is guaranteed to have no external dependencies. 42 | 43 | ## Usage 44 | 45 | $ disclose [node_path] [project_path] 46 | 47 | ## Example 48 | 49 | On Windows, 50 | 51 | > disclose "C:\Program Files\nodejs\node.exe" "C:\Users\pmq20\AppData\Roaming\npm\node_modules\coffee-script" 52 | 53 | On Unix, 54 | 55 | $ disclose /usr/local/bin/node /usr/local/lib/node_modules/coffee-script 56 | 57 | ## Hints 58 | 59 | * We recommend using `npm3` to generate the `node_modules` folder before packing 60 | 61 | ## License 62 | 63 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 64 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "disclose" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /disclose.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'disclose/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "disclose" 8 | spec.version = Disclose::VERSION 9 | spec.authors = ["The rugged tests are fragile"] 10 | spec.email = ["pmq2001@gmail.com"] 11 | 12 | spec.summary = %q{Pack your Node.js project into an executable without recompiling Node.js.} 13 | spec.description = %q{Pack your Node.js project into an executable without recompiling Node.js.} 14 | spec.homepage = "https://github.com/pmq20/disclose" 15 | spec.license = "MIT" 16 | 17 | spec.files = [ 18 | '.gitignore', 19 | '.rspec', 20 | '.travis.yml', 21 | 'Gemfile', 22 | 'LICENSE', 23 | 'README.md', 24 | 'Rakefile', 25 | 'bin/console', 26 | 'bin/setup', 27 | 'disclose.gemspec', 28 | 'exe/disclose', 29 | 'lib/disclose.rb', 30 | 'lib/disclose/c.rb', 31 | 'lib/disclose/libiconv_2_dll.h', 32 | 'lib/disclose/libintl_2_dll.h', 33 | 'lib/disclose/tar_exe.h', 34 | 'lib/disclose/gzip_exe.h', 35 | 'lib/disclose/version.rb', 36 | 'lib/disclose/windows.rb', 37 | ] 38 | spec.bindir = "exe" 39 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 40 | spec.require_paths = ["lib"] 41 | 42 | spec.add_development_dependency "bundler", "~> 1.12" 43 | spec.add_development_dependency "rake", "~> 10.0" 44 | spec.add_development_dependency "rspec", "~> 3.0" 45 | spec.add_development_dependency "pry" 46 | end 47 | -------------------------------------------------------------------------------- /exe/disclose: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "disclose" 4 | 5 | unless 2 == ARGV.size 6 | puts Disclose.usage 7 | exit 1 8 | end 9 | 10 | begin 11 | instance = Disclose.new *ARGV 12 | instance.run! 13 | rescue Disclose::Error => e 14 | STDERR.puts e.message 15 | exit 1 16 | end 17 | -------------------------------------------------------------------------------- /lib/disclose.rb: -------------------------------------------------------------------------------- 1 | require "disclose/version" 2 | require "disclose/c" 3 | require 'fileutils' 4 | require 'shellwords' 5 | require 'tmpdir' 6 | require 'json' 7 | require 'digest/md5' 8 | 9 | class Disclose 10 | class Error < RuntimeError; end 11 | 12 | def self.usage 13 | %Q{ 14 | disclose v#{VERSION} 15 | 16 | Usage: disclose [node_path] [project_path] 17 | e.g. disclose /usr/local/bin/node /usr/local/lib/node_modules/coffee-script 18 | 19 | }.strip 20 | end 21 | 22 | def initialize(node_path, project_path) 23 | @node_path = node_path 24 | @project_path = project_path 25 | @working_dir = Dir.mktmpdir 26 | parse_binaries! 27 | end 28 | 29 | def parse_binaries! 30 | @package_path = File.join(@project_path, 'package.json') 31 | raise Error, "No package.json exist at #{@package_path}." unless File.exist?(@package_path) 32 | @package_json = JSON.parse File.read @package_path 33 | @binaries = @package_json['bin'] 34 | if @binaries 35 | STDERR.puts "Detected binaries: #{@binaries}" 36 | else 37 | raise Error, "No Binaries detected inside #{@package_path}." 38 | end 39 | end 40 | 41 | def run! 42 | tar! 43 | header! 44 | c! 45 | end 46 | 47 | def tar! 48 | chdir(@working_dir) do 49 | exe("tar hcf tar.tar -C \"#{@project_path}\" . -C \"#{File.dirname @node_path}\" \"#{File.basename @node_path}\"") 50 | exe("gzip tar.tar") 51 | end 52 | end 53 | 54 | def header! 55 | chdir(@working_dir) do 56 | exe("xxd -i tar.tar.gz > tar.h") 57 | @md5 = Digest::MD5.file('tar.h').to_s 58 | end 59 | end 60 | 61 | def c! 62 | chdir(@working_dir) do 63 | @binaries.each do |key,value| 64 | FileUtils.cp('tar.h', "#{key}.c") 65 | File.open("#{key}.c", "a") do |f| 66 | C.src(f, value, @md5, File.basename(@node_path)) 67 | end 68 | 69 | exe("gcc #{ENV['DISCLOSE_COMPILER_ARG']} #{key}.c -o #{key} -lpthread") 70 | 71 | puts "======= Success =======" 72 | puts File.join(@working_dir, key) 73 | puts "=======================" 74 | end 75 | end 76 | end 77 | 78 | private 79 | 80 | def exe(cmd) 81 | STDERR.puts "$ #{cmd}" 82 | STDERR.print `#{cmd}` 83 | raise Error, "#{cmd} failed!" unless $?.success? 84 | end 85 | 86 | def chdir(path) 87 | STDERR.puts "$ cd #{path}" 88 | Dir.chdir(path) { yield } 89 | STDERR.puts "$ cd #{Dir.pwd}" 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/disclose/c.rb: -------------------------------------------------------------------------------- 1 | class Disclose 2 | module C 3 | class << self 4 | def slash 5 | Gem.win_platform? ? %q{\\\\} : '/' 6 | end 7 | 8 | def src(f, name, md5, node_name) 9 | windows_prepare(f) if Gem.win_platform? 10 | f.puts %Q{ 11 | #{ '#include ' if Gem.win_platform? } 12 | #{ '#include ' if Gem.win_platform? } 13 | #{ '#include ' if Gem.win_platform? } 14 | #{ '#include ' if Gem.win_platform? } 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define PBSTR "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||" 26 | #define PBWIDTH 60 27 | 28 | char *tmp_prefix; 29 | char md5_path[1024]; 30 | char cwd_path[1024]; 31 | double percentage; 32 | 33 | void printProgress() 34 | { 35 | int val = (int) (percentage); 36 | int lpad = (int) (percentage / 100.0 * PBWIDTH); 37 | int rpad = PBWIDTH - lpad; 38 | fprintf(stderr, "\\rExtracting %3d%% [%.*s%*s]", val, lpad, PBSTR, rpad, ""); 39 | fflush(stderr); 40 | } 41 | 42 | void *progress() 43 | { 44 | while (percentage < 100) { 45 | percentage += (rand() % 100) * 1.0 / 100; 46 | sleep(1); 47 | printProgress(); 48 | } 49 | percentage = 100.0; 50 | printProgress(); 51 | return NULL; 52 | } 53 | 54 | void get_tmp_prefix() { 55 | tmp_prefix = getenv("TMPDIR"); 56 | if (NULL != tmp_prefix) return; 57 | tmp_prefix = getenv("TMP"); 58 | if (NULL != tmp_prefix) return; 59 | tmp_prefix = getenv("TEMP"); 60 | if (NULL != tmp_prefix) return; 61 | tmp_prefix = cwd_path; 62 | } 63 | 64 | void untar() { 65 | char cmd[1024] = {0}; 66 | char file0[1024] = {0}; 67 | char file[1024] = {0}; 68 | char dir[1024] = {0}; 69 | FILE *fp = NULL; 70 | int ret; 71 | pthread_t thread; 72 | 73 | pthread_create(&thread, NULL, progress, NULL); 74 | 75 | // Be careful about the number of XXXXXX 76 | // Relate to the magical numbers 23, 20, 19 below. 77 | snprintf(file0, 1023, "%s#{slash}disclose.file.XXXXXX", tmp_prefix); 78 | snprintf(dir, 1023, "%s#{slash}disclose.dir.XXXXXX", tmp_prefix); 79 | 80 | mktemp(file0); 81 | mktemp(dir); 82 | mkdir(dir#{', 0777' unless Gem.win_platform?}); 83 | 84 | snprintf(file, 1023, "%s.gz", file0); 85 | 86 | fp = fopen(file, "wb"); 87 | assert(fp); 88 | fwrite(tar_tar_gz, sizeof(unsigned char), sizeof(tar_tar_gz), fp); 89 | fclose(fp); 90 | 91 | #{tar_windows if Gem.win_platform?} 92 | 93 | chdir(tmp_prefix); 94 | 95 | snprintf(cmd, 1023, "gzip -d \\"%s\\"", file + strlen(file) - 23); 96 | ret = system(cmd); 97 | assert(0 == ret); 98 | 99 | file[strlen(file) - 3] = '\\0'; 100 | 101 | snprintf(cmd, 1023, "tar xf \\"%s\\" -C \\"%s\\"", file + strlen(file) - 20, dir + strlen(dir) - 19); 102 | ret = system(cmd); 103 | assert(0 == ret); 104 | 105 | chdir(cwd_path); 106 | 107 | rename(dir, md5_path); 108 | 109 | percentage = 100.0; 110 | pthread_join(thread, NULL); 111 | 112 | fprintf(stderr, "\\n"); 113 | fflush(stderr); 114 | } 115 | 116 | int main(int argc, char *argv[]) { 117 | int i, index; 118 | struct stat info; 119 | 120 | getcwd(cwd_path, sizeof(cwd_path)); 121 | get_tmp_prefix(); 122 | snprintf(md5_path, 1023, "%s#{slash}disclose.#{md5}", tmp_prefix); 123 | 124 | if( stat( md5_path, &info ) != 0 ) 125 | untar(); 126 | 127 | assert(0 == stat( md5_path, &info ) && info.st_mode & S_IFDIR); 128 | 129 | #{Gem.win_platform? ? execute_windows(name, node_name) : execute_unix(name, node_name)} 130 | } 131 | } 132 | end 133 | 134 | def execute_unix(name, node_name) 135 | %Q{ 136 | char **argv2 = NULL; 137 | char arg0[1024] = {0}; 138 | char arg1[1024] = {0}; 139 | 140 | argv2 = malloc(sizeof(char*) * (argc + 10)); 141 | assert(argv2); 142 | 143 | snprintf(arg0, 1023, "%s#{slash}#{node_name}", md5_path); 144 | snprintf(arg1, 1023, "%s#{slash}#{name}", md5_path); 145 | 146 | argv2[0] = arg0; 147 | argv2[1] = arg1; 148 | index = 2; 149 | for (i = 1; i < argc; ++i) { 150 | argv2[index] = argv[i]; 151 | index += 1; 152 | } 153 | argv2[index] = NULL; 154 | 155 | execv(argv2[0], argv2); 156 | 157 | return 1; 158 | } 159 | end 160 | 161 | def execute_windows(name, node_name) 162 | %Q{ 163 | char arg0[32768] = {0}; 164 | 165 | char *arg0_ptr = arg0; 166 | snprintf(arg0_ptr, 32767 - strlen(arg0), "%s#{slash}#{node_name}", md5_path); 167 | arg0_ptr += strlen(arg0_ptr); 168 | snprintf(arg0_ptr, 32767 - strlen(arg0), " \\"%s#{slash}#{name}\\" ", md5_path); 169 | for (i = 1; i < argc; ++i) { 170 | arg0_ptr += strlen(arg0_ptr); 171 | snprintf(arg0_ptr, 32767 - strlen(arg0), " \\"%s\\" ", argv[i]); 172 | } 173 | 174 | STARTUPINFO si; 175 | PROCESS_INFORMATION pi; 176 | 177 | ZeroMemory( &si, sizeof(si) ); 178 | si.cb = sizeof(si); 179 | ZeroMemory( &pi, sizeof(pi) ); 180 | 181 | // Start the child process. 182 | if( !CreateProcess( NULL, // No module name (use command line) 183 | arg0, // Command line 184 | NULL, // Process handle not inheritable 185 | NULL, // Thread handle not inheritable 186 | FALSE, // Set handle inheritance to FALSE 187 | 0, // No creation flags 188 | NULL, // Use parent's environment block 189 | NULL, // Use parent's starting directory 190 | &si, // Pointer to STARTUPINFO structure 191 | &pi ) // Pointer to PROCESS_INFORMATION structure 192 | ) 193 | { 194 | printf( "CreateProcess failed (%d).\\n", GetLastError() ); 195 | return 101; 196 | } 197 | 198 | // Wait until child process exits. 199 | WaitForSingleObject( pi.hProcess, INFINITE ); 200 | 201 | // Close process and thread handles. 202 | CloseHandle( pi.hProcess ); 203 | CloseHandle( pi.hThread ); 204 | 205 | return 0; 206 | } 207 | end 208 | 209 | def tar_windows 210 | %Q{ 211 | chdir(tmp_prefix); 212 | 213 | fp = fopen("libiconv-2.dll", "wb"); 214 | assert(fp); 215 | fwrite(libiconv_2_dll, sizeof(unsigned char), sizeof(libiconv_2_dll), fp); 216 | fclose(fp); 217 | 218 | fp = fopen("libintl-2.dll", "wb"); 219 | assert(fp); 220 | fwrite(libintl_2_dll, sizeof(unsigned char), sizeof(libintl_2_dll), fp); 221 | fclose(fp); 222 | 223 | fp = fopen("tar.exe", "wb"); 224 | assert(fp); 225 | fwrite(tar_exe, sizeof(unsigned char), sizeof(tar_exe), fp); 226 | fclose(fp); 227 | 228 | fp = fopen("gzip.exe", "wb"); 229 | assert(fp); 230 | fwrite(gzip_exe, sizeof(unsigned char), sizeof(gzip_exe), fp); 231 | fclose(fp); 232 | 233 | chdir(cwd_path); 234 | } 235 | end 236 | 237 | def windows_prepare(f) 238 | f.puts File.read File.expand_path('../libiconv_2_dll.h', __FILE__) 239 | f.puts File.read File.expand_path('../libintl_2_dll.h', __FILE__) 240 | f.puts File.read File.expand_path('../tar_exe.h', __FILE__) 241 | f.puts File.read File.expand_path('../gzip_exe.h', __FILE__) 242 | end 243 | end 244 | end 245 | end 246 | -------------------------------------------------------------------------------- /lib/disclose/version.rb: -------------------------------------------------------------------------------- 1 | class Disclose 2 | VERSION = "0.9.1" 3 | end 4 | -------------------------------------------------------------------------------- /lib/disclose/windows.rb: -------------------------------------------------------------------------------- 1 | class Disclose 2 | module Windows 3 | 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/disclose_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Disclose do 4 | it 'has a version number' do 5 | expect(Disclose::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'disclose' 3 | --------------------------------------------------------------------------------