@webqit/webflo 0.11.33 → 0.11.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "vanila-javascript"
13
13
  ],
14
14
  "homepage": "https://webqit.io/tooling/webflo",
15
- "version": "0.11.33",
15
+ "version": "0.11.35",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -46,15 +46,21 @@ export default class Virtualization extends Dotfile {
46
46
  initial: config.entries,
47
47
  questions: [
48
48
  {
49
- name: 'host',
49
+ name: 'path',
50
50
  type: 'text',
51
- message: 'Enter Host name',
51
+ message: 'Enter local pathname to target server if exists. (Leave empty to explicitly specify hostnames and port number.)',
52
52
  validation: ['important'],
53
53
  },
54
54
  {
55
- name: 'path',
55
+ name: 'hostnames',
56
+ type: 'text',
57
+ message: 'Enter host names. (Leave empty to automatically derive hostnames from the config of the target server specified above.)',
58
+ validation: ['important'],
59
+ },
60
+ {
61
+ name: 'port',
56
62
  type: 'text',
57
- message: 'Enter local path',
63
+ message: 'Enter target port. (Leave empty to automatically derive port number from the config of the target server specified above.)',
58
64
  validation: ['important'],
59
65
  },
60
66
  ],
@@ -21,16 +21,16 @@ export default class Server extends Dotfile {
21
21
  withDefaults(config) {
22
22
  return _merge(true, {
23
23
  port: process.env.port || 3000,
24
+ domains: [],
25
+ force_www: '',
24
26
  https: {
25
27
  port: 0,
26
28
  keyfile: '',
27
29
  certfile: '',
28
- certdoms: ['*'],
30
+ domains: [],
29
31
  force: false,
30
32
  },
31
- force_www: '',
32
33
  oohtml_support: 'full',
33
- shared: false,
34
34
  }, config);
35
35
  }
36
36
 
@@ -60,6 +60,19 @@ export default class Server extends Dotfile {
60
60
  initial: config.port,
61
61
  validation: ['important'],
62
62
  },
63
+ {
64
+ name: 'domains',
65
+ type: 'list',
66
+ message: '[domains]: Enter a list of allowed domains if necessary (comma-separated)',
67
+ validation: ['important'],
68
+ },
69
+ {
70
+ name: 'force_www',
71
+ type: 'select',
72
+ message: '[force_www]: Force add/remove "www" on hostname?',
73
+ choices: CHOICES.force_www,
74
+ initial: this.indexOfInitial(CHOICES.force_www, config.force_www),
75
+ },
63
76
  {
64
77
  name: 'https',
65
78
  controls: {
@@ -86,9 +99,9 @@ export default class Server extends Dotfile {
86
99
  validation: ['important'],
87
100
  },
88
101
  {
89
- name: 'certdoms',
102
+ name: 'domains',
90
103
  type: 'list',
91
- message: '[certdoms]: Enter the CERT domains (comma-separated)',
104
+ message: '[domains]: Enter the CERT domains (comma-separated)',
92
105
  validation: ['important'],
93
106
  },
94
107
  {
@@ -100,13 +113,6 @@ export default class Server extends Dotfile {
100
113
  },
101
114
  ],
102
115
  },
103
- {
104
- name: 'force_www',
105
- type: 'select',
106
- message: '[force_www]: Force add/remove "www" on hostname?',
107
- choices: CHOICES.force_www,
108
- initial: this.indexOfInitial(CHOICES.force_www, config.force_www),
109
- },
110
116
  {
111
117
  name: 'oohtml_support',
112
118
  type: 'select',
@@ -115,14 +121,6 @@ export default class Server extends Dotfile {
115
121
  initial: this.indexOfInitial(CHOICES.oohtml_support, config.oohtml_support),
116
122
  validation: ['important'],
117
123
  },
118
- {
119
- name: 'shared',
120
- type: 'toggle',
121
- message: '[shared]: Shared server?',
122
- active: 'YES',
123
- inactive: 'NO',
124
- initial: config.shared,
125
- },
126
124
  ];
127
125
  }
128
126
  }
@@ -22,13 +22,13 @@ export default class Router {
22
22
  *
23
23
  * @return void
24
24
  */
25
- constructor(cx, path) {
25
+ constructor(cx, path = []) {
26
26
  this.cx = cx;
27
27
  this.path = _isArray(path) ? path : (path + '').split('/').filter(a => a);
28
28
  }
29
29
 
30
30
  /**
31
- * Performs dynamic routing.
31
+ * Performs dynamic routing
32
32
  *
33
33
  * @param array|string method
34
34
  * @param Object event
@@ -59,7 +59,7 @@ export default class Router {
59
59
  // Broadcast any hints exported by handler
60
60
  if (thisTick.exports.hints) { await event.port.post({ ...thisTick.exports.hints, $type: 'handler:hints' }); }
61
61
  const methods = _arrFrom(thisTick.method);
62
- const handler = _isFunction(thisTick.exports) && methods.includes('default') ? thisTick.exports : methods.reduce((_handler, name) => _handler || thisTick.exports[name.toLowerCase()], null);
62
+ const handler = _isFunction(thisTick.exports) && methods.includes('default') ? thisTick.exports : methods.reduce((_handler, name) => _handler || thisTick.exports[name], null);
63
63
  if (handler) {
64
64
  // -------------
65
65
  // Dynamic response
@@ -0,0 +1,21 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+
6
+ import { _isFunction } from "@webqit/util/js/index.js";
7
+
8
+ /**
9
+ * ---------------------------
10
+ * The base Runtime class
11
+ * ---------------------------
12
+ */
13
+
14
+ export default class Runtime {
15
+ constructor(cx, client) {
16
+ this.cx = cx;
17
+ this.cx.runtime = this;
18
+ this.client = _isFunction(client) ? client(this.cx) : client;
19
+ if (!this.client || !this.client.handle) throw new Error(`Application instance must define a ".handle()" method.`);
20
+ }
21
+ }
@@ -0,0 +1,29 @@
1
+
2
+ /**
3
+ * ---------------------------
4
+ * The base RuntimeClient class
5
+ * ---------------------------
6
+ */
7
+
8
+ export default class RuntimeClient {
9
+
10
+ constructor(cx) {
11
+ this.cx = cx;
12
+ }
13
+
14
+ /**
15
+ * Initializes application itself.
16
+ *
17
+ * @param HttpEvent httpEvent
18
+ * @param Function remoteFetch
19
+ *
20
+ * @return Boolean|undefined
21
+ */
22
+ async init(httpEvent, remoteFetch) {
23
+ // The app router
24
+ const router = new this.Router(this.cx, '/');
25
+ return router.route(['init'], httpEvent, {}, async event => {
26
+ }, remoteFetch);
27
+ }
28
+
29
+ }
@@ -17,6 +17,7 @@ import xResponse from "../xResponse.js";
17
17
  import xfetch from '../xfetch.js';
18
18
  import xHttpEvent from '../xHttpEvent.js';
19
19
  import Workport from './Workport.js';
20
+ import _Runtime from '../Runtime.js';
20
21
 
21
22
  const URL = xURL(whatwag.URL);
22
23
  const FormData = xFormData(whatwag.FormData);
@@ -41,7 +42,7 @@ export {
41
42
  Observer,
42
43
  }
43
44
 
44
- export default class Runtime {
45
+ export default class Runtime extends _Runtime {
45
46
 
46
47
  /**
47
48
  * Runtime
@@ -52,15 +53,7 @@ export default class Runtime {
52
53
  * @return void
53
54
  */
54
55
  constructor(cx, clientCallback) {
55
- // ---------------
56
- this.cx = cx;
57
- this.clients = new Map;
58
- // ---------------
59
- this.cx.runtime = this;
60
- let client = clientCallback(this.cx, '*');
61
- if (!client || !client.handle) throw new Error(`Application instance must define a ".handle()" method.`);
62
- this.clients.set('*', client);
63
-
56
+ super(cx, clientCallback);
64
57
  // -----------------------
65
58
  // Initialize location
66
59
  Observer.set(this, 'location', new Url(window.document.location));
@@ -157,27 +150,24 @@ export default class Runtime {
157
150
  // -----------------------
158
151
  // Service Worker && COMM
159
152
  if (this.cx.params.service_worker_support) {
160
- let workport = new Workport(this.cx.params.worker_filename, { scope: this.cx.params.worker_scope, startMessages: true });
153
+ const workport = new Workport(this.cx.params.worker_filename, { scope: this.cx.params.worker_scope, startMessages: true });
161
154
  Observer.set(this, 'workport', workport);
162
- workport.messaging.listen(async evt => {
163
- let responsePort = evt.ports[0];
164
- let client = this.clients.get('*');
165
- let response = client.alert && await client.alert(evt);
166
- if (responsePort) {
167
- if (response instanceof Promise) {
168
- response.then(data => {
169
- responsePort.postMessage(data);
170
- });
171
- } else {
172
- responsePort.postMessage(response);
173
- }
174
- }
175
- });
176
155
  }
177
156
 
178
- // ---------------
179
- this.go(this.location, {}, { srcType: 'init' });
180
- // ---------------
157
+ // -----------------------
158
+ // Initialize and Hydration
159
+ (async () => {
160
+ let shouldHydrate;
161
+ if (this.client.init) {
162
+ const request = this.generateRequest(this.location);
163
+ const httpEvent = new HttpEvent(request, { srcType: 'initialization' }, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
164
+ shouldHydrate = await this.client.init(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
165
+ }
166
+ if (shouldHydrate !== false) {
167
+ this.go(this.location, {}, { srcType: 'hydration' });
168
+ }
169
+ })();
170
+
181
171
  }
182
172
 
183
173
  /**
@@ -211,9 +201,9 @@ export default class Runtime {
211
201
  }
212
202
 
213
203
  // Generates request object
214
- generateRequest(href, init) {
204
+ generateRequest(href, init = {}) {
215
205
  return new Request(href, {
216
- signal: this._abortController.signal,
206
+ signal: this._abortController && this._abortController.signal,
217
207
  ...init,
218
208
  headers: {
219
209
  'Accept': 'application/json',
@@ -245,7 +235,7 @@ export default class Runtime {
245
235
  // ------------
246
236
  // Put his forward before instantiating a request and aborting previous
247
237
  // Same-page hash-links clicks on chrome recurse here from histroy popstate
248
- if (detail.srcType !== 'init' && (_before(url.href, '#') === _before(init.referrer, '#') && (init.method || 'GET').toUpperCase() === 'GET')) {
238
+ if (detail.srcType !== 'hydration' && (_before(url.href, '#') === _before(init.referrer, '#') && (init.method || 'GET').toUpperCase() === 'GET')) {
249
239
  return new Response(null, { status: 304 }); // Not Modified
250
240
  }
251
241
  // ------------
@@ -266,17 +256,14 @@ export default class Runtime {
266
256
  // ------------
267
257
  // Run
268
258
  // ------------
269
- // The request object
270
- let request = this.generateRequest(url.href, init);
271
- // The navigation event
272
- let httpEvent = new HttpEvent(request, detail, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
273
- // Response
274
- let client = this.clients.get('*'), response, finalResponse;
259
+ const request = this.generateRequest(url.href, init);
260
+ const httpEvent = new HttpEvent(request, detail, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
261
+ let response, finalResponse;
275
262
  try {
276
263
  // ------------
277
264
  // Response
278
265
  // ------------
279
- response = await client.handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
266
+ response = await this.client.handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
280
267
  finalResponse = this.handleResponse(httpEvent, response);
281
268
  // ------------
282
269
  // Address bar
@@ -299,12 +286,14 @@ export default class Runtime {
299
286
  // Rendering
300
287
  // ------------
301
288
  if (finalResponse.ok && (finalResponse.headers.contentType === 'application/json' || finalResponse.headers.contentType.startsWith('multipart/form-data'))) {
302
- client.render && await client.render(httpEvent, finalResponse);
289
+ this.client.render && await this.client.render(httpEvent, finalResponse);
303
290
  } else if (!finalResponse.ok) {
304
291
  if ([404, 500].includes(finalResponse.status)) {
305
292
  Observer.set(this.network, 'error', new Error(finalResponse.statusText, { cause: finalResponse.status }));
306
293
  }
307
- client.unrender && await client.unrender(httpEvent);
294
+ if (!finalResponse.headers.get('Location')) {
295
+ this.client.unrender && await this.client.unrender(httpEvent);
296
+ }
308
297
  }
309
298
  } catch(e) {
310
299
  console.error(e);
@@ -321,7 +310,7 @@ export default class Runtime {
321
310
  let href = request;
322
311
  if (request instanceof Request) {
323
312
  href = request.url;
324
- } else if (request instanceof self.URL) {
313
+ } else if (request instanceof whatwag.URL) {
325
314
  href = request.href;
326
315
  }
327
316
  Observer.set(this.network, 'remote', href);
@@ -3,19 +3,16 @@
3
3
  * @imports
4
4
  */
5
5
  import Router from './Router.js';
6
+ import _RuntimeClient from '../RuntimeClient.js';
6
7
 
7
- export default class RuntimeClient {
8
+ export default class RuntimeClient extends _RuntimeClient {
8
9
 
9
- /**
10
- * RuntimeClient
11
- *
12
- * @param Context cx
13
- */
14
- constructor(cx) {
15
- this.cx = cx;
10
+ // Returns router class
11
+ get Router() {
12
+ return Router;
16
13
  }
17
14
 
18
- /**
15
+ /**
19
16
  * Handles HTTP events.
20
17
  *
21
18
  * @param HttpEvent httpEvent
@@ -25,12 +22,12 @@ export default class RuntimeClient {
25
22
  */
26
23
  async handle(httpEvent, remoteFetch) {
27
24
  // The app router
28
- const router = new Router(this.cx, httpEvent.url.pathname);
25
+ const router = new this.Router(this.cx, httpEvent.url.pathname);
29
26
  const handle = async () => {
30
27
  // --------
31
28
  // ROUTE FOR DATA
32
29
  // --------
33
- let httpMethodName = httpEvent.request.method.toUpperCase();
30
+ const httpMethodName = httpEvent.request.method.toUpperCase();
34
31
  return router.route([httpMethodName, 'default'], httpEvent, {}, async event => {
35
32
  return remoteFetch(event.request);
36
33
  }, remoteFetch);
@@ -46,7 +43,7 @@ export default class RuntimeClient {
46
43
  // Renderer
47
44
  async render(httpEvent, response) {
48
45
  let data = await response.jsonfy();
49
- const router = new Router(this.cx, httpEvent.url.pathname);
46
+ const router = new this.Router(this.cx, httpEvent.url.pathname);
50
47
  return router.route('render', httpEvent, data, async (httpEvent, data) => {
51
48
  // --------
52
49
  // OOHTML would waiting for DOM-ready in order to be initialized
@@ -57,7 +54,7 @@ export default class RuntimeClient {
57
54
  if (!window.document.state.env) {
58
55
  window.document.setState({
59
56
  env: 'client',
60
- onHydration: (httpEvent.detail || {}).srcType === 'init',
57
+ onHydration: (httpEvent.detail || {}).srcType === 'hydration',
61
58
  network: this.cx.runtime.network,
62
59
  url: this.cx.runtime.location,
63
60
  }, { update: true });
@@ -9,7 +9,7 @@ import { Observer } from './Runtime.js';
9
9
  export default class Workport {
10
10
 
11
11
  constructor(file, params = {}) {
12
- this.ready = navigator.serviceWorker ? navigator.serviceWorker.ready : new Promise;
12
+ this.ready = navigator.serviceWorker ? navigator.serviceWorker.ready : new Promise(() => {});
13
13
 
14
14
  // --------
15
15
  // Registration and lifecycle
@@ -83,7 +83,19 @@ export default class Workport {
83
83
  },
84
84
  listen: callback => {
85
85
  if (navigator.serviceWorker) {
86
- navigator.serviceWorker.addEventListener('message', callback);
86
+ navigator.serviceWorker.addEventListener('message', evt => {
87
+ const response = callback(evt);
88
+ let responsePort = evt.ports[0];
89
+ if (responsePort) {
90
+ if (response instanceof Promise) {
91
+ response.then(data => {
92
+ responsePort.postMessage(data);
93
+ });
94
+ } else {
95
+ responsePort.postMessage(response);
96
+ }
97
+ }
98
+ });
87
99
  }
88
100
  return this.post;
89
101
  },
@@ -12,9 +12,7 @@ import Runtime from './Runtime.js';
12
12
  export async function start(clientCallback = null) {
13
13
  const cx = this || {};
14
14
  const defaultClientCallback = _cx => new RuntimeClient(_cx);
15
- return new Runtime(Context.create(cx), ( ...args ) => {
16
- return clientCallback ? clientCallback( ...args.concat( defaultClientCallback ) ) : defaultClientCallback( ...args );
17
- });
15
+ return new Runtime(Context.create(cx), clientCallback || defaultClientCallback);
18
16
  }
19
17
 
20
18
  /**
@@ -6,6 +6,8 @@ import { _any } from '@webqit/util/arr/index.js';
6
6
  import { urlPattern } from '../../util.js';
7
7
  import { HttpEvent, Request, Response, fetch as xfetch, Observer } from '../Runtime.js';
8
8
  import Workport from './Workport.js';
9
+ import _Worker from '../../Runtime.js';
10
+
9
11
  export {
10
12
  URL,
11
13
  FormData,
@@ -25,7 +27,7 @@ export {
25
27
  * ---------------------------
26
28
  */
27
29
 
28
- export default class Worker {
30
+ export default class Worker extends _Worker {
29
31
 
30
32
  /**
31
33
  * Runtime
@@ -36,18 +38,10 @@ export default class Worker {
36
38
  * @return void
37
39
  */
38
40
  constructor(cx, clientCallback) {
39
-
41
+ super(cx, clientCallback);
40
42
  // ---------------
41
- this.cx = cx;
42
- this.clients = new Map;
43
43
  this.mockSessionStore = {};
44
- // ---------------
45
- this.cx.runtime = this;
46
- let client = clientCallback(this.cx, '*');
47
- if (!client || !client.handle) throw new Error(`Application instance must define a ".handle()" method.`);
48
- this.clients.set('*', client);
49
-
50
- // -------------
44
+ // --------------
51
45
  // ONINSTALL
52
46
  self.addEventListener('install', evt => {
53
47
  if (this.cx.params.skip_waiting) { self.skipWaiting(); }
@@ -113,26 +107,17 @@ export default class Worker {
113
107
 
114
108
  // -------------
115
109
  // Workport
116
- let workport = new Workport();
110
+ const workport = new Workport();
117
111
  Observer.set(this, 'workport', workport);
118
- workport.messaging.listen(async evt => {
119
- let responsePort = evt.ports[0];
120
- let client = this.clients.get('*');
121
- let response = client.alert && await client.alert(evt);
122
- if (responsePort) {
123
- if (response instanceof Promise) {
124
- response.then(data => {
125
- responsePort.postMessage(data);
126
- });
127
- } else {
128
- responsePort.postMessage(response);
129
- }
130
- }
131
- });
132
- workport.push.listen(async evt => {
133
- let client = this.clients.get('*');
134
- client.alert && await client.alert(evt);
135
- });
112
+
113
+ // -------------
114
+ // Initialize
115
+ (async () => {
116
+ if (!this.client.init) return;
117
+ const request = this.generateRequest('/');
118
+ const httpEvent = new HttpEvent(request, { srcType: 'initialization' }, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
119
+ await this.client.init(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
120
+ })();
136
121
 
137
122
  }
138
123
 
@@ -151,12 +136,12 @@ export default class Worker {
151
136
  init = { referrer: this.location.href, ...init };
152
137
  // ------------
153
138
  // The request object
154
- let request = await this.generateRequest(url.href, init);
139
+ const request = await this.generateRequest(url.href, init);
155
140
  if (detail.event) {
156
141
  Object.defineProperty(detail.event, 'request', { value: request });
157
142
  }
158
143
  // The navigation event
159
- let httpEvent = new HttpEvent(request, detail, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
144
+ const httpEvent = new HttpEvent(request, detail, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
160
145
  httpEvent.port.listen(message => {
161
146
  if (message.$type === 'handler:hints' && message.session) {
162
147
  // TODO: Sync session data from client
@@ -166,18 +151,18 @@ export default class Worker {
166
151
  // Response
167
152
  let response;
168
153
  if (httpEvent.request.url.startsWith(self.origin)/* && httpEvent.request.mode === 'navigate'*/) {
169
- response = await this.clients.get('*').handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
154
+ response = await this.client.handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
170
155
  } else {
171
156
  response = await this.remoteFetch(httpEvent.request);
172
157
  }
173
- let finalResponse = this.handleResponse(httpEvent, response);
158
+ const finalResponse = this.handleResponse(httpEvent, response);
174
159
  // Return value
175
160
  return finalResponse;
176
161
  }
177
162
 
178
163
  // Generates request object
179
- generateRequest(href, init) {
180
- let request = new Request(href, init);
164
+ generateRequest(href, init = {}) {
165
+ const request = new Request(href, init);
181
166
  return request;
182
167
  }
183
168
 
@@ -3,19 +3,16 @@
3
3
  * @imports
4
4
  */
5
5
  import Router from '../Router.js';
6
+ import _WorkerClient from '../../RuntimeClient.js';
6
7
 
7
- export default class WorkerClient {
8
+ export default class RuntimeClient extends _WorkerClient {
8
9
 
9
- /**
10
- * WorkerClient
11
- *
12
- * @param Context cx
13
- */
14
- constructor(cx) {
15
- this.cx = cx;
10
+ // Returns router class
11
+ get Router() {
12
+ return Router;
16
13
  }
17
14
 
18
- /**
15
+ /**
19
16
  * Handles HTTP events.
20
17
  *
21
18
  * @param HttpEvent httpEvent
@@ -25,12 +22,12 @@ export default class WorkerClient {
25
22
  */
26
23
  async handle(httpEvent, remoteFetch) {
27
24
  // The app router
28
- const router = new Router(this.cx, httpEvent.url.pathname);
25
+ const router = new this.Router(this.cx, httpEvent.url.pathname);
29
26
  const handle = async () => {
30
27
  // --------
31
28
  // ROUTE FOR DATA
32
29
  // --------
33
- let httpMethodName = httpEvent.request.method.toUpperCase();
30
+ const httpMethodName = httpEvent.request.method.toUpperCase();
34
31
  let response = await router.route([httpMethodName, 'default'], httpEvent, {}, async event => {
35
32
  return remoteFetch(event.request);
36
33
  }, remoteFetch);
@@ -15,7 +15,17 @@ export default class Workport {
15
15
  if (!client) {
16
16
  self.addEventListener('message', evt => {
17
17
  this.client = evt.source;
18
- callback(evt);
18
+ const response = callback(evt);
19
+ let responsePort = evt.ports[0];
20
+ if (responsePort) {
21
+ if (response instanceof Promise) {
22
+ response.then(data => {
23
+ responsePort.postMessage(data);
24
+ });
25
+ } else {
26
+ responsePort.postMessage(response);
27
+ }
28
+ }
19
29
  });
20
30
  return this.post;
21
31
  }
@@ -12,9 +12,7 @@ import Worker from './Worker.js';
12
12
  export async function start(clientCallback = null) {
13
13
  const cx = this || {};
14
14
  const defaultClientCallback = _cx => new WorkerClient(_cx);
15
- return new Worker(Context.create(cx), ( ...args ) => {
16
- return clientCallback ? clientCallback( ...args.concat( defaultClientCallback ) ) : defaultClientCallback( ...args );
17
- });
15
+ return new Worker(Context.create(cx), clientCallback || defaultClientCallback);
18
16
  }
19
17
 
20
18
  /**
@@ -80,6 +80,7 @@ export default class Router extends _Router {
80
80
  ext = Path.parse(filename.substring(0, filename.length - ext.length)).ext;
81
81
  }
82
82
  }
83
+
83
84
  // read file from file system
84
85
  return new Promise(resolve => {
85
86
  Fs.readFile(filename, function(err, data) {