-
Notifications
You must be signed in to change notification settings - Fork 6
/
index.js
209 lines (187 loc) · 9.02 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
'use strict';
var fs = require('graceful-fs');
var resolvePath = require('path').resolve;
var joinPath = require('path').join;
var mergeOptions = require('merge-options');
var DEFAULT_TEMPLATE_PATH = {
PROJECT: resolveDefaultTemplatePath('project-template.xml'),
DIR: resolveDefaultTemplatePath('dir-template.xml'),
FILE: resolveDefaultTemplatePath('file-template.xml')
};
var RE = {
PRE_TAG_INDENTS: /^\s+?</mg,
DIR_NAME: getInjectRegExp('dirName'),
FILES: getInjectRegExp('files'),
FILE_NAME: getInjectRegExp('fileName'),
FILE_PATH: getInjectRegExp('filePath'),
INPUT_EXE: getInjectRegExp('inputExe'),
OUTPUT_EXE: getInjectRegExp('outputExe'),
OPT_DELETE_EXTRACTED: getInjectRegExp('deleteExtractedOnExit'),
OPT_COMPRESS_FILES: getInjectRegExp('compressFiles'),
OPT_SHARE_VIRTUAL_SYSTEM: getInjectRegExp('shareVirtualSystem'),
OPT_MAP_WITH_TEMP: getInjectRegExp('mapExecutableWithTemporaryFile'),
OPT_ALLOW_RUNNING_VIRTUAL_EXE: getInjectRegExp('allowRunningOfVirtualExeFiles')
};
// Helper to get absolute path to template
function resolveDefaultTemplatePath(templateName) {
return joinPath(__dirname, 'templates', templateName);
}
// Helper to generate a regular expression which will match strings that look like:
// '<!-- inject: ' + what + ' -->'
function getInjectRegExp(what) {
return new RegExp('<!--\\s*?inject\\s*?:\\s*?' + what + '\\s*?-->', 'i');
}
// Take a template path, read/load it's content and return it. If we fail to load the file, we will throw an appropriate
// error.
// Note: The template file should be encoded in UCS2/UTF16LE (that's the encoding that Enigma Virtual Box expects)
function loadTemplate(templatePath) {
var contents;
try {
contents = fs.readFileSync(resolvePath(templatePath), 'ucs2');
// We remove indents to trim down template size (you can always beautify/prettify the end result if you wish)
contents = contents.replace(RE.PRE_TAG_INDENTS, '<');
} catch (e) {
e.message = "Failed to load template. Template path: '" + templatePath + "'.\n" + e.message;
throw e;
}
return contents;
}
// The default filter approves adding all files and directories
function defaultFilter(fullPath, name, isDir) {
return true;
}
// The Dir class represents a directory
function Dir(name, tree) {
this.name = name;
this.tree = tree;
}
// Take a path and return an array that will contain the entire file list located at that path (sub directories and
// everything). For a file the matching array element will be a String containing it's name (no path). For a directory
// the matching element is going to be an Object that has a `name` key that holds the directory name (String) and a
// `tree` key that holds an Array returned from readDirTree
function readDirTree(path) {
var dirTree = fs.readdirSync(path);
var n = dirTree.length;
var name;
var innerPath;
var stats;
var dir;
while (n--) {
name = dirTree[n];
innerPath = resolvePath(path, name);
stats = fs.lstatSync(innerPath);
if (stats.isDirectory()) {
// Create dir object
dir = new Dir(name, readDirTree(innerPath));
// Replace dir name with dir tree
dirTree[n] = dir;
}
}
return dirTree;
}
// Take a path to pack, along with templates and return a xml (String) that can be placed as the value of a `Files` tag.
// The `filter` function is used to decide if the file or directory should be added to xml (return true) or not (return
// false). If we fail to read the directory tree for the provided path, it will throw an appropriate error
function generateDirTreeXml(path2Pack, dirTemplate, fileTemplate, filter) {
// Helper for the recursive creation of the xml
var generateDirTreeXmlPart = function (path, dirTree) {
var parts = [];
dirTree.forEach(function (element) {
var isDir = element instanceof Dir;
var name = isDir ? element.name : element;
var fullPath = joinPath(path, name);
var part;
var filesXml;
// Check if the caller wants to skip this file or directory
if (!filter(fullPath, name, isDir)) {
return;
}
if (isDir) {
// The element describes a directory
part = dirTemplate.replace(RE.DIR_NAME, name);
filesXml = generateDirTreeXmlPart(fullPath, element.tree);
part = part.replace(RE.FILES, filesXml);
} else {
// The element describes a file
part = fileTemplate.replace(RE.FILE_NAME, element);
part = part.replace(RE.FILE_PATH, fullPath);
}
// Add the xml for the element
parts.push(part);
});
// We return a xml string for the current dirTree
return parts.join('');
};
var dirTree;
try {
dirTree = readDirTree(path2Pack);
} catch (e) {
e.message = "Failed to read the directory tree of: '" + path2Pack + "'.\n" + e.message;
throw e;
}
return generateDirTreeXmlPart(path2Pack, dirTree);
}
// This is the entry point to the module.
// In this function we synchronously generate an 'Enigma Virtual Box' project file. The file will include entries for
// all the files and dirs located at `path2Pack`, so when you process the project using Enigma's GUI/CLI you will get an
// executable with all the files packed into it.
// - projectName (String) - the file path to which we want to save the generated evb file (e.g. 'build/myProject.evb')
// - inputExe (String) - the input executable file path. Enigma packs the files from `path2Pack` into a copy of this exe
// - outputExe (String) - the output executable file path. Enigma saves the packed file to this path
// - path2Pack (String) - the path to the directory with the content that we want to pack into the copy of inputExe
// - options (Object) - optional
// - filter (Function) - optional, if provided it will be called with each file and directory from `path2Pack`. The
// function should return true for any file or directory the user want to pack, and false for anything else
// - templatePath (Object) - optional, will default to the files in the templates directory:
// - project (String) - optional, path to a project template
// - dir (String) - optional, path to a directory template
// - file (String) - optional, path to a file template
// - evbOptions (Object) - optional:
// - deleteExtractedOnExit (Boolean) - defaults to true
// - compressFiles (Boolean) - defaults to true
// - shareVirtualSystem (Boolean) - defaults to false
// - mapExecutableWithTemporaryFile (Boolean) - defaults to true
// - allowRunningOfVirtualExeFiles (Boolean) - defaults to true
module.exports = function generate(projectName, inputExe, outputExe, path2Pack, options) {
// Merge options with defaults
options = mergeOptions({
filter: defaultFilter,
templatePath: {
project: DEFAULT_TEMPLATE_PATH.PROJECT,
dir: DEFAULT_TEMPLATE_PATH.DIR,
file: DEFAULT_TEMPLATE_PATH.FILE
},
evbOptions: {
deleteExtractedOnExit: true,
compressFiles: true,
shareVirtualSystem: false,
mapExecutableWithTemporaryFile: true,
allowRunningOfVirtualExeFiles: true
}
}, options);
var templatePath = options.templatePath;
var evbOptions = options.evbOptions;
// Load templates
var projectTemplate = loadTemplate(templatePath.project);
var dirTemplate = loadTemplate(templatePath.dir);
var fileTemplate = loadTemplate(templatePath.file);
// Fill the project template
projectTemplate = projectTemplate.
// Set input and output executables
replace(RE.INPUT_EXE, resolvePath(inputExe)).
replace(RE.OUTPUT_EXE, resolvePath(outputExe)).
// Set options
replace(RE.OPT_DELETE_EXTRACTED, Boolean(evbOptions.deleteExtractedOnExit).toString()).
replace(RE.OPT_COMPRESS_FILES, Boolean(evbOptions.compressFiles).toString()).
replace(RE.OPT_SHARE_VIRTUAL_SYSTEM, Boolean(evbOptions.shareVirtualSystem).toString()).
replace(RE.OPT_MAP_WITH_TEMP, Boolean(evbOptions.mapExecutableWithTemporaryFile).toString()).
replace(RE.OPT_ALLOW_RUNNING_VIRTUAL_EXE, Boolean(evbOptions.allowRunningOfVirtualExeFiles).toString()).
// Add files
replace(RE.FILES, generateDirTreeXml(resolvePath(path2Pack), dirTemplate, fileTemplate, options.filter));
// Save the project to file
// Note: When you create a project manually using Enigma's GUI it prepends BOM (byte order mark) to the file.
// fs.writeFile doesn't do that, but it doesn't seem to cause any issue with Enigma. If an issue related to the
// missing BOM arises, we can add it by prepending '\ufeff' to projectTemplate (for details see:
// http://stackoverflow.com/a/27975629)
fs.writeFileSync(resolvePath(projectName), projectTemplate, 'ucs2');
};