spyne-cli 0.6.8 → 0.7.1

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 (94) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +80 -30
  3. package/index.js +131 -21
  4. package/package.json +31 -6
  5. package/src/cli/args.js +197 -0
  6. package/src/cli/render.js +198 -0
  7. package/src/core/create-app.js +256 -0
  8. package/src/core/generate-app.js +155 -0
  9. package/src/core/generate-module.js +146 -0
  10. package/src/registry.js +180 -0
  11. package/src/spyne-file-prompt.js +77 -30
  12. package/src/templates/generate-prompt-input-fields.js +1 -1
  13. package/src/ui.js +5 -1
  14. package/lib/ansi.js +0 -116
  15. package/lib/combos.js +0 -75
  16. package/lib/completer.js +0 -52
  17. package/lib/interpolate.js +0 -266
  18. package/lib/keypress.js +0 -243
  19. package/lib/placeholder.js +0 -63
  20. package/lib/prompt.js +0 -485
  21. package/lib/prompts/autocomplete.js +0 -113
  22. package/lib/prompts/basicauth.js +0 -41
  23. package/lib/prompts/confirm.js +0 -13
  24. package/lib/prompts/editable.js +0 -136
  25. package/lib/prompts/form.js +0 -196
  26. package/lib/prompts/index.js +0 -28
  27. package/lib/prompts/input.js +0 -55
  28. package/lib/prompts/invisible.js +0 -11
  29. package/lib/prompts/list.js +0 -36
  30. package/lib/prompts/multiselect.js +0 -11
  31. package/lib/prompts/numeral.js +0 -1
  32. package/lib/prompts/password.js +0 -18
  33. package/lib/prompts/quiz.js +0 -37
  34. package/lib/prompts/scale.js +0 -237
  35. package/lib/prompts/select.js +0 -139
  36. package/lib/prompts/snippet.js +0 -185
  37. package/lib/prompts/sort.js +0 -37
  38. package/lib/prompts/survey.js +0 -163
  39. package/lib/prompts/text.js +0 -1
  40. package/lib/prompts/toggle.js +0 -109
  41. package/lib/render.js +0 -33
  42. package/lib/roles.js +0 -46
  43. package/lib/state.js +0 -69
  44. package/lib/styles.js +0 -144
  45. package/lib/symbols.js +0 -66
  46. package/lib/theme.js +0 -11
  47. package/lib/timer.js +0 -38
  48. package/lib/types/array.js +0 -658
  49. package/lib/types/auth.js +0 -29
  50. package/lib/types/boolean.js +0 -88
  51. package/lib/types/index.js +0 -7
  52. package/lib/types/number.js +0 -86
  53. package/lib/types/string.js +0 -185
  54. package/lib/utils.js +0 -268
  55. package/mocha.conf.cjs +0 -16
  56. package/src/app/channels/.gitkeep +0 -0
  57. package/src/app/channels/channel-custom.js +0 -1
  58. package/src/app/components/.gitkeep +0 -0
  59. package/src/app/traits/.gitkeep +0 -0
  60. package/src/hello-world-app-source/README.md +0 -15
  61. package/src/hello-world-app-source/apache-htaccess +0 -20
  62. package/src/hello-world-app-source/karma.conf.js +0 -75
  63. package/src/hello-world-app-source/package.json +0 -50
  64. package/src/hello-world-app-source/src/app/app-view.js +0 -38
  65. package/src/hello-world-app-source/src/app/channels/.gitkeep +0 -0
  66. package/src/hello-world-app-source/src/app/components/.gitkeep +0 -0
  67. package/src/hello-world-app-source/src/app/traits/.gitkeep +0 -0
  68. package/src/hello-world-app-source/src/index.js +0 -16
  69. package/src/hello-world-app-source/src/index.tmpl.html +0 -12
  70. package/src/hello-world-app-source/src/scss/01-vendors/normalize.css +0 -349
  71. package/src/hello-world-app-source/src/scss/02-variables/mixins.scss +0 -0
  72. package/src/hello-world-app-source/src/scss/03-components/.gitkeep +0 -0
  73. package/src/hello-world-app-source/src/scss/main.scss +0 -15
  74. package/src/hello-world-app-source/src/static/data/.gitkeep +0 -0
  75. package/src/hello-world-app-source/src/static/fonts/.gitkeep +0 -0
  76. package/src/hello-world-app-source/src/static/imgs/.gitkeep +0 -0
  77. package/src/hello-world-app-source/src/tests/unit-tests/index.test.js +0 -10
  78. package/src/hello-world-app-source/webpack.config.js +0 -178
  79. package/src/spyne-app-create.js +0 -209
  80. package/src/spyne-app-creator.js +0 -119
  81. package/src/spyne-starter-app-create.js +0 -147
  82. package/tests/create-spyne-app.test.js +0 -63
  83. package/tests/generate-file-prompt.test.js +0 -45
  84. package/tests/generate-file-string.test.js +0 -66
  85. package/tests/generate-prompt-input-fields-methods.test.js +0 -177
  86. package/tests/generate-prompt-input-fields.test.js +0 -179
  87. package/tests/generate-prompt-input-object.test.js +0 -39
  88. package/tests/generate-prompt-output.test.js +0 -58
  89. package/tests/index.test.js +0 -11
  90. package/tests/mocks/answers.js +0 -33
  91. package/tests/mocks/answers.json +0 -33
  92. package/tests/mocks/enquirer-data.js +0 -411
  93. package/tests/mocks/enquirer-data.json +0 -409
  94. package/tests/utils/file-utils.test.js +0 -84
@@ -0,0 +1,146 @@
1
+ // Core module generation. No terminal output — returns structured results.
2
+ // The CLI layer is responsible for rendering these.
3
+
4
+ import path from 'path';
5
+ import GenerateFileString from '../templates/generate-file-string.js';
6
+ import {detectSpyneProject} from './create-app.js';
7
+ import {getLocalFileDirectory, onSaveSpyneFileToDir} from '../utils/file-utils.js';
8
+ import {insertChannelStrings} from '../utils/insert-channel-strings-to-index-file.js';
9
+ import {Data} from '../spyne-template-prompts.js';
10
+
11
+ const {fileTypesArr} = Data;
12
+
13
+ const toPascalCase = (str) => String(str).
14
+ replace(/\.js$/, '').
15
+ split(/[-_\s]+/).
16
+ filter(Boolean).
17
+ map((s) => s.charAt(0).toUpperCase() + s.slice(1)).
18
+ join('');
19
+
20
+ const toConstantCase = (str) => {
21
+ const name = String(str).toLowerCase().startsWith('channel')
22
+ ? str
23
+ : `Channel${str}`;
24
+ return name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').
25
+ replace(/[-\s]+/g, '_').
26
+ toUpperCase();
27
+ };
28
+
29
+ const toMethodPrefix = (className) => {
30
+ const words = String(className).
31
+ replace(/([a-z0-9])([A-Z])/g, '$1 $2').
32
+ split(/[\s-_]+/).
33
+ filter(Boolean).
34
+ slice(0, 2);
35
+ const joined = words.map(
36
+ (w, i) => i === 0
37
+ ? w.toLowerCase()
38
+ : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
39
+ return `${joined}$`;
40
+ };
41
+
42
+ const withTrailingSlash = (dir) => dir.endsWith('/') ? dir : `${dir}/`;
43
+
44
+ /**
45
+ * Fill in the same defaults the interactive prompts would have offered, so a
46
+ * flags-only invocation produces identical output to the picker.
47
+ */
48
+ export const resolveModuleArgs = (args = {}) => {
49
+ const {fileType} = args;
50
+ const fileName = String(args.fileName).endsWith('.js')
51
+ ? args.fileName
52
+ : `${args.fileName}.js`;
53
+ const className = args.className || toPascalCase(fileName);
54
+ const fileDirectory = withTrailingSlash(
55
+ args.fileDirectory || getLocalFileDirectory(fileType));
56
+
57
+ const resolved = {fileType, fileName, className, fileDirectory};
58
+
59
+ if (fileType === 'Channel') {
60
+ resolved.channelName = args.channelName || toConstantCase(className);
61
+ resolved.replayLastPayload = args.replayLastPayload === true ||
62
+ args.replayLastPayload === 'true';
63
+ }
64
+
65
+ if (fileType === 'SpyneTrait') {
66
+ resolved.methodPrefix = args.methodPrefix || toMethodPrefix(className);
67
+ }
68
+
69
+ return resolved;
70
+ };
71
+
72
+ /**
73
+ * Generate a single Spyne module file.
74
+ *
75
+ * @param {Object} args
76
+ * @returns {Promise<Object>} structured result — never throws for expected
77
+ * failures (missing name, existing file); those surface on the result.
78
+ */
79
+ export async function generateModule(args = {}) {
80
+ if (!fileTypesArr.includes(args.fileType)) {
81
+ return {
82
+ ok: false,
83
+ error: {
84
+ code: 'INVALID_FILE_TYPE',
85
+ message: `Unknown file type "${args.fileType}". Expected one of: ${fileTypesArr.join(
86
+ ', ')}.`,
87
+ },
88
+ };
89
+ }
90
+
91
+ if (!args.fileName) {
92
+ return {
93
+ ok: false,
94
+ error: {code: 'MISSING_FILE_NAME', message: 'A file name is required.'},
95
+ };
96
+ }
97
+
98
+ // Modules generate into an existing project; an app does not exist yet.
99
+ if (args.requireProject !== false && !detectSpyneProject().isSpyneProject) {
100
+ return {
101
+ ok: false,
102
+ error: {
103
+ code: 'NOT_IN_SPYNE_PROJECT',
104
+ message: 'Run this inside a SpyneJS project. To start a new one: spyne-cli create-app <app-name>',
105
+ },
106
+ };
107
+ }
108
+
109
+ const resolved = resolveModuleArgs(args);
110
+ const {fileType, fileName, className, fileDirectory} = resolved;
111
+ const {fileString} = new GenerateFileString(fileType, resolved);
112
+ const saved = onSaveSpyneFileToDir(fileString, fileName, fileDirectory);
113
+
114
+ // The CLI prints the generated source for copy-paste when a write fails, so
115
+ // failure results carry the same payload a success would.
116
+ if (!saved.fileHasSaved) {
117
+ return {
118
+ ok: false,
119
+ error: {
120
+ code: saved.fileExists ? 'FILE_ALREADY_EXISTS' : 'WRITE_FAILED',
121
+ message: saved.fileExists
122
+ ? `${saved.fileDirPath} already exists.`
123
+ : `Could not write ${saved.fileDirPath}.`,
124
+ },
125
+ ...resolved,
126
+ filePath: saved.fileDirPath,
127
+ errorType: saved.errorType,
128
+ channelRegistered: false,
129
+ fileString,
130
+ };
131
+ }
132
+
133
+ let channelRegistered = false;
134
+ if (fileType === 'Channel') {
135
+ channelRegistered = insertChannelStrings(className, fileName).fileHasSaved;
136
+ }
137
+
138
+ return {
139
+ ok: true,
140
+ ...resolved,
141
+ filePath: saved.fileDirPath,
142
+ relativePath: path.relative(process.cwd(), saved.fileDirPath),
143
+ channelRegistered,
144
+ fileString,
145
+ };
146
+ }
@@ -0,0 +1,180 @@
1
+ // Single source of truth for every spyne-cli command.
2
+ //
3
+ // Three consumers read this registry: the interactive picker, the argv parser,
4
+ // and (in a later release) the SpyneJS MCP server. Adding a command here makes
5
+ // it available to all three — no other file needs to change.
6
+
7
+ import {createApp, DEFAULT_TEMPLATE, TEMPLATES} from './core/create-app.js';
8
+ import {generateModule} from './core/generate-module.js';
9
+
10
+ const templateValues = Object.keys(TEMPLATES);
11
+
12
+ const moduleArgs = (extra = {}) => ({
13
+ fileName: {
14
+ type: 'string',
15
+ positional: 0,
16
+ required: true,
17
+ description: 'File name for the generated module (.js is appended if omitted).',
18
+ },
19
+ className: {
20
+ type: 'string',
21
+ description: 'Class name. Defaults to the PascalCase form of the file name.',
22
+ },
23
+ fileDirectory: {
24
+ type: 'string',
25
+ alias: 'd',
26
+ description: 'Directory to write into. Defaults to the conventional path for this type.',
27
+ },
28
+ ...extra,
29
+ });
30
+
31
+ /**
32
+ * A generation target: interactive via the picker, deterministic via flags,
33
+ * enumerable programmatically.
34
+ *
35
+ * @typedef {Object} Command
36
+ * @property {string} name command name as typed
37
+ * @property {string} summary one line, shown in help and the picker
38
+ * @property {'app'|'module'} kind app creates a directory; module writes into a project
39
+ * @property {Object} args arg name -> schema
40
+ * @property {Function} handler async (args) => structured result
41
+ */
42
+ export const commands = [
43
+ {
44
+ name: 'create-app',
45
+ summary: 'Create a new SpyneJS application',
46
+ pickerLabel: 'App',
47
+ kind: 'app',
48
+ args: {
49
+ appName: {
50
+ type: 'string',
51
+ positional: 0,
52
+ required: true,
53
+ description: 'Directory name for the new application.',
54
+ },
55
+ template: {
56
+ type: 'string',
57
+ alias: 't',
58
+ enum: templateValues,
59
+ default: DEFAULT_TEMPLATE,
60
+ description: 'Template to scaffold from.',
61
+ },
62
+ prompt: {
63
+ type: 'string',
64
+ alias: 'p',
65
+ description: 'Site description — AI generates pages, routes, content, ' +
66
+ 'and images (shell template only).',
67
+ },
68
+ install: {
69
+ type: 'boolean',
70
+ default: true,
71
+ negatable: true,
72
+ description: 'Install dependencies after scaffolding.',
73
+ negatedDescription: 'Skip dependency installation.',
74
+ },
75
+ git: {
76
+ type: 'boolean',
77
+ default: true,
78
+ negatable: true,
79
+ description: 'Initialise a fresh git repository.',
80
+ negatedDescription: 'Skip git initialisation.',
81
+ },
82
+ },
83
+ handler: createApp,
84
+ },
85
+ {
86
+ name: 'create-viewstream',
87
+ summary: 'Generate a ViewStream class',
88
+ pickerLabel: 'ViewStream',
89
+ kind: 'module',
90
+ fileType: 'ViewStream',
91
+ args: moduleArgs(),
92
+ handler: (args) => generateModule({...args, fileType: 'ViewStream'}),
93
+ },
94
+ {
95
+ name: 'create-domelement',
96
+ summary: 'Generate a DomElement class',
97
+ pickerLabel: 'DomElement',
98
+ kind: 'module',
99
+ fileType: 'DomElement',
100
+ args: moduleArgs(),
101
+ handler: (args) => generateModule({...args, fileType: 'DomElement'}),
102
+ },
103
+ {
104
+ name: 'create-channel',
105
+ summary: 'Generate a Channel class and register it',
106
+ pickerLabel: 'Channel',
107
+ kind: 'module',
108
+ fileType: 'Channel',
109
+ args: moduleArgs({
110
+ channelName: {
111
+ type: 'string',
112
+ description: 'Channel name. Defaults to the CONSTANT_CASE form of the class name.',
113
+ },
114
+ replayLastPayload: {
115
+ type: 'boolean',
116
+ default: false,
117
+ description: 'Send the cached payload to new subscribers.',
118
+ },
119
+ }),
120
+ handler: (args) => generateModule({...args, fileType: 'Channel'}),
121
+ },
122
+ {
123
+ name: 'create-trait',
124
+ summary: 'Generate a SpyneTrait class',
125
+ pickerLabel: 'SpyneTrait',
126
+ kind: 'module',
127
+ fileType: 'SpyneTrait',
128
+ args: moduleArgs({
129
+ methodPrefix: {
130
+ type: 'string',
131
+ description: 'String prepended to all trait methods. Derived from the class name by default.',
132
+ },
133
+ }),
134
+ handler: (args) => generateModule({...args, fileType: 'SpyneTrait'}),
135
+ },
136
+ ];
137
+
138
+ // `new` shipped in 0.6.x and stays working; create-app is canonical.
139
+ export const aliases = {
140
+ new: {command: 'create-app', deprecated: true, since: '0.7.1'},
141
+ };
142
+
143
+ /**
144
+ * Flags that existed in 0.6.x and no longer do. Declared rather than deleted so
145
+ * the CLI can say what happened instead of reporting an unknown option.
146
+ */
147
+ export const removedFlags = {
148
+ spa: {
149
+ removedIn: '0.7.1',
150
+ message: '"--spa" has been removed. Pick a template with --template starter|shell.',
151
+ },
152
+ };
153
+
154
+ export const commandNames = commands.map((c) => c.name);
155
+
156
+ export const getCommand = (name) => {
157
+ if (!name) return undefined;
158
+ const alias = aliases[name];
159
+ const target = alias ? alias.command : name;
160
+ return commands.find((c) => c.name === target);
161
+ };
162
+
163
+ export const getAlias = (name) => aliases[name];
164
+
165
+ export const moduleCommands = commands.filter((c) => c.kind === 'module');
166
+
167
+ export const appCommands = commands.filter((c) => c.kind === 'app');
168
+
169
+ /**
170
+ * Registry as plain data — no handlers, no functions. This is the shape the
171
+ * MCP server will enumerate to expose each generation target as a capability.
172
+ */
173
+ export const describeCommands = () => commands.map(
174
+ ({name, summary, kind, args}) => ({
175
+ name,
176
+ summary,
177
+ kind,
178
+ args: Object.fromEntries(
179
+ Object.entries(args).map(([argName, schema]) => [argName, {...schema}])),
180
+ }));
@@ -1,14 +1,25 @@
1
1
  import enquirer from 'enquirer';
2
2
  const {prompt, Select} = enquirer;
3
+ import c from 'ansi-colors';
3
4
 
4
5
  import {Data} from './spyne-template-prompts.js';
5
6
  const {promptInputHash} = Data;
6
7
  import GeneratePromptInputObject from './templates/generate-prompt-input-object.js';
7
- import GenerateFileString from './templates/generate-file-string.js';
8
- import {onSaveSpyneFileToDir} from './utils/file-utils.js';
9
- import {generatePromptOutput} from './templates/generate-prompt-output.js';
10
- import {addChannelToIndexJS} from './utils/add-channel-to-index-file.js';
11
- import {insertChannelStrings} from './utils/insert-channel-strings-to-index-file.js';
8
+ import {appCommands, moduleCommands} from './registry.js';
9
+ import {generateModule} from './core/generate-module.js';
10
+ import {createApp, detectSpyneProject} from './core/create-app.js';
11
+ import {
12
+ appNamePrompt,
13
+ createProgressReporter,
14
+ renderAppResult,
15
+ renderError,
16
+ renderModuleResult,
17
+ selectSizing,
18
+ templatePrompt,
19
+ } from './cli/render.js';
20
+
21
+ // Picker labels come from the registry, so command messaging has one home.
22
+ const APP_LABELS = new Set(appCommands.map((cmd) => cmd.pickerLabel));
12
23
 
13
24
  export default class SpyneFilePrompt {
14
25
 
@@ -32,48 +43,84 @@ export default class SpyneFilePrompt {
32
43
  return props.map(mapInputTypesToPrompObj);
33
44
  }
34
45
 
46
+ /**
47
+ * The picker is a renderer over the registry: App first, then every module
48
+ * command in registry order.
49
+ */
35
50
  getSelectPromptObj() {
36
51
  const promptGen = new GeneratePromptInputObject('selectFileType');
37
- return promptGen.getPrompObject();
52
+ const promptObj = promptGen.getPrompObject();
38
53
 
54
+ promptObj.choices = [
55
+ ...appCommands.map((cmd) => ({
56
+ name: cmd.pickerLabel,
57
+ message: c.greenBright(cmd.pickerLabel),
58
+ hint: c.dim(`— ${cmd.summary}`),
59
+ })),
60
+ ...moduleCommands.map((cmd) => c.greenBright(cmd.pickerLabel)),
61
+ ];
62
+
63
+ Object.assign(promptObj, selectSizing(promptObj.choices.length));
64
+
65
+ return promptObj;
39
66
  }
40
67
 
41
- saveFileAndSendOutput(answers) {
42
- const {fileType, fileName, fileDirectory, className} = answers;
43
- const {fileString} = new GenerateFileString(fileType, answers);
44
- const savedProps = onSaveSpyneFileToDir(fileString, fileName, fileDirectory);
68
+ async saveFileAndSendOutput(answers) {
69
+ const result = await generateModule(answers);
45
70
 
46
- // default is no channel to be registered, yet
47
- let channelHasRegistered = false;
71
+ if (!result.ok && result.error.code === 'NOT_IN_SPYNE_PROJECT') {
72
+ console.log(renderError(result.error));
73
+ process.exitCode = 1;
74
+ return result;
75
+ }
48
76
 
49
- if (fileType === 'Channel'){
50
- //const savedChannelToIndexProps = addChannelToIndexJS(className, fileName);
51
- const savedChannelToIndexProps = insertChannelStrings(className, fileName);
77
+ console.log(renderModuleResult(result));
78
+ if (!result.ok) process.exitCode = 1;
79
+ return result;
80
+ }
52
81
 
53
- // check if channel has been registered
54
- channelHasRegistered = savedChannelToIndexProps.fileHasSaved;
82
+ async runAppPrompt() {
83
+ const {isSpyneProject, root} = detectSpyneProject();
84
+
85
+ if (isSpyneProject) {
86
+ const {proceed} = await prompt({
87
+ type: 'confirm',
88
+ name: 'proceed',
89
+ initial: false,
90
+ message: c.yellow(
91
+ `This creates a new app inside an existing SpyneJS project (${root}) — continue?`),
92
+ });
93
+ if (!proceed) return undefined;
55
94
  }
56
95
 
57
- const msgOutput = generatePromptOutput(answers, savedProps, fileString, channelHasRegistered);
96
+ // Template first: the general choice before the specific one.
97
+ const answers = await prompt([templatePrompt(), appNamePrompt()]);
58
98
 
99
+ const result = await createApp({
100
+ ...answers,
101
+ onProgress: createProgressReporter(),
102
+ });
59
103
 
60
- console.log(msgOutput);
104
+ console.log(result.ok
105
+ ? renderAppResult(result)
106
+ : renderError(result.error));
107
+
108
+ if (!result.ok) process.exitCode = 1;
109
+ return result;
61
110
  }
62
111
 
63
112
  async startPrompt() {
64
- const selectPromptObj = this.getSelectPromptObj();
65
- //console.log('select prompt obj is ',selectPromptObj);
66
- const selectPrompt = new Select(selectPromptObj);
67
- const onSelectComplete = async (fileType) => {
68
- const filePromptArr = SpyneFilePrompt.getFilePrompt(fileType);
69
- //console.log("promp arr is ",filePromptArr);
70
- const filePrompt = await prompt(filePromptArr);
71
- const answersObj = Object.assign({}, filePrompt, {fileType});
72
- this.saveFileAndSendOutput(answersObj);
73
- };
113
+ const selectPrompt = new Select(this.getSelectPromptObj());
114
+ const selection = await selectPrompt.run();
74
115
 
75
- selectPrompt.run().then(onSelectComplete);
116
+ if (APP_LABELS.has(selection)) {
117
+ return this.runAppPrompt();
118
+ }
76
119
 
120
+ const filePromptArr = SpyneFilePrompt.getFilePrompt(selection);
121
+ const filePrompt = await prompt(filePromptArr);
122
+ return this.saveFileAndSendOutput(
123
+ Object.assign({}, filePrompt, {fileType: selection}));
77
124
  }
78
125
 
79
126
  };
@@ -174,7 +174,7 @@ const generateMesage = () => {
174
174
  const defaultMsgFn = messageColor(`Enter the ${_fileType} file name`);
175
175
 
176
176
  const inputTypeHash = {
177
- selectFileType: messageColor(`Select file type`),
177
+ selectFileType: messageColor(`What would you like to create?`),
178
178
  fileName: defaultMsgFn,
179
179
  className: messageColor(`Enter the ${_fileType} class name`),
180
180
  fileDirectory: messageColor(`Enter where you would like to save the ${_fileType} file`),
package/src/ui.js CHANGED
@@ -1,11 +1,15 @@
1
1
  import figlet from 'figlet';
2
2
  import chalk from 'chalk';
3
+ import { readFileSync } from 'fs';
4
+
5
+ const { version } = JSON.parse(
6
+ readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
3
7
 
4
8
  export class SpyneCliUI {
5
9
 
6
10
  constructor() {}
7
11
  static title(){
8
- const figletTxt = figlet.textSync('spyne-cli 6.8', {
12
+ const figletTxt = figlet.textSync(`spyne-cli ${version}`, {
9
13
  horizontalLayout: 'universal smushing'
10
14
  })
11
15
  const chalkOutput = chalk.blue(figletTxt);
package/lib/ansi.js DELETED
@@ -1,116 +0,0 @@
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 DELETED
@@ -1,75 +0,0 @@
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
- };