spyne-cli 0.6.8 → 0.7.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.
- package/LICENSE +661 -0
- package/README.md +80 -30
- package/index.js +123 -21
- package/package.json +24 -6
- package/src/cli/args.js +197 -0
- package/src/cli/render.js +184 -0
- package/src/core/create-app.js +232 -0
- package/src/core/generate-module.js +146 -0
- package/src/registry.js +174 -0
- package/src/spyne-file-prompt.js +77 -30
- package/src/templates/generate-prompt-input-fields.js +1 -1
- package/src/ui.js +5 -1
- package/lib/ansi.js +0 -116
- package/lib/combos.js +0 -75
- package/lib/completer.js +0 -52
- package/lib/interpolate.js +0 -266
- package/lib/keypress.js +0 -243
- package/lib/placeholder.js +0 -63
- package/lib/prompt.js +0 -485
- package/lib/prompts/autocomplete.js +0 -113
- package/lib/prompts/basicauth.js +0 -41
- package/lib/prompts/confirm.js +0 -13
- package/lib/prompts/editable.js +0 -136
- package/lib/prompts/form.js +0 -196
- package/lib/prompts/index.js +0 -28
- package/lib/prompts/input.js +0 -55
- package/lib/prompts/invisible.js +0 -11
- package/lib/prompts/list.js +0 -36
- package/lib/prompts/multiselect.js +0 -11
- package/lib/prompts/numeral.js +0 -1
- package/lib/prompts/password.js +0 -18
- package/lib/prompts/quiz.js +0 -37
- package/lib/prompts/scale.js +0 -237
- package/lib/prompts/select.js +0 -139
- package/lib/prompts/snippet.js +0 -185
- package/lib/prompts/sort.js +0 -37
- package/lib/prompts/survey.js +0 -163
- package/lib/prompts/text.js +0 -1
- package/lib/prompts/toggle.js +0 -109
- package/lib/render.js +0 -33
- package/lib/roles.js +0 -46
- package/lib/state.js +0 -69
- package/lib/styles.js +0 -144
- package/lib/symbols.js +0 -66
- package/lib/theme.js +0 -11
- package/lib/timer.js +0 -38
- package/lib/types/array.js +0 -658
- package/lib/types/auth.js +0 -29
- package/lib/types/boolean.js +0 -88
- package/lib/types/index.js +0 -7
- package/lib/types/number.js +0 -86
- package/lib/types/string.js +0 -185
- package/lib/utils.js +0 -268
- package/mocha.conf.cjs +0 -16
- package/src/app/channels/.gitkeep +0 -0
- package/src/app/channels/channel-custom.js +0 -1
- package/src/app/components/.gitkeep +0 -0
- package/src/app/traits/.gitkeep +0 -0
- package/src/hello-world-app-source/README.md +0 -15
- package/src/hello-world-app-source/apache-htaccess +0 -20
- package/src/hello-world-app-source/karma.conf.js +0 -75
- package/src/hello-world-app-source/package.json +0 -50
- package/src/hello-world-app-source/src/app/app-view.js +0 -38
- package/src/hello-world-app-source/src/app/channels/.gitkeep +0 -0
- package/src/hello-world-app-source/src/app/components/.gitkeep +0 -0
- package/src/hello-world-app-source/src/app/traits/.gitkeep +0 -0
- package/src/hello-world-app-source/src/index.js +0 -16
- package/src/hello-world-app-source/src/index.tmpl.html +0 -12
- package/src/hello-world-app-source/src/scss/01-vendors/normalize.css +0 -349
- package/src/hello-world-app-source/src/scss/02-variables/mixins.scss +0 -0
- package/src/hello-world-app-source/src/scss/03-components/.gitkeep +0 -0
- package/src/hello-world-app-source/src/scss/main.scss +0 -15
- package/src/hello-world-app-source/src/static/data/.gitkeep +0 -0
- package/src/hello-world-app-source/src/static/fonts/.gitkeep +0 -0
- package/src/hello-world-app-source/src/static/imgs/.gitkeep +0 -0
- package/src/hello-world-app-source/src/tests/unit-tests/index.test.js +0 -10
- package/src/hello-world-app-source/webpack.config.js +0 -178
- package/src/spyne-app-create.js +0 -209
- package/src/spyne-app-creator.js +0 -119
- package/src/spyne-starter-app-create.js +0 -147
- package/tests/create-spyne-app.test.js +0 -63
- package/tests/generate-file-prompt.test.js +0 -45
- package/tests/generate-file-string.test.js +0 -66
- package/tests/generate-prompt-input-fields-methods.test.js +0 -177
- package/tests/generate-prompt-input-fields.test.js +0 -179
- package/tests/generate-prompt-input-object.test.js +0 -39
- package/tests/generate-prompt-output.test.js +0 -58
- package/tests/index.test.js +0 -11
- package/tests/mocks/answers.js +0 -33
- package/tests/mocks/answers.json +0 -33
- package/tests/mocks/enquirer-data.js +0 -411
- package/tests/mocks/enquirer-data.json +0 -409
- package/tests/utils/file-utils.test.js +0 -84
package/src/registry.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
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
|
+
install: {
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
default: true,
|
|
65
|
+
negatable: true,
|
|
66
|
+
description: 'Install dependencies after scaffolding.',
|
|
67
|
+
negatedDescription: 'Skip dependency installation.',
|
|
68
|
+
},
|
|
69
|
+
git: {
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
default: true,
|
|
72
|
+
negatable: true,
|
|
73
|
+
description: 'Initialise a fresh git repository.',
|
|
74
|
+
negatedDescription: 'Skip git initialisation.',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
handler: createApp,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'create-viewstream',
|
|
81
|
+
summary: 'Generate a ViewStream class',
|
|
82
|
+
pickerLabel: 'ViewStream',
|
|
83
|
+
kind: 'module',
|
|
84
|
+
fileType: 'ViewStream',
|
|
85
|
+
args: moduleArgs(),
|
|
86
|
+
handler: (args) => generateModule({...args, fileType: 'ViewStream'}),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'create-domelement',
|
|
90
|
+
summary: 'Generate a DomElement class',
|
|
91
|
+
pickerLabel: 'DomElement',
|
|
92
|
+
kind: 'module',
|
|
93
|
+
fileType: 'DomElement',
|
|
94
|
+
args: moduleArgs(),
|
|
95
|
+
handler: (args) => generateModule({...args, fileType: 'DomElement'}),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'create-channel',
|
|
99
|
+
summary: 'Generate a Channel class and register it',
|
|
100
|
+
pickerLabel: 'Channel',
|
|
101
|
+
kind: 'module',
|
|
102
|
+
fileType: 'Channel',
|
|
103
|
+
args: moduleArgs({
|
|
104
|
+
channelName: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
description: 'Channel name. Defaults to the CONSTANT_CASE form of the class name.',
|
|
107
|
+
},
|
|
108
|
+
replayLastPayload: {
|
|
109
|
+
type: 'boolean',
|
|
110
|
+
default: false,
|
|
111
|
+
description: 'Send the cached payload to new subscribers.',
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
114
|
+
handler: (args) => generateModule({...args, fileType: 'Channel'}),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'create-trait',
|
|
118
|
+
summary: 'Generate a SpyneTrait class',
|
|
119
|
+
pickerLabel: 'SpyneTrait',
|
|
120
|
+
kind: 'module',
|
|
121
|
+
fileType: 'SpyneTrait',
|
|
122
|
+
args: moduleArgs({
|
|
123
|
+
methodPrefix: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
description: 'String prepended to all trait methods. Derived from the class name by default.',
|
|
126
|
+
},
|
|
127
|
+
}),
|
|
128
|
+
handler: (args) => generateModule({...args, fileType: 'SpyneTrait'}),
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
// `new` shipped in 0.6.x and stays working; create-app is canonical.
|
|
133
|
+
export const aliases = {
|
|
134
|
+
new: {command: 'create-app', deprecated: true, since: '0.7.0'},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Flags that existed in 0.6.x and no longer do. Declared rather than deleted so
|
|
139
|
+
* the CLI can say what happened instead of reporting an unknown option.
|
|
140
|
+
*/
|
|
141
|
+
export const removedFlags = {
|
|
142
|
+
spa: {
|
|
143
|
+
removedIn: '0.7.0',
|
|
144
|
+
message: '"--spa" has been removed. Pick a template with --template starter|shell.',
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const commandNames = commands.map((c) => c.name);
|
|
149
|
+
|
|
150
|
+
export const getCommand = (name) => {
|
|
151
|
+
if (!name) return undefined;
|
|
152
|
+
const alias = aliases[name];
|
|
153
|
+
const target = alias ? alias.command : name;
|
|
154
|
+
return commands.find((c) => c.name === target);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const getAlias = (name) => aliases[name];
|
|
158
|
+
|
|
159
|
+
export const moduleCommands = commands.filter((c) => c.kind === 'module');
|
|
160
|
+
|
|
161
|
+
export const appCommands = commands.filter((c) => c.kind === 'app');
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Registry as plain data — no handlers, no functions. This is the shape the
|
|
165
|
+
* MCP server will enumerate to expose each generation target as a capability.
|
|
166
|
+
*/
|
|
167
|
+
export const describeCommands = () => commands.map(
|
|
168
|
+
({name, summary, kind, args}) => ({
|
|
169
|
+
name,
|
|
170
|
+
summary,
|
|
171
|
+
kind,
|
|
172
|
+
args: Object.fromEntries(
|
|
173
|
+
Object.entries(args).map(([argName, schema]) => [argName, {...schema}])),
|
|
174
|
+
}));
|
package/src/spyne-file-prompt.js
CHANGED
|
@@ -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
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
77
|
+
console.log(renderModuleResult(result));
|
|
78
|
+
if (!result.ok) process.exitCode = 1;
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
52
81
|
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
65
|
-
|
|
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
|
-
|
|
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(`
|
|
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(
|
|
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
|
-
};
|
package/lib/completer.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
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
|
-
};
|