@webqit/webflo 0.8.48-0 → 0.8.49

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.
@@ -0,0 +1,25 @@
1
+ # Base installations
2
+ FROM node:12-alpine
3
+ RUN apk --no-cache add git
4
+
5
+ # We'll install npm packages at one-level higher than
6
+ # actuall app root, so that we can bind-mount host system's app root
7
+ # without overriding the container's node_modules directory
8
+ WORKDIR /home/www
9
+ RUN npm install
10
+ RUN npm install @webqit/webflo -g
11
+ RUN npm install @webqit/playui-cli -g
12
+
13
+ # Move one-level in, for the reasons above
14
+ WORKDIR /home/www/app
15
+
16
+ # To auto-start app (flags optional), we would add...
17
+ # CMD ["webflo", "start", "--env=dev", "--http-only"]
18
+
19
+ # To build the image locally...
20
+ # docker build --no-cache -t webflo ./docker
21
+
22
+ # To publish to docker hub...
23
+ # docker login -u webqit
24
+ # docker tag webflo webqit/webflo
25
+ # docker push webqit/webflo
@@ -0,0 +1,69 @@
1
+ # Webflo Container
2
+ This is simply a node.js container with the `@webqit/webflo` framework installed. Once started, any webflo app can be deployed into the container from any git repository.
3
+
4
+ ## Usage
5
+ This container image lives on Docker Hub and can be pulled to a local machine or a remote Virtual Machine (VM) on any cloud platform.
6
+
7
+ + [To Use Locally](#to-use-locally)
8
+ + [To Use In the Cloud](#to-use-in-the-cloud)
9
+ * [To Deploy An App From Any Repo](#to-deploy-an-app-from-any-repo)
10
+
11
+ ### To Use Locally
12
+ Ensure you have docker installed on your computer and run the following command from any location on your terminal:
13
+
14
+ ```shell
15
+ docker pull webqit/webflo:latest
16
+ ```
17
+
18
+ The above command pulls the `webqit/webflo` image to your local machine. (But this can be automatically done by docker on running any docker commands that reference the `webqit/webflo` image.)
19
+
20
+ Next is to use the following commands to start the container and the Webflo runtime. In each case, the first part of the command starts the container, while the second part (from `webflo start`) starts the application.
21
+
22
+ #### To Start
23
+ Start the container using `docker run`; map a port (e.g `80`) of your host machine to `3000` of the container (unless changed webflo expects to run on port `3000`); optionally, give your container a name; reference `webqit/webflo` as the image to use; and lastly, start webflo using `webflo start`.
24
+
25
+ ```shell
26
+ docker run -d -p 80:3000 --name my-app webqit/webflo webflo start
27
+ ```
28
+
29
+ Visit [localhost](http://localhost) to view your app.
30
+
31
+ #### To Start In Dev Mode
32
+ Webflo's *dev* mode is the perfect mode for developing locally. All you do is append the `--env=dev` flag to your webflo commands. [(Learn more)](#)
33
+
34
+ ```shell
35
+ docker run -d -p 80:3000 --name my-app webqit/webflo webflo start --env=dev
36
+ ```
37
+
38
+ In *dev* mode, webflo automatically restarts as you make changes to your codebase. Since webflo now lives inside a container, you'll need to *bind* the directory of your source code on your host machine to the `/home/www/app` directory of the container.
39
+
40
+ ```shell
41
+ docker run -d -v /Users/me/my-app:/home/www/app -p 80:3000 --name my-app webqit/webflo webflo start --env=dev
42
+ ```
43
+
44
+ ### To Use In the Cloud
45
+ TODO
46
+
47
+ ### To Deploy An App From Any Repo
48
+ Whether running locally or in the cloud, webflo can easily take your application from any git repo. This follows webflo's normal `deploy` command.
49
+
50
+ Simply point docker at your container (using `docker exec [container-name]`) and execute the `webflo deploy` command.
51
+
52
+ ```shell
53
+ docker exec my-app webflo deploy https://github.com/me/my-app
54
+ ```
55
+
56
+ If you will need to install any npm dependencies, you would run `npm install` on the appropriate directory in your container.
57
+
58
+ ```shell
59
+ docker exec my-app npm install
60
+ ```
61
+
62
+ If you will need to run additional webflo commands (e.g `webflo restart` to restart the application), you would follow the same pattern above.
63
+
64
+ ```shell
65
+ docker exec my-app webflo restart
66
+ ```
67
+
68
+ ## Extending this Build
69
+ TODO
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.8.48-0",
15
+ "version": "0.8.49",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -38,17 +38,21 @@
38
38
  "@octokit/webhooks": "^7.15.1",
39
39
  "@webqit/backpack": "^0.0.37",
40
40
  "@webqit/browser-pie": "^0.0.16",
41
- "@webqit/pseudo-browser": "^0.3.30",
41
+ "@webqit/pseudo-browser": "^0.3.35",
42
42
  "@webqit/util": "^0.8.7",
43
- "accepts": "^1.3.7",
44
43
  "client-sessions": "^0.8.0",
45
44
  "cookie": "^0.4.1",
45
+ "form-data-encoder": "^1.6.0",
46
+ "formdata-node": "^4.3.0",
46
47
  "formidable": "^2.0.0-dev.20200131.2",
47
48
  "is-glob": "^4.0.1",
48
49
  "micromatch": "^3.1.10",
50
+ "mime-types": "^2.1.33",
49
51
  "minimatch": "^3.0.4",
52
+ "node-fetch": "^2.6.1",
50
53
  "pm2": "^5.1.0",
51
54
  "simple-git": "^2.20.1",
55
+ "stream-slice": "^0.1.2",
52
56
  "touch": "^3.1.0",
53
57
  "undici": "^4.3.1",
54
58
  "uuid": "^8.3.2",
@@ -64,9 +64,9 @@ export async function deploy(Ui, origin, flags = {}, layout = {}) {
64
64
  Fs.mkdirSync(origin.deploy_path, {recursive: true});
65
65
  }
66
66
  }
67
- git.cwd(origin.deploy_path);
67
+ await git.cwd(origin.deploy_path);
68
68
  // Must come after git.cwd()
69
- git.init();
69
+ await git.init();
70
70
 
71
71
  const hosts = {
72
72
  github: 'https://github.com',
@@ -0,0 +1,130 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import _isString from '@webqit/util/js/isString.js';
6
+ import _isFunction from '@webqit/util/js/isFunction.js';
7
+ import _isArray from '@webqit/util/js/isArray.js';
8
+ import _arrFrom from '@webqit/util/arr/from.js';
9
+
10
+ /**
11
+ * ---------------------------
12
+ * The Router class
13
+ * ---------------------------
14
+ */
15
+
16
+ export default class Router {
17
+
18
+ /**
19
+ * Constructs a new Router instance
20
+ * over route definitions.
21
+ *
22
+ * @param string|array path
23
+ * @param object layout
24
+ * @param object context
25
+ *
26
+ * @return void
27
+ */
28
+ constructor(path, layout, context) {
29
+ this.path = _isArray(path) ? path : (path + '').split('/').filter(a => a);
30
+ this.layout = layout;
31
+ this.context = context;
32
+ }
33
+
34
+ /**
35
+ * Performs dynamic routing.
36
+ *
37
+ * @param array|string method
38
+ * @param Object event
39
+ * @param any arg
40
+ * @param function _default
41
+ *
42
+ * @return object
43
+ */
44
+ async route(method, event, arg, _default) {
45
+
46
+ const $this = this;
47
+
48
+ // ----------------
49
+ // The loop
50
+ // ----------------
51
+ const next = async function(thisTick) {
52
+ const _this = { ...$this.context };
53
+ if (!thisTick.trail || thisTick.trail.length < thisTick.destination.length) {
54
+ thisTick = await $this.readTick(thisTick);
55
+ // -------------
56
+ _this.pathname = `/${thisTick.trail.join('/')}`;
57
+ _this.stepname = thisTick.trail[thisTick.trail.length - 1];
58
+ $this.finalizeHandlerContext(_this, thisTick);
59
+ // -------------
60
+ if (thisTick.exports) {
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);
63
+ if (handler) {
64
+ // -------------
65
+ // Dynamic response
66
+ // -------------
67
+ const _next = async (..._args) => {
68
+ const nextTick = { ...thisTick, arg: _args[0] };
69
+ if (_args.length > 1) {
70
+ var _url = _args[1], _request, requestInit = { ...(_args[2] || {}) };
71
+ if (_args[1] instanceof nextTick.event.Request) {
72
+ _request = _args[1];
73
+ _url = _request.url;
74
+ } else if (!_isString(_url)) {
75
+ throw new Error('Router redirect url must be a string!');
76
+ }
77
+ var newDestination = _url.startsWith('/') ? _url : $this.pathJoin(`/${thisTick.trail.join('/')}`, _url);
78
+ if (newDestination.startsWith('../')) {
79
+ throw new Error('Router redirect cannot traverse beyond the routing directory! (' + _url + ' >> ' + newDestination + ')');
80
+ }
81
+ if (requestInit.method) {
82
+ nextTick.method = requestInit.method;
83
+ if (_isArray(requestInit.method)) {
84
+ requestInit.method = requestInit.method[0];
85
+ }
86
+ }
87
+ if (_request) {
88
+ nextTick.event = thisTick.event.retarget(_request, { ...requestInit, _proxy: { url: newDestination/** non-standard but works */ } });
89
+ } else {
90
+ nextTick.event = thisTick.event.retarget(newDestination, requestInit);
91
+ }
92
+
93
+ nextTick.source = thisTick.destination.join('/');
94
+ nextTick.destination = newDestination.split('?').shift().split('/').map(a => a.trim()).filter(a => a);
95
+ nextTick.trail = _args[1].startsWith('/') ? [] : thisTick.trail.reduce((_commonRoot, _seg, i) => _commonRoot.length === i && _seg === nextTick.destination[i] ? _commonRoot.concat(_seg) : _commonRoot, []);
96
+ nextTick.trailOnFile = thisTick.trailOnFile.slice(0, nextTick.trail.length);
97
+ }
98
+ return next(nextTick);
99
+ };
100
+ // -------------
101
+ const nextPathname = thisTick.destination.slice(thisTick.trail.length);
102
+ _next.pathname = nextPathname.join('/');
103
+ _next.stepname = nextPathname[0];
104
+ // -------------
105
+ return await handler.call(_this, thisTick.event, thisTick.arg, _next/*next*/);
106
+ }
107
+ // Handler not found but exports found
108
+ return next(thisTick);
109
+ } else if ((thisTick.currentSegmentOnFile || {}).dirExists) {
110
+ // Exports not found but directory found
111
+ return next(thisTick);
112
+ }
113
+ }
114
+ // -------------
115
+ // Local file
116
+ // -------------
117
+ if (_default) {
118
+ return await _default.call(_this, thisTick.event, thisTick.arg);
119
+ }
120
+ };
121
+
122
+ return next({
123
+ destination: this.path,
124
+ event,
125
+ method,
126
+ arg,
127
+ });
128
+
129
+ }
130
+ };
@@ -0,0 +1,60 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { _isTypeObject, _isNumeric } from '@webqit/util/js/index.js';
6
+ import { _before } from '@webqit/util/str/index.js';
7
+ import { wwwFormSet, wwwFormPathSerializeCallback } from './util.js';
8
+
9
+ /**
10
+ * The _Headers Mixin
11
+ */
12
+ const _FormData = NativeFormData => class extends NativeFormData {
13
+
14
+ tee(callback = null) {
15
+ const formData1 = new this.constructor, formData2 = new this.constructor;
16
+ for (var [ name, value ] of this.entries()) {
17
+ const formDataType = formDataType(value);
18
+ if ((callback && callback(value, name, formDataType)) || (!callback && !formDataType)) {
19
+ formData1.append(name, value);
20
+ } else {
21
+ formData2.append(name, value);
22
+ }
23
+ }
24
+ return [ formData1, formData2 ];
25
+ }
26
+
27
+ json(data = {}, callback = null) {
28
+ if (arguments.length) {
29
+ Object.keys(data).forEach(key => {
30
+ wwwFormPathSerializeCallback(key, data[key], (_wwwFormPath, _value) => {
31
+ if (!callback || callback(_wwwFormPath, _value, _isTypeObject(_value))) {
32
+ this.append(_wwwFormPath, _value);
33
+ }
34
+ }, value => !formDataType(value));
35
+ });
36
+ return;
37
+ }
38
+ var jsonBuild; // We'll dynamically determine if this should be an array or an object
39
+ for (var [ name, value ] of this.entries()) {
40
+ if (!jsonBuild) {
41
+ jsonBuild = _isNumeric(_before(name, '[')) ? [] : {};
42
+ }
43
+ wwwFormSet(jsonBuild, name, value);
44
+ }
45
+ return jsonBuild;
46
+ }
47
+
48
+ }
49
+
50
+ export default _FormData;
51
+
52
+ export const formDataType = (value, list = null) => {
53
+ if (!_isTypeObject(value)) {
54
+ return;
55
+ }
56
+ const toStringTag = value[Symbol.toStringTag];
57
+ return (list || [
58
+ 'Uint8Array', 'Uint16Array', 'Uint32Array', 'ArrayBuffer', 'Blob', 'File', 'FormData', 'Stream'
59
+ ]).reduce((_toStringTag, type) => _toStringTag || (toStringTag === type ? type : null), null);
60
+ };
@@ -0,0 +1,88 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { _after, _beforeLast } from "@webqit/util/str/index.js";
6
+ import { _isString, _getType, _isObject, _isFunction } from "@webqit/util/js/index.js";
7
+ import { _isTypeObject } from '@webqit/util/js/index.js';
8
+
9
+ /**
10
+ * The _Headers Mixin
11
+ */
12
+ const _Headers = NativeHeaders => class extends NativeHeaders {
13
+
14
+ // construct
15
+ constructor(definition = {}) {
16
+ if (definition instanceof NativeHeaders) {
17
+ // It's another Headers instance
18
+ super(definition);
19
+ } else {
20
+ super();
21
+ this.json(definition);
22
+ }
23
+ }
24
+
25
+ json(headers = {}, replace = true) {
26
+ if (arguments.length) {
27
+ const _setters = getAllPropertyDescriptors(this);
28
+ const setters = Object.keys(_setters).reduce((list, key) => list.concat((typeof key !== 'symbol') && ('set' in _setters[key]) ? key : []), []);
29
+ Object.keys(headers).forEach(name => {
30
+ var nameCs = setters.reduce((prev, curr) => prev || (curr === name || curr.toLocaleLowerCase() === name ? curr : null), null);
31
+ if (nameCs) {
32
+ if (replace || this[nameCs] === undefined) {
33
+ this[nameCs] = headers[name];
34
+ }
35
+ } else {
36
+ if (replace || !this.has(name)) {
37
+ this.set(name, headers[name]);
38
+ }
39
+ }
40
+ });
41
+ return;
42
+ }
43
+ const _headers = {};
44
+ for (var [ name, value ] of this) {
45
+ _headers[name] = value;
46
+ }
47
+ return _headers;
48
+ }
49
+
50
+ set cacheControl(value) {
51
+ return this.set('Cache-Control', value);
52
+ }
53
+
54
+ get cacheControl() {
55
+ return this.get('Cache-Control');
56
+ }
57
+
58
+ set contentLength(value) {
59
+ return this.set('Content-Length', value);
60
+ }
61
+
62
+ get contentLength() {
63
+ return this.get('Content-Length');
64
+ }
65
+
66
+ set contentType(value) {
67
+ return this.set('Content-Type', value);
68
+ }
69
+
70
+ get contentType() {
71
+ return this.get('Content-Type');
72
+ }
73
+
74
+ }
75
+
76
+ export default _Headers;
77
+
78
+ function getAllPropertyDescriptors(obj) {
79
+ if (!obj) {
80
+ return Object.create(null);
81
+ } else {
82
+ const proto = Object.getPrototypeOf(obj);
83
+ return proto === Object.prototype ? {} : {
84
+ ...getAllPropertyDescriptors(proto),
85
+ ...Object.getOwnPropertyDescriptors(obj)
86
+ };
87
+ }
88
+ }
@@ -0,0 +1,191 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { _isArray, _isEmpty, _isNumber, _isObject, _isPlainArray, _isPlainObject, _isString } from '@webqit/util/js/index.js';
6
+ import { formDataType } from './_FormData.js';
7
+
8
+ /**
9
+ * The _Request Mixin
10
+ */
11
+ const _MessageStream = (NativeMessageStream, Headers, FormData) => {
12
+ const MessageStream = class extends NativeMessageStream {
13
+
14
+ constructor(input, init = {}) {
15
+ var _proxy = {}, _meta = {};
16
+ if ((('headers' in init) && !(init instanceof Headers)) || ('_proxy' in init) || ('meta' in init)) {
17
+ init = { ...init };
18
+ if (('headers' in init) && !(init instanceof Headers)) {
19
+ init.headers = new Headers(init.headers);
20
+ }
21
+ if (('_proxy' in init)) {
22
+ _proxy = init._proxy;
23
+ delete init._proxy;
24
+ }
25
+ if (('meta' in init)) {
26
+ _meta = init.meta;
27
+ delete init.meta;
28
+ }
29
+ arguments[1] = init;
30
+ }
31
+ super(...arguments);
32
+ this._proxy = _proxy;
33
+ this._meta = _meta;
34
+ }
35
+
36
+ clone() {
37
+ const clone = new this.constructor(super.clone());
38
+ clone._proxy = this._proxy;
39
+ clone._headers = this._headers;
40
+ clone._typedDataCache = this._typedDataCache;
41
+ clone._meta = this._meta;
42
+ return clone;
43
+ }
44
+
45
+ get url() {
46
+ return 'url' in this._proxy ? this._proxy.url : super.url;
47
+ }
48
+
49
+ get headers() {
50
+ if (!this._headers) {
51
+ this._headers = new Headers(super.headers);
52
+ }
53
+ return this._headers;
54
+ }
55
+
56
+ get original() {
57
+ return this._typedDataCache.original;
58
+ }
59
+
60
+ get originalType() {
61
+ return this._typedDataCache.originalType;
62
+ }
63
+
64
+ get meta() {
65
+ return this._meta || {};
66
+ }
67
+
68
+ async arrayBuffer() {
69
+ if (this._typedDataCache.arrayBuffer) {
70
+ return this._typedDataCache.arrayBuffer;
71
+ }
72
+ return super.arrayBuffer();
73
+ }
74
+
75
+ async blob() {
76
+ if (this._typedDataCache.blob) {
77
+ return this._typedDataCache.blob;
78
+ }
79
+ return super.blob();
80
+ }
81
+
82
+ async formData() {
83
+ if (this._typedDataCache.formData) {
84
+ return this._typedDataCache.formData;
85
+ }
86
+ const formData = await super.formData();
87
+ formData.tee = FormData.prototype.tee.bind(formData);
88
+ formData.json = FormData.prototype.json.bind(formData);
89
+ return formData;
90
+ }
91
+
92
+ async json() {
93
+ if (this._typedDataCache.json) {
94
+ return this._typedDataCache.json;
95
+ }
96
+ return super.json();
97
+ }
98
+
99
+ async text() {
100
+ if (this._typedDataCache.text) {
101
+ return this._typedDataCache.text;
102
+ }
103
+ return super.text();
104
+ }
105
+
106
+ // Payload
107
+ jsonBuild(force = false) {
108
+ if (!this._typedDataCache.jsonBuild || force) {
109
+ this._typedDataCache.jsonBuild = new Promise(async resolve => {
110
+ var request = this, jsonBuild, contentType = request.headers.get('content-type') || '';
111
+ var type = contentType === 'application/json' || this._typedDataCache.json ? 'json' : (
112
+ contentType === 'application/x-www-form-urlencoded' || contentType.startsWith('multipart/') || this._typedDataCache.formData || (!contentType && !['get'].includes((request.method || '').toLowerCase())) ? 'formData' : (
113
+ contentType === 'text/plain' ? 'plain' : 'other'
114
+ )
115
+ );
116
+ if (type === 'formData') {
117
+ jsonBuild = (await request.formData()).json();
118
+ } else {
119
+ jsonBuild = type === 'json' ? await request.json() : (
120
+ type === 'plain' ? await request.text() : request.body
121
+ );
122
+ }
123
+ resolve(jsonBuild);
124
+ });
125
+ }
126
+ return this._typedDataCache.jsonBuild;
127
+ }
128
+
129
+ };
130
+ // ----------
131
+ MessageStream.Headers = Headers;
132
+ // ----------
133
+ return MessageStream;
134
+ }
135
+
136
+ export default _MessageStream;
137
+
138
+ export function encodeBody(body, globals) {
139
+ const detailsObj = { body, original: body };
140
+ if (_isString(body) || _isNumber(body)) {
141
+ detailsObj.originalType = 'text';
142
+ detailsObj.text = body;
143
+ detailsObj.headers = {
144
+ contentLength: (body + '').length,
145
+ };
146
+ return detailsObj;
147
+ }
148
+ detailsObj.originalType = formDataType(body);
149
+ if ([ 'Blob', 'File' ].includes(detailsObj.originalType)) {
150
+ detailsObj.blob = body;
151
+ detailsObj.headers = {
152
+ contentType: body.type,
153
+ contentLength: body.size,
154
+ };
155
+ } else if ([ 'Uint8Array', 'Uint16Array', 'Uint32Array', 'ArrayBuffer' ].includes(detailsObj.originalType)) {
156
+ detailsObj.arrayBuffer = body;
157
+ detailsObj.headers = {
158
+ contentLength: body.byteLength,
159
+ };
160
+ } else if (detailsObj.originalType === 'FormData') {
161
+ detailsObj.formData = body;
162
+ encodeFormData(detailsObj, body, globals);
163
+ } else if ((_isObject(body) && _isPlainObject(body)) || (_isArray(body) && _isPlainArray(body))) {
164
+ // Deserialize object while detecting if multipart
165
+ var hasBlobs, formData = new globals.FormData;
166
+ formData.json(body, (path, value, objectType) => {
167
+ hasBlobs = hasBlobs || objectType;
168
+ return true;
169
+ });
170
+ if (hasBlobs) {
171
+ detailsObj.formData = formData;
172
+ encodeFormData(detailsObj, formData, globals);
173
+ } else {
174
+ detailsObj.json = body;
175
+ detailsObj.body = JSON.stringify(body);
176
+ detailsObj.headers = {
177
+ contentType: 'application/json',
178
+ contentLength: Buffer.byteLength(detailsObj.body, 'utf8'), // Buffer.from(string).length
179
+ };
180
+ }
181
+ detailsObj.jsonBuild = body;
182
+ }
183
+ return detailsObj;
184
+ }
185
+
186
+ const encodeFormData = (detailsObj, formData, globals) => {
187
+ if (!globals.FormDataEncoder || !globals.ReadableStream) return;
188
+ const encoder = new globals.FormDataEncoder(formData);
189
+ detailsObj.body = globals.ReadableStream.from(encoder.encode());
190
+ detailsObj.headers = encoder.headers;
191
+ };