spyne-cli 0.3.0

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.
Files changed (70) hide show
  1. package/README.md +3 -0
  2. package/index-cli.js +56 -0
  3. package/index.js +14 -0
  4. package/lib/ansi.js +116 -0
  5. package/lib/combos.js +75 -0
  6. package/lib/completer.js +52 -0
  7. package/lib/interpolate.js +266 -0
  8. package/lib/keypress.js +243 -0
  9. package/lib/placeholder.js +63 -0
  10. package/lib/prompt.js +485 -0
  11. package/lib/prompts/autocomplete.js +113 -0
  12. package/lib/prompts/basicauth.js +41 -0
  13. package/lib/prompts/confirm.js +13 -0
  14. package/lib/prompts/editable.js +136 -0
  15. package/lib/prompts/form.js +196 -0
  16. package/lib/prompts/index.js +28 -0
  17. package/lib/prompts/input.js +55 -0
  18. package/lib/prompts/invisible.js +11 -0
  19. package/lib/prompts/list.js +36 -0
  20. package/lib/prompts/multiselect.js +11 -0
  21. package/lib/prompts/numeral.js +1 -0
  22. package/lib/prompts/password.js +18 -0
  23. package/lib/prompts/quiz.js +37 -0
  24. package/lib/prompts/scale.js +237 -0
  25. package/lib/prompts/select.js +139 -0
  26. package/lib/prompts/snippet.js +185 -0
  27. package/lib/prompts/sort.js +37 -0
  28. package/lib/prompts/survey.js +163 -0
  29. package/lib/prompts/text.js +1 -0
  30. package/lib/prompts/toggle.js +109 -0
  31. package/lib/render.js +33 -0
  32. package/lib/roles.js +46 -0
  33. package/lib/state.js +69 -0
  34. package/lib/styles.js +144 -0
  35. package/lib/symbols.js +66 -0
  36. package/lib/theme.js +11 -0
  37. package/lib/timer.js +38 -0
  38. package/lib/types/array.js +658 -0
  39. package/lib/types/auth.js +29 -0
  40. package/lib/types/boolean.js +88 -0
  41. package/lib/types/index.js +7 -0
  42. package/lib/types/number.js +86 -0
  43. package/lib/types/string.js +185 -0
  44. package/lib/utils.js +268 -0
  45. package/package.json +45 -0
  46. package/src/app/channels/.gitkeep +0 -0
  47. package/src/app/components/.gitkeep +0 -0
  48. package/src/app/traits/.gitkeep +0 -0
  49. package/src/spyne-file-prompt.js +63 -0
  50. package/src/spyne-template-prompts.js +171 -0
  51. package/src/templates/generate-file-string.js +131 -0
  52. package/src/templates/generate-prompt-input-fields.js +256 -0
  53. package/src/templates/generate-prompt-input-object.js +46 -0
  54. package/src/templates/generate-prompt-output.js +109 -0
  55. package/src/ui.js +16 -0
  56. package/src/utils/color-utils.js +36 -0
  57. package/src/utils/error-logger.js +15 -0
  58. package/src/utils/file-utils.js +69 -0
  59. package/tests/generate-file-prompt.test.js +47 -0
  60. package/tests/generate-file-string.test.js +68 -0
  61. package/tests/generate-prompt-input-fields-methods.test.js +178 -0
  62. package/tests/generate-prompt-input-fields.test.js +181 -0
  63. package/tests/generate-prompt-input-object.test.js +41 -0
  64. package/tests/generate-prompt-output.test.js +60 -0
  65. package/tests/index.test.js +13 -0
  66. package/tests/mocks/answers.js +33 -0
  67. package/tests/mocks/answers.json +33 -0
  68. package/tests/mocks/enquirer-data.js +411 -0
  69. package/tests/mocks/enquirer-data.json +409 -0
  70. package/tests/utils/file-utils.test.js +70 -0
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ ## spyne-cli
2
+ ### Spyne Command Line Tool
3
+ Generates spyne objects and saves them to standard spyne application directories.
package/index-cli.js ADDED
@@ -0,0 +1,56 @@
1
+ #! /usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import fsPromises from "fs/promises";
6
+ import R from 'ramda';
7
+
8
+
9
+ const args = process.argv;
10
+
11
+ const methodStr = args.length>=3 ? args[2] : 'empty';
12
+ const undefinedFn = async ()=>console.log('params are undefined.');
13
+
14
+
15
+ const deleteFile = async (filePath) => {
16
+ try {
17
+ await fsPromises.unlink(filePath);
18
+ //console.log('Successfully removed file!');
19
+ } catch (err) {
20
+ console.log(err);
21
+ }
22
+ };
23
+
24
+ function getFiles(dir) {
25
+ return fs.readdirSync(dir).flatMap((item) => {
26
+ const path = `${dir}/${item}`;
27
+ if (fs.statSync(path).isDirectory()) {
28
+ return getFiles(path);
29
+ }
30
+
31
+ return path;
32
+ });
33
+ }
34
+
35
+ const resetAppDir = ()=>{
36
+
37
+ const appDir = path.resolve('./', 'src/app');
38
+ const gitKeepRe = /(.*\/)(.gitkeep)/g;
39
+ const readFilesArr = R.compose(R.reject(R.test(gitKeepRe)))(getFiles(appDir));
40
+ readFilesArr.forEach(deleteFile);
41
+ console.log('app dir is reset');
42
+
43
+ }
44
+
45
+
46
+
47
+ const methodHash = {
48
+ resetAppDir
49
+ }
50
+
51
+
52
+ const methodFn = methodHash[methodStr] || undefinedFn;
53
+
54
+ methodFn();
55
+
56
+
package/index.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {SpyneCliUI} from './src/ui.js';
3
+ import clear from 'clear';
4
+ import SpyneFilePrompt from './src/spyne-file-prompt.js';
5
+
6
+ const startPromptFn = async()=>{
7
+ clear();
8
+ SpyneCliUI.title();
9
+ const spyneFilePrompt = new SpyneFilePrompt();
10
+ await spyneFilePrompt.startPrompt();
11
+ }
12
+
13
+ startPromptFn();
14
+
package/lib/ansi.js ADDED
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ const isTerm = process.env.TERM_PROGRAM === 'Apple_Terminal';
4
+ const colors = require('ansi-colors');
5
+ const utils = require('./utils');
6
+ const ansi = module.exports = exports;
7
+ const ESC = '\u001b[';
8
+ const BEL = '\u0007';
9
+ let hidden = false;
10
+
11
+ const code = ansi.code = {
12
+ bell: BEL,
13
+ beep: BEL,
14
+ beginning: `${ESC}G`,
15
+ down: `${ESC}J`,
16
+ esc: ESC,
17
+ getPosition: `${ESC}6n`,
18
+ hide: `${ESC}?25l`,
19
+ line: `${ESC}2K`,
20
+ lineEnd: `${ESC}K`,
21
+ lineStart: `${ESC}1K`,
22
+ restorePosition: ESC + (isTerm ? '8' : 'u'),
23
+ savePosition: ESC + (isTerm ? '7' : 's'),
24
+ screen: `${ESC}2J`,
25
+ show: `${ESC}?25h`,
26
+ up: `${ESC}1J`
27
+ };
28
+
29
+ const cursor = ansi.cursor = {
30
+ get hidden() {
31
+ return hidden;
32
+ },
33
+
34
+ hide() {
35
+ hidden = true;
36
+ return code.hide;
37
+ },
38
+ show() {
39
+ hidden = false;
40
+ return code.show;
41
+ },
42
+
43
+ forward: (count = 1) => `${ESC}${count}C`,
44
+ backward: (count = 1) => `${ESC}${count}D`,
45
+ nextLine: (count = 1) => `${ESC}E`.repeat(count),
46
+ prevLine: (count = 1) => `${ESC}F`.repeat(count),
47
+
48
+ up: (count = 1) => count ? `${ESC}${count}A` : '',
49
+ down: (count = 1) => count ? `${ESC}${count}B` : '',
50
+ right: (count = 1) => count ? `${ESC}${count}C` : '',
51
+ left: (count = 1) => count ? `${ESC}${count}D` : '',
52
+
53
+ to(x, y) {
54
+ return y ? `${ESC}${y + 1};${x + 1}H` : `${ESC}${x + 1}G`;
55
+ },
56
+
57
+ move(x = 0, y = 0) {
58
+ let res = '';
59
+ res += (x < 0) ? cursor.left(-x) : (x > 0) ? cursor.right(x) : '';
60
+ res += (y < 0) ? cursor.up(-y) : (y > 0) ? cursor.down(y) : '';
61
+ return res;
62
+ },
63
+
64
+ restore(state = {}) {
65
+ let { after, cursor, initial, input, prompt, size, value } = state;
66
+ initial = utils.isPrimitive(initial) ? String(initial) : '';
67
+ input = utils.isPrimitive(input) ? String(input) : '';
68
+ value = utils.isPrimitive(value) ? String(value) : '';
69
+
70
+ if (size) {
71
+ let codes = ansi.cursor.up(size) + ansi.cursor.to(prompt.length);
72
+ let diff = input.length - cursor;
73
+ if (diff > 0) {
74
+ codes += ansi.cursor.left(diff);
75
+ }
76
+ return codes;
77
+ }
78
+
79
+ if (value || after) {
80
+ let pos = (!input && !!initial) ? -initial.length : -input.length + cursor;
81
+ if (after) pos -= after.length;
82
+ if (input === '' && initial && !prompt.includes(initial)) {
83
+ pos += initial.length;
84
+ }
85
+ return ansi.cursor.move(pos);
86
+ }
87
+ }
88
+ };
89
+
90
+ const erase = ansi.erase = {
91
+ screen: code.screen,
92
+ up: code.up,
93
+ down: code.down,
94
+ line: code.line,
95
+ lineEnd: code.lineEnd,
96
+ lineStart: code.lineStart,
97
+ lines(n) {
98
+ let str = '';
99
+ for (let i = 0; i < n; i++) {
100
+ str += ansi.erase.line + (i < n - 1 ? ansi.cursor.up(1) : '');
101
+ }
102
+ if (n) str += ansi.code.beginning;
103
+ return str;
104
+ }
105
+ };
106
+
107
+ ansi.clear = (input = '', columns = process.stdout.columns) => {
108
+ if (!columns) return erase.line + cursor.to(0);
109
+ let width = str => [...colors.unstyle(str)].length;
110
+ let lines = input.split(/\r?\n/);
111
+ let rows = 0;
112
+ for (let line of lines) {
113
+ rows += 1 + Math.floor(Math.max(width(line) - 1, 0) / columns);
114
+ }
115
+ return (erase.line + cursor.prevLine()).repeat(rows - 1) + erase.line + cursor.to(0);
116
+ };
package/lib/combos.js ADDED
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Actions are mappings from keypress event names to method names
5
+ * in the prompts.
6
+ */
7
+
8
+ exports.ctrl = {
9
+ a: 'first',
10
+ b: 'backward',
11
+ c: 'cancel',
12
+ d: 'deleteForward',
13
+ e: 'last',
14
+ f: 'forward',
15
+ g: 'reset',
16
+ i: 'tab',
17
+ k: 'cutForward',
18
+ l: 'reset',
19
+ n: 'newItem',
20
+ m: 'cancel',
21
+ j: 'submit',
22
+ p: 'search',
23
+ r: 'remove',
24
+ s: 'save',
25
+ u: 'undo',
26
+ w: 'cutLeft',
27
+ x: 'toggleCursor',
28
+ v: 'paste'
29
+ };
30
+
31
+ exports.shift = {
32
+ up: 'shiftUp',
33
+ down: 'shiftDown',
34
+ left: 'shiftLeft',
35
+ right: 'shiftRight',
36
+ tab: 'prev'
37
+ };
38
+
39
+ exports.fn = {
40
+ up: 'pageUp',
41
+ down: 'pageDown',
42
+ left: 'pageLeft',
43
+ right: 'pageRight',
44
+ delete: 'deleteForward'
45
+ };
46
+
47
+ // <alt> on Windows
48
+ exports.option = {
49
+ b: 'backward',
50
+ f: 'forward',
51
+ d: 'cutRight',
52
+ left: 'cutLeft',
53
+ up: 'altUp',
54
+ down: 'altDown'
55
+ };
56
+
57
+ exports.keys = {
58
+ pageup: 'pageUp', // <fn>+<up> (mac), <Page Up> (windows)
59
+ pagedown: 'pageDown', // <fn>+<down> (mac), <Page Down> (windows)
60
+ home: 'home', // <fn>+<left> (mac), <home> (windows)
61
+ end: 'end', // <fn>+<right> (mac), <end> (windows)
62
+ cancel: 'cancel',
63
+ delete: 'deleteForward',
64
+ backspace: 'delete',
65
+ down: 'down',
66
+ enter: 'submit',
67
+ escape: 'cancel',
68
+ left: 'left',
69
+ space: 'space',
70
+ number: 'number',
71
+ return: 'submit',
72
+ right: 'right',
73
+ tab: 'next',
74
+ up: 'up'
75
+ };
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ const unique = arr => arr.filter((v, i) => arr.lastIndexOf(v) === i);
4
+ const compact = arr => unique(arr).filter(Boolean);
5
+
6
+ module.exports = (action, data = {}, value = '') => {
7
+ let { past = [], present = '' } = data;
8
+ let rest, prev;
9
+
10
+ switch (action) {
11
+ case 'prev':
12
+ case 'undo':
13
+ rest = past.slice(0, past.length - 1);
14
+ prev = past[past.length - 1] || '';
15
+ return {
16
+ past: compact([value, ...rest]),
17
+ present: prev
18
+ };
19
+
20
+ case 'next':
21
+ case 'redo':
22
+ rest = past.slice(1);
23
+ prev = past[0] || '';
24
+ return {
25
+ past: compact([...rest, value]),
26
+ present: prev
27
+ };
28
+
29
+ case 'save':
30
+ return {
31
+ past: compact([...past, value]),
32
+ present: ''
33
+ };
34
+
35
+ case 'remove':
36
+ prev = compact(past.filter(v => v !== value));
37
+ present = '';
38
+
39
+ if (prev.length) {
40
+ present = prev.pop();
41
+ }
42
+
43
+ return {
44
+ past: prev,
45
+ present
46
+ };
47
+
48
+ default: {
49
+ throw new Error(`Invalid action: "${action}"`);
50
+ }
51
+ }
52
+ };
@@ -0,0 +1,266 @@
1
+ 'use strict';
2
+
3
+ const colors = require('ansi-colors');
4
+ const clean = (str = '') => {
5
+ return typeof str === 'string' ? str.replace(/^['"]|['"]$/g, '') : '';
6
+ };
7
+
8
+ /**
9
+ * This file contains the interpolation and rendering logic for
10
+ * the Snippet prompt.
11
+ */
12
+
13
+ class Item {
14
+ constructor(token) {
15
+ this.name = token.key;
16
+ this.field = token.field || {};
17
+ this.value = clean(token.initial || this.field.initial || '');
18
+ this.message = token.message || this.name;
19
+ this.cursor = 0;
20
+ this.input = '';
21
+ this.lines = [];
22
+ }
23
+ }
24
+
25
+ const tokenize = async(options = {}, defaults = {}, fn = token => token) => {
26
+ let unique = new Set();
27
+ let fields = options.fields || [];
28
+ let input = options.template;
29
+ let tabstops = [];
30
+ let items = [];
31
+ let keys = [];
32
+ let line = 1;
33
+
34
+ if (typeof input === 'function') {
35
+ input = await input();
36
+ }
37
+
38
+ let i = -1;
39
+ let next = () => input[++i];
40
+ let peek = () => input[i + 1];
41
+ let push = token => {
42
+ token.line = line;
43
+ tabstops.push(token);
44
+ };
45
+
46
+ push({ type: 'bos', value: '' });
47
+
48
+ while (i < input.length - 1) {
49
+ let value = next();
50
+
51
+ if (/^[^\S\n ]$/.test(value)) {
52
+ push({ type: 'text', value });
53
+ continue;
54
+ }
55
+
56
+ if (value === '\n') {
57
+ push({ type: 'newline', value });
58
+ line++;
59
+ continue;
60
+ }
61
+
62
+ if (value === '\\') {
63
+ value += next();
64
+ push({ type: 'text', value });
65
+ continue;
66
+ }
67
+
68
+ if ((value === '$' || value === '#' || value === '{') && peek() === '{') {
69
+ let n = next();
70
+ value += n;
71
+
72
+ let token = { type: 'template', open: value, inner: '', close: '', value };
73
+ let ch;
74
+
75
+ while ((ch = next())) {
76
+ if (ch === '}') {
77
+ if (peek() === '}') ch += next();
78
+ token.value += ch;
79
+ token.close = ch;
80
+ break;
81
+ }
82
+
83
+ if (ch === ':') {
84
+ token.initial = '';
85
+ token.key = token.inner;
86
+ } else if (token.initial !== void 0) {
87
+ token.initial += ch;
88
+ }
89
+
90
+ token.value += ch;
91
+ token.inner += ch;
92
+ }
93
+
94
+ token.template = token.open + (token.initial || token.inner) + token.close;
95
+ token.key = token.key || token.inner;
96
+
97
+ if (defaults.hasOwnProperty(token.key)) {
98
+ token.initial = defaults[token.key];
99
+ }
100
+
101
+ token = fn(token);
102
+ push(token);
103
+
104
+ keys.push(token.key);
105
+ unique.add(token.key);
106
+
107
+ let item = items.find(item => item.name === token.key);
108
+ token.field = fields.find(ch => ch.name === token.key);
109
+
110
+ if (!item) {
111
+ item = new Item(token);
112
+ items.push(item);
113
+ }
114
+
115
+ item.lines.push(token.line - 1);
116
+ continue;
117
+ }
118
+
119
+ let last = tabstops[tabstops.length - 1];
120
+ if (last.type === 'text' && last.line === line) {
121
+ last.value += value;
122
+ } else {
123
+ push({ type: 'text', value });
124
+ }
125
+ }
126
+
127
+ push({ type: 'eos', value: '' });
128
+ return { input, tabstops, unique, keys, items };
129
+ };
130
+
131
+ module.exports = async prompt => {
132
+ let options = prompt.options;
133
+ let required = new Set(options.required === true ? [] : (options.required || []));
134
+ let defaults = { ...options.values, ...options.initial };
135
+ let { tabstops, items, keys } = await tokenize(options, defaults);
136
+
137
+ let result = createFn('result', prompt, options);
138
+ let format = createFn('format', prompt, options);
139
+ let isValid = createFn('validate', prompt, options, true);
140
+ let isVal = prompt.isValue.bind(prompt);
141
+
142
+ return async(state = {}, submitted = false) => {
143
+ let index = 0;
144
+
145
+ state.required = required;
146
+ state.items = items;
147
+ state.keys = keys;
148
+ state.output = '';
149
+
150
+ let validate = async(value, state, item, index) => {
151
+ let error = await isValid(value, state, item, index);
152
+ if (error === false) {
153
+ return 'Invalid field ' + item.name;
154
+ }
155
+ return error;
156
+ };
157
+
158
+ for (let token of tabstops) {
159
+ let value = token.value;
160
+ let key = token.key;
161
+
162
+ if (token.type !== 'template') {
163
+ if (value) state.output += value;
164
+ continue;
165
+ }
166
+
167
+ if (token.type === 'template') {
168
+ let item = items.find(ch => ch.name === key);
169
+
170
+ if (options.required === true) {
171
+ state.required.add(item.name);
172
+ }
173
+
174
+ let val = [item.input, state.values[item.value], item.value, value].find(isVal);
175
+ let field = item.field || {};
176
+ let message = field.message || token.inner;
177
+
178
+ if (submitted) {
179
+ let error = await validate(state.values[key], state, item, index);
180
+ if ((error && typeof error === 'string') || error === false) {
181
+ state.invalid.set(key, error);
182
+ continue;
183
+ }
184
+
185
+ state.invalid.delete(key);
186
+ let res = await result(state.values[key], state, item, index);
187
+ state.output += colors.unstyle(res);
188
+ continue;
189
+ }
190
+
191
+ item.placeholder = false;
192
+
193
+ let before = value;
194
+ value = await format(value, state, item, index);
195
+
196
+ if (val !== value) {
197
+ state.values[key] = val;
198
+ value = prompt.styles.typing(val);
199
+ state.missing.delete(message);
200
+
201
+ } else {
202
+ state.values[key] = void 0;
203
+ val = `<${message}>`;
204
+ value = prompt.styles.primary(val);
205
+ item.placeholder = true;
206
+
207
+ if (state.required.has(key)) {
208
+ state.missing.add(message);
209
+ }
210
+ }
211
+
212
+ if (state.missing.has(message) && state.validating) {
213
+ value = prompt.styles.warning(val);
214
+ }
215
+
216
+ if (state.invalid.has(key) && state.validating) {
217
+ value = prompt.styles.danger(val);
218
+ }
219
+
220
+ if (index === state.index) {
221
+ if (before !== value) {
222
+ value = prompt.styles.underline(value);
223
+ } else {
224
+ value = prompt.styles.heading(colors.unstyle(value));
225
+ }
226
+ }
227
+
228
+ index++;
229
+ }
230
+
231
+ if (value) {
232
+ state.output += value;
233
+ }
234
+ }
235
+
236
+ let lines = state.output.split('\n').map(l => ' ' + l);
237
+ let len = items.length;
238
+ let done = 0;
239
+
240
+ for (let item of items) {
241
+ if (state.invalid.has(item.name)) {
242
+ item.lines.forEach(i => {
243
+ if (lines[i][0] !== ' ') return;
244
+ lines[i] = state.styles.danger(state.symbols.bullet) + lines[i].slice(1);
245
+ });
246
+ }
247
+
248
+ if (prompt.isValue(state.values[item.name])) {
249
+ done++;
250
+ }
251
+ }
252
+
253
+ state.completed = ((done / len) * 100).toFixed(0);
254
+ state.output = lines.join('\n');
255
+ return state.output;
256
+ };
257
+ };
258
+
259
+ function createFn(prop, prompt, options, fallback) {
260
+ return (value, state, item, index) => {
261
+ if (typeof item.field[prop] === 'function') {
262
+ return item.field[prop].call(prompt, value, state, item, index);
263
+ }
264
+ return [fallback, value].find(v => prompt.isValue(v));
265
+ };
266
+ }