@makano/rew 1.1.2 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
package/bin/ui CHANGED
Binary file
package/build.sh ADDED
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+ opath=./bin/ui
3
+ if [ $1 ]; then
4
+ opath=$1
5
+ fi
6
+ g++ ./cpp/ui.cpp -o $opath `pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0 libwebsockets jsoncpp`
package/cpp/ui.cpp ADDED
@@ -0,0 +1,217 @@
1
+ #include <gtk/gtk.h>
2
+ #include <webkit2/webkit2.h>
3
+ #include <json/json.h>
4
+ #include <iostream>
5
+ #include <sstream>
6
+ #include <string>
7
+ #include <fstream>
8
+ #include <thread>
9
+ #include <sys/inotify.h>
10
+ #include <unistd.h>
11
+ #include <vector>
12
+
13
+ struct AppData
14
+ {
15
+ GtkWidget *window;
16
+ WebKitWebView *web_view;
17
+ WebKitUserContentManager *content_manager;
18
+ };
19
+
20
+ static void handle_json_message(const Json::Value &json, AppData *user_data)
21
+ {
22
+ if (json.isMember("action") && json.isMember("data"))
23
+ {
24
+ std::string action = json["action"].asString();
25
+ std::string data = json["data"].asString();
26
+
27
+ // std::cout << action << std::endl;
28
+
29
+ if (action == "setTitle")
30
+ {
31
+ gtk_window_set_title(GTK_WINDOW(user_data->window), data.c_str());
32
+ }
33
+ else if (action == "log")
34
+ {
35
+ g_print("%s\n", data.c_str());
36
+ }
37
+ else if (action == "HTML")
38
+ {
39
+ webkit_web_view_load_html(user_data->web_view, data.c_str(), NULL);
40
+ g_print("SETUP::HTML");
41
+ }
42
+ else if (action == "JS")
43
+ {
44
+ webkit_web_view_run_javascript(user_data->web_view, data.c_str(), NULL, NULL, NULL);
45
+ }
46
+ else if (action == "JS2")
47
+ {
48
+ const char *js_code = g_strdup_printf("%s;", data.c_str());
49
+ WebKitUserScript *user_script = webkit_user_script_new(js_code,
50
+ WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
51
+ WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
52
+ NULL, NULL);
53
+ webkit_user_content_manager_add_script(user_data->content_manager, user_script);
54
+ }
55
+ }
56
+ else
57
+ {
58
+ g_print("Invalid JSON format: 'action' and 'data' fields are required\n");
59
+ }
60
+ }
61
+
62
+ static void js_callback(WebKitUserContentManager *manager,
63
+ WebKitJavascriptResult *js_result,
64
+ gpointer user_data)
65
+ {
66
+ JSCValue *value = webkit_javascript_result_get_js_value(js_result);
67
+ if (jsc_value_is_string(value))
68
+ {
69
+ char *str_value = jsc_value_to_string(value);
70
+
71
+ Json::Value root;
72
+ Json::CharReaderBuilder builder;
73
+ std::string errors;
74
+ std::istringstream json_stream(str_value);
75
+ bool success = Json::parseFromStream(builder, json_stream, &root, &errors);
76
+
77
+ if (success)
78
+ {
79
+ handle_json_message(root, static_cast<AppData *>(user_data));
80
+ }
81
+ else
82
+ {
83
+ g_print("%s", str_value);
84
+ }
85
+
86
+ g_free(str_value);
87
+ }
88
+ }
89
+
90
+ void watch_file(const std::string &file_path, AppData *app_data)
91
+ {
92
+ int inotify_fd = inotify_init();
93
+ if (inotify_fd < 0)
94
+ {
95
+ perror("inotify_init");
96
+ return;
97
+ }
98
+
99
+ int watch_fd = inotify_add_watch(inotify_fd, file_path.c_str(), IN_MODIFY);
100
+ if (watch_fd < 0)
101
+ {
102
+ perror("inotify_add_watch");
103
+ close(inotify_fd);
104
+ return;
105
+ }
106
+
107
+ const size_t event_size = sizeof(struct inotify_event);
108
+ const size_t buf_len = 1024 * (event_size + 16);
109
+ std::vector<char> buffer(buf_len);
110
+
111
+ while (true)
112
+ {
113
+ int length = read(inotify_fd, buffer.data(), buf_len);
114
+ if (length < 0)
115
+ {
116
+ perror("read");
117
+ break;
118
+ }
119
+
120
+ for (int i = 0; i < length;)
121
+ {
122
+ struct inotify_event *event = reinterpret_cast<struct inotify_event *>(&buffer[i]);
123
+ if (event->mask & IN_MODIFY)
124
+ {
125
+ std::ifstream file(file_path);
126
+ std::stringstream buffer;
127
+ buffer << file.rdbuf();
128
+ std::string content = buffer.str();
129
+ file.close();
130
+
131
+ if (!content.empty())
132
+ {
133
+ Json::Value root;
134
+ Json::CharReaderBuilder builder;
135
+ std::string errors;
136
+ std::istringstream json_stream(content);
137
+ bool success = Json::parseFromStream(builder, json_stream, &root, &errors);
138
+
139
+ // std::cout << "CONTENT DETECTED" << std::endl;
140
+
141
+ if (success)
142
+ {
143
+ handle_json_message(root, app_data);
144
+ }
145
+ else
146
+ {
147
+ g_print("Failed to parse JSON string: %s\n", errors.c_str());
148
+ }
149
+
150
+ std::ofstream clear_file(file_path, std::ofstream::out | std::ofstream::trunc);
151
+ clear_file.close();
152
+ }
153
+ }
154
+ i += event_size + event->len;
155
+ }
156
+ }
157
+
158
+ close(watch_fd);
159
+ close(inotify_fd);
160
+ }
161
+
162
+ int main(int argc, char **argv)
163
+ {
164
+ if (argc != 2)
165
+ {
166
+ g_print("Usage: %s <RUNID>\n", argv[0]);
167
+ return 1;
168
+ }
169
+
170
+ const char *rid = argv[1];
171
+ std::string file_path = "/tmp/" + std::string(rid) + ".ruw.ui.socket";
172
+
173
+ AppData app_data;
174
+
175
+ gtk_init(&argc, &argv);
176
+
177
+ GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
178
+ gtk_window_set_title(GTK_WINDOW(window), "WebKit Example");
179
+ gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
180
+
181
+ app_data.window = window;
182
+
183
+ WebKitUserContentManager *content_manager = webkit_user_content_manager_new();
184
+ webkit_user_content_manager_register_script_message_handler(content_manager, "external");
185
+ g_signal_connect(content_manager, "script-message-received::external", G_CALLBACK(js_callback), &app_data);
186
+ app_data.content_manager = content_manager;
187
+
188
+ const char *js_code = g_strdup_printf("window.RUNID = \"%s\";", rid);
189
+ WebKitUserScript *user_script = webkit_user_script_new(js_code,
190
+ WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
191
+ WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
192
+ NULL, NULL);
193
+ webkit_user_content_manager_add_script(content_manager, user_script);
194
+
195
+ WebKitWebView *web_view = WEBKIT_WEB_VIEW(webkit_web_view_new_with_user_content_manager(content_manager));
196
+ gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(web_view));
197
+ app_data.web_view = web_view;
198
+
199
+ webkit_web_view_load_html(web_view, "Loading...", NULL);
200
+
201
+ GIOChannel *channel = g_io_channel_unix_new(fileno(stdin));
202
+ GIOFlags flags = static_cast<GIOFlags>(g_io_channel_get_flags(channel) | G_IO_FLAG_NONBLOCK);
203
+ g_io_channel_set_flags(channel, flags, NULL);
204
+
205
+ g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
206
+
207
+ std::thread watch_thread(watch_file, file_path, &app_data);
208
+
209
+ g_print("INIT::READY");
210
+
211
+ gtk_widget_show_all(window);
212
+ gtk_main();
213
+
214
+ watch_thread.detach();
215
+
216
+ return 0;
217
+ }
package/cpp/ui1.cpp ADDED
@@ -0,0 +1,101 @@
1
+ #include <gtk/gtk.h>
2
+ #include <webkit2/webkit2.h>
3
+ #include <json/json.h>
4
+
5
+ struct AppData {
6
+ const char* url;
7
+ GtkWidget* window;
8
+ };
9
+
10
+ static void handle_json_message(const Json::Value& json, AppData* user_data) {
11
+ // Check if the JSON object has the "action" and "data" fields
12
+ if (json.isMember("action") && json.isMember("data")) {
13
+ std::string action = json["action"].asString();
14
+ std::string data = json["data"].asString();
15
+
16
+ // Perform actions based on the received data
17
+ if (action == "setTitle") {
18
+ // Set the GTK window's title
19
+ gtk_window_set_title(GTK_WINDOW(user_data->window), data.c_str());
20
+ } else if (action == "log") {
21
+ // Set the GTK window's title
22
+ g_print("%s\n", data.c_str());
23
+ } else {
24
+ // Handle other actions as needed
25
+ }
26
+ } else {
27
+ // Invalid JSON format
28
+ g_print("Invalid JSON format: 'action' and 'data' fields are required\n");
29
+ }
30
+ }
31
+
32
+ static void js_callback(WebKitUserContentManager* manager,
33
+ WebKitJavascriptResult* js_result,
34
+ gpointer user_data) {
35
+ JSCValue* value = webkit_javascript_result_get_js_value(js_result);
36
+ if (jsc_value_is_string(value)) {
37
+ char* str_value = jsc_value_to_string(value);
38
+
39
+ Json::Value root;
40
+ Json::CharReaderBuilder builder;
41
+ std::string errors;
42
+ std::istringstream json_stream(str_value);
43
+ bool success = Json::parseFromStream(builder, json_stream, &root, &errors);
44
+
45
+ if (success) {
46
+ // Handle the incoming JSON message
47
+ handle_json_message(root, static_cast<AppData*>(user_data));
48
+ } else {
49
+ g_print("%s", str_value);
50
+ // g_print("Failed to parse JSON string: %s\n", errors.c_str());
51
+ }
52
+
53
+ g_free(str_value);
54
+ }
55
+ }
56
+
57
+
58
+ int main(int argc, char** argv) {
59
+ if (argc != 3) {
60
+ g_print("Usage: %s <URL> <RUNID>\n", argv[0]);
61
+ return 1;
62
+ }
63
+
64
+ const char* url = argv[1];
65
+ const char* rid = argv[2];
66
+
67
+ AppData app_data;
68
+ app_data.url = url;
69
+
70
+ gtk_init(&argc, &argv);
71
+
72
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
73
+ gtk_window_set_title(GTK_WINDOW(window), "WebKit Example");
74
+ gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
75
+
76
+ app_data.window = window;
77
+
78
+ WebKitUserContentManager* content_manager = webkit_user_content_manager_new();
79
+ webkit_user_content_manager_register_script_message_handler(content_manager, "external");
80
+ g_signal_connect(content_manager, "script-message-received::external", G_CALLBACK(js_callback), &app_data);
81
+
82
+ const char* js_code = g_strdup_printf("window.RUNID = \"%s\";", rid);
83
+ WebKitUserScript* user_script = webkit_user_script_new(js_code,
84
+ WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
85
+ WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
86
+ NULL, NULL);
87
+ webkit_user_content_manager_add_script(content_manager, user_script);
88
+
89
+ WebKitWebView* web_view = WEBKIT_WEB_VIEW(webkit_web_view_new_with_user_content_manager(content_manager));
90
+ gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(web_view));
91
+
92
+ webkit_web_view_load_uri(web_view, url);
93
+
94
+ g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
95
+
96
+ gtk_widget_show_all(window);
97
+
98
+ gtk_main();
99
+
100
+ return 0;
101
+ }
package/cpp/ui2.cpp ADDED
@@ -0,0 +1,105 @@
1
+ #include <gtk/gtk.h>
2
+ #include <libwebsockets.h>
3
+ #include <json/json.h>
4
+ #include <iostream>
5
+ #include <string>
6
+ #include <map>
7
+
8
+ std::map<std::string, GtkWidget *> widgets;
9
+
10
+ static void on_button_clicked(GtkWidget *widget, gpointer data)
11
+ {
12
+ // Send event back to Node.js server
13
+ std::string id = static_cast<char *>(data);
14
+ std::string message = "{\"event\": \"buttonClicked\", \"id\": \"" + id + "\"}";
15
+ lws_write(static_cast<lws *>(data), (unsigned char *)message.c_str(), message.length(), LWS_WRITE_TEXT);
16
+ }
17
+
18
+ static int callback_websocket(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len)
19
+ {
20
+ switch (reason)
21
+ {
22
+ case LWS_CALLBACK_CLIENT_ESTABLISHED:
23
+ std::cout << "Connected to server" << std::endl;
24
+ break;
25
+ case LWS_CALLBACK_CLIENT_RECEIVE:
26
+ {
27
+ std::string message(static_cast<char *>(in), len);
28
+ Json::CharReaderBuilder rbuilder;
29
+ Json::Value root;
30
+ std::string errors;
31
+
32
+ std::istringstream s(message);
33
+ std::string doc;
34
+ std::getline(s, doc);
35
+ std::istringstream ss(doc);
36
+ if (Json::parseFromStream(rbuilder, ss, &root, &errors))
37
+ {
38
+ std::string action = root["action"].asString();
39
+
40
+ if (action == "createButton")
41
+ {
42
+ GtkWidget *button = gtk_button_new_with_label(root["label"].asString().c_str());
43
+ g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), strdup(root["id"].asString().c_str()));
44
+ widgets[root["id"].asString()] = button;
45
+ gtk_container_add(GTK_CONTAINER(gtk_window_new(GTK_WINDOW_TOPLEVEL)), button);
46
+ gtk_widget_show_all(button);
47
+ }
48
+ }
49
+ break;
50
+ }
51
+ case LWS_CALLBACK_CLIENT_CLOSED:
52
+ std::cout << "Disconnected from server" << std::endl;
53
+ break;
54
+ default:
55
+ break;
56
+ }
57
+ return 0;
58
+ }
59
+
60
+ static const struct lws_protocols protocols[] = {
61
+ {
62
+ "example-protocol",
63
+ callback_websocket,
64
+ 0,
65
+ 1024,
66
+ },
67
+ {NULL, NULL, 0, 0}};
68
+
69
+ int main(int argc, char **argv)
70
+ {
71
+ std::cout << "Starting" << std::endl;
72
+ if (argc != 2)
73
+ {
74
+ std::cerr << "Usage: " << argv[0] << " <WebSocket server URI>" << std::endl;
75
+ return 1;
76
+ }
77
+
78
+ gtk_init(&argc, &argv);
79
+
80
+ struct lws_context_creation_info info;
81
+ memset(&info, 0, sizeof info);
82
+ info.port = CONTEXT_PORT_NO_LISTEN;
83
+ info.protocols = protocols;
84
+
85
+ struct lws_context *context = lws_create_context(&info);
86
+
87
+ struct lws_client_connect_info ccinfo = {0};
88
+ ccinfo.context = context;
89
+ ccinfo.address = argv[1];
90
+ ccinfo.port = 8080;
91
+ ccinfo.path = "/";
92
+ ccinfo.host = lws_canonical_hostname(context);
93
+ ccinfo.origin = "origin";
94
+ ccinfo.protocol = protocols[0].name;
95
+
96
+ struct lws *wsi = lws_client_connect_via_info(&ccinfo);
97
+
98
+ while (lws_service(context, 1000) >= 0)
99
+ {
100
+ gtk_main_iteration_do(false);
101
+ }
102
+
103
+ lws_context_destroy(context);
104
+ return 0;
105
+ }
@@ -3,11 +3,12 @@
3
3
  const yargs = require('yargs/yargs');
4
4
  const path = require('path');
5
5
  const { hideBin } = require('yargs/helpers');
6
- const { fork, exec } = require('child_process');
6
+ const { fork, exec, execSync } = require('child_process');
7
7
  const { watch } = require('chokidar');
8
8
  const utils = require('./utils');
9
- const { existsSync } = require('fs');
9
+ const { existsSync, readFileSync, writeFileSync, mkdirSync } = require('fs');
10
10
  const { log } = require('./log');
11
+ const { compileFile, compile } = require('../modules/compiler');
11
12
 
12
13
  yargs(hideBin(process.argv))
13
14
  .command(
@@ -97,6 +98,17 @@ yargs(hideBin(process.argv))
97
98
  utils.createProject(argv.path);
98
99
  }
99
100
  )
101
+ .command('ui-bin <path>', 'Build the UI bin for your own app', (yargs) => {
102
+ yargs
103
+ .positional('path', {
104
+ describe: 'Path of the output bin',
105
+ type: 'string',
106
+ });
107
+ },
108
+ (argv) => {
109
+ execSync('sh '+path.resolve(__dirname, '../../../build.sh')+' '+argv.path);
110
+ }
111
+ )
100
112
  .command('run <path | package>', 'Run an app', (yargs) => {
101
113
  yargs
102
114
  .positional('path', {
@@ -125,9 +137,69 @@ yargs(hideBin(process.argv))
125
137
  .positional('file', {
126
138
  describe: 'File to build',
127
139
  type: 'string',
140
+ })
141
+ .option('output', {
142
+ alias: 'o',
143
+ describe: 'Output directory',
144
+ type: 'string',
128
145
  });
129
146
  }, (argv) => {
130
- console.log(`Building file: ${argv.file}`);
147
+
148
+ function readFile(filePath) {
149
+ return readFileSync(filePath, { encoding: 'utf-8' });
150
+ }
151
+
152
+ function extractImports(content) {
153
+ const importRegex = /(\w+)\s*=\s*imp\s*['"](.+?)['"]/g;
154
+ const imports = [];
155
+ let match;
156
+ while ((match = importRegex.exec(content)) !== null) {
157
+ imports.push({ variable: match[1], url: match[2] });
158
+ }
159
+ return imports;
160
+ }
161
+
162
+ function writeCompiledFile(filePath, compiledCode) {
163
+ const dirName = outputDir ? outputDir : path.dirname(filePath);
164
+ if(!existsSync(dirName)) mkdirSync(dirName, { recursive: true });
165
+ const baseName = path.basename(filePath, path.extname(filePath));
166
+ const newFilePath = path.join(dirName, `${baseName}.js`);
167
+ writeFileSync(newFilePath, compiledCode, { encoding: 'utf-8' });
168
+ log(`Compiled: ${newFilePath}`);
169
+ }
170
+
171
+ function processFile(filePath, importsArray) {
172
+ const content = readFile(filePath);
173
+ const imports = extractImports(content);
174
+
175
+ imports.forEach(importStatement => {
176
+ const importedFilePath = path.resolve(path.dirname(filePath), importStatement.url);
177
+ if (!importsArray.some(importObj => importObj.url === importStatement.url)) {
178
+
179
+ if(existsSync(importedFilePath)){
180
+ importsArray.push(importStatement);
181
+ processFile(importedFilePath, importsArray);
182
+ } else if(existsSync(importedFilePath+'.coffee')){
183
+ importsArray.push(importStatement);
184
+ processFile(importedFilePath+'.coffee', importsArray);
185
+ } else if(existsSync(importedFilePath+'.js')){
186
+ importsArray.push(importStatement);
187
+ processFile(importedFilePath+'.js', importsArray);
188
+ }
189
+
190
+ }
191
+ });
192
+
193
+ const compiled = compile({ content }, {});
194
+ writeCompiledFile(filePath, compiled);
195
+ }
196
+
197
+ const filePath = path.resolve(process.cwd(), argv.file);
198
+ const importsArray = [];
199
+ const outputDir = argv.output ? path.resolve(process.cwd(), argv.output) : null;
200
+ log('Start compile at', outputDir || 'default path');
201
+ processFile(filePath, importsArray);
202
+ log('Compiled', importsArray.length + 1, 'files.', ':end');
131
203
  })
132
204
  .help()
133
205
  .argv;
@@ -72,15 +72,16 @@ module.exports = {
72
72
  fs.writeFileSync(path.join(projectPath, '.gitignore'), `node_modules/\npackage-lock.json`);
73
73
  execSync('cd '+projectPath+' && git init .');
74
74
  }
75
- log('Installing '+npm_package_name);
76
- exec('cd '+projectPath+' && npm i '+npm_package_name, (err) => {
77
- if(err){
78
- console.error(err);
79
- process.exit(0);
80
- } else {
81
- rl.close();
82
- }
83
- });
75
+ // log('Installing '+npm_package_name);
76
+ // exec('cd '+projectPath+' && npm i '+npm_package_name, (err) => {
77
+ // if(err){
78
+ // console.error(err);
79
+ // process.exit(0);
80
+ // } else {
81
+ // rl.close();
82
+ // }
83
+ // });
84
+ log('Done.', ':end');
84
85
  }
85
86
  if (!fs.existsSync(projectPath)) {
86
87
  rl.question(logget('Package Name: '), (pkg) => {
@@ -2,7 +2,8 @@ const execOptions = {
2
2
  sharedContext: true,
3
3
  resolveExtensions: [{ext: '.js', options: { type: 'js' }}, '.coffee'],
4
4
  nativeRequire: false,
5
- cwdAlias: '$'
5
+ cwdAlias: '$',
6
+ jsxPragma: 'createElement'
6
7
  }
7
8
 
8
9
  module.exports.execOptions = execOptions;
@@ -0,0 +1,27 @@
1
+ const shell = require('child_process');
2
+
3
+
4
+ module.exports = (currentFile) => {
5
+
6
+ function exec(command, options){
7
+ return shell.execSync(command, { stdio: options?.output == false ? null : 'inherit' });
8
+ }
9
+
10
+ exec.background = function execAsync(command, options, callback){
11
+ if(typeof options == "function" && !callback){
12
+ callback = options;
13
+ options = {};
14
+ }
15
+ if(!options) options = {};
16
+ if(!callback) callback = () => {};
17
+ return shell.exec(command, {
18
+ ...options,
19
+ }, callback);
20
+ }
21
+
22
+ function spawn(command, options){
23
+ return shell.spawn(command, options);
24
+ }
25
+
26
+ return { exec, spawn };
27
+ }
@@ -1,9 +1,27 @@
1
+
2
+ function exportsThe(item, name, context){
3
+ if (name) {
4
+ if(!context.module.exports) context.module.exports = {};
5
+ context.module.exports[name] = item;
6
+ } else {
7
+ if(context.module.exports) context.module.exports.default = item;
8
+ else context.module.exports = item;
9
+ }
10
+ }
11
+
12
+ module.exports.pubFunction = function (context) {
13
+ return function (name, item) {
14
+ if(name && !item){
15
+ item = name;
16
+ name = null;
17
+ }
18
+ exportsThe(item, name, context);
19
+ };
20
+ };
21
+
1
22
  module.exports.exportsFunction = function (context) {
2
23
  return function (item, name) {
3
- if (name) {
4
- context.module.exports[name] = item;
5
- } else {
6
- context.module.exports = item;
7
- }
24
+ exportsThe(item, name, context);
8
25
  };
9
26
  };
27
+
@@ -13,12 +13,16 @@ module.exports = (currentFile) => {
13
13
  return fs.readFileSync(gp(filepath), options);
14
14
  }
15
15
 
16
+ function realpath(filepath, options = { encoding: 'utf-8' }){
17
+ return gp(filepath);
18
+ }
19
+
16
20
  function write(filepath, content, options){
17
21
  return fs.writeFileSync(gp(filepath), content, options);
18
22
  }
19
23
 
20
24
  function exists(filepath, options){
21
- return fs.existsSync(filepath);
25
+ return fs.existsSync(gp(filepath));
22
26
  }
23
27
 
24
28
  function fstat(filepath, options){
@@ -49,6 +53,7 @@ module.exports = (currentFile) => {
49
53
  fstat,
50
54
  exists,
51
55
  write,
52
- read
56
+ read,
57
+ realpath
53
58
  };
54
59
  }