bunosh 0.1.4 → 0.2.2
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/COMPLETION.md +214 -0
- package/README.md +489 -73
- package/UPGRADE.md +200 -0
- package/bunosh.js +56 -0
- package/index.js +22 -11
- package/package.json +32 -24
- package/src/completion.js +341 -0
- package/src/font.js +258 -0
- package/src/formatters/base.js +17 -0
- package/src/formatters/console.js +81 -0
- package/src/formatters/factory.js +17 -0
- package/src/formatters/github-actions.js +43 -0
- package/src/init.js +13 -6
- package/src/io.js +20 -0
- package/src/printer.js +91 -0
- package/src/program.js +374 -154
- package/src/task.js +148 -0
- package/src/tasks/copyFile.js +21 -0
- package/src/tasks/exec.js +204 -0
- package/src/tasks/fetch.js +47 -0
- package/src/tasks/{writeToFile.jsx → writeToFile.js} +18 -16
- package/src/upgrade.js +255 -0
- package/types.d.ts +44 -0
- package/run.js +0 -31
- package/src/io.jsx +0 -47
- package/src/output.js +0 -37
- package/src/task.jsx +0 -209
- package/src/tasks/copyFile.jsx +0 -14
- package/src/tasks/exec.jsx +0 -104
- package/src/tasks/fetch.jsx +0 -74
- package/templates/banner.js +0 -8
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { BUNOSHFILE } from './program.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates shell completion scripts for bunosh commands
|
|
7
|
+
*/
|
|
8
|
+
export class CompletionGenerator {
|
|
9
|
+
constructor(commands = []) {
|
|
10
|
+
this.commands = commands;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generates bash completion script
|
|
15
|
+
*/
|
|
16
|
+
generateBashCompletion() {
|
|
17
|
+
const commandList = this.commands.map(cmd => cmd.name).join(' ');
|
|
18
|
+
|
|
19
|
+
return `#!/bin/bash
|
|
20
|
+
|
|
21
|
+
# Bash completion for bunosh
|
|
22
|
+
_bunosh_completion() {
|
|
23
|
+
local cur prev opts
|
|
24
|
+
COMPREPLY=()
|
|
25
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
26
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
27
|
+
|
|
28
|
+
# Available commands
|
|
29
|
+
opts="${commandList} --help --version init completion"
|
|
30
|
+
|
|
31
|
+
# Special handling for specific commands
|
|
32
|
+
case "\${prev}" in
|
|
33
|
+
bunosh)
|
|
34
|
+
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
35
|
+
return 0
|
|
36
|
+
;;
|
|
37
|
+
completion)
|
|
38
|
+
COMPREPLY=( $(compgen -W "bash zsh fish" -- \${cur}) )
|
|
39
|
+
return 0
|
|
40
|
+
;;
|
|
41
|
+
init)
|
|
42
|
+
# No completion for init
|
|
43
|
+
return 0
|
|
44
|
+
;;
|
|
45
|
+
*)
|
|
46
|
+
# Default completion for other commands
|
|
47
|
+
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
48
|
+
return 0
|
|
49
|
+
;;
|
|
50
|
+
esac
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Register the completion function
|
|
54
|
+
complete -F _bunosh_completion bunosh
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generates zsh completion script
|
|
60
|
+
*/
|
|
61
|
+
generateZshCompletion() {
|
|
62
|
+
const commandCompletions = this.commands.map(cmd => {
|
|
63
|
+
const args = cmd.args ? cmd.args.map(arg => `'${arg}'`).join(' ') : '';
|
|
64
|
+
const desc = cmd.description ? cmd.description.replace(/'/g, "\\'") : '';
|
|
65
|
+
return ` '${cmd.name}[${desc}]${args ? ':' + args : ''}'`;
|
|
66
|
+
}).join('\n');
|
|
67
|
+
|
|
68
|
+
return `#compdef bunosh
|
|
69
|
+
|
|
70
|
+
# Zsh completion for bunosh
|
|
71
|
+
_bunosh() {
|
|
72
|
+
local context state line
|
|
73
|
+
typeset -A opt_args
|
|
74
|
+
|
|
75
|
+
_arguments -C \\
|
|
76
|
+
'1: :_bunosh_commands' \\
|
|
77
|
+
'*::arg:->args'
|
|
78
|
+
|
|
79
|
+
case $state in
|
|
80
|
+
args)
|
|
81
|
+
case $line[1] in
|
|
82
|
+
completion)
|
|
83
|
+
_arguments \\
|
|
84
|
+
'1: :(bash zsh fish)'
|
|
85
|
+
;;
|
|
86
|
+
init)
|
|
87
|
+
# No arguments for init
|
|
88
|
+
;;
|
|
89
|
+
*)
|
|
90
|
+
# Default argument completion
|
|
91
|
+
_files
|
|
92
|
+
;;
|
|
93
|
+
esac
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_bunosh_commands() {
|
|
99
|
+
local commands
|
|
100
|
+
commands=(
|
|
101
|
+
${commandCompletions}
|
|
102
|
+
'completion[Generate shell completion scripts]'
|
|
103
|
+
'init[Create a new Bunoshfile.js]'
|
|
104
|
+
'--help[Show help information]'
|
|
105
|
+
'--version[Show version information]'
|
|
106
|
+
)
|
|
107
|
+
_describe 'commands' commands
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_bunosh
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generates fish completion script
|
|
116
|
+
*/
|
|
117
|
+
generateFishCompletion() {
|
|
118
|
+
const commandCompletions = this.commands.map(cmd => {
|
|
119
|
+
const desc = cmd.description ? cmd.description : '';
|
|
120
|
+
return `complete -c bunosh -f -a "${cmd.name}" -d "${desc}"`;
|
|
121
|
+
}).join('\n');
|
|
122
|
+
|
|
123
|
+
return `# Fish completion for bunosh
|
|
124
|
+
|
|
125
|
+
# Basic commands
|
|
126
|
+
${commandCompletions}
|
|
127
|
+
complete -c bunosh -f -a "completion" -d "Generate shell completion scripts"
|
|
128
|
+
complete -c bunosh -f -a "init" -d "Create a new Bunoshfile.js"
|
|
129
|
+
complete -c bunosh -f -l help -d "Show help information"
|
|
130
|
+
complete -c bunosh -f -l version -d "Show version information"
|
|
131
|
+
|
|
132
|
+
# Completion for completion command
|
|
133
|
+
complete -c bunosh -f -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Extracts commands from the current Bunoshfile for completion
|
|
140
|
+
*/
|
|
141
|
+
export function getCompletionCommands() {
|
|
142
|
+
try {
|
|
143
|
+
if (!fs.existsSync(BUNOSHFILE)) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const source = fs.readFileSync(BUNOSHFILE, 'utf8');
|
|
148
|
+
|
|
149
|
+
// Simple regex to extract export function names and comments
|
|
150
|
+
const functionRegex = /\/\*\*\s*\n\s*\*\s*(.+?)\s*\n[\s\S]*?\*\/\s*\n\s*export\s+(?:async\s+)?function\s+(\w+)/g;
|
|
151
|
+
const commands = [];
|
|
152
|
+
let match;
|
|
153
|
+
|
|
154
|
+
while ((match = functionRegex.exec(source)) !== null) {
|
|
155
|
+
const [, description, functionName] = match;
|
|
156
|
+
const commandName = prepareCommandName(functionName);
|
|
157
|
+
|
|
158
|
+
commands.push({
|
|
159
|
+
name: commandName,
|
|
160
|
+
description: description.trim(),
|
|
161
|
+
functionName
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Also check for simple exports without JSDoc
|
|
166
|
+
const simpleExportRegex = /export\s+(?:async\s+)?function\s+(\w+)/g;
|
|
167
|
+
while ((match = simpleExportRegex.exec(source)) !== null) {
|
|
168
|
+
const [, functionName] = match;
|
|
169
|
+
const commandName = prepareCommandName(functionName);
|
|
170
|
+
|
|
171
|
+
// Don't add duplicates
|
|
172
|
+
if (!commands.find(cmd => cmd.name === commandName)) {
|
|
173
|
+
commands.push({
|
|
174
|
+
name: commandName,
|
|
175
|
+
description: '',
|
|
176
|
+
functionName
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return commands.sort((a, b) => a.name.localeCompare(b.name));
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Converts function name to command name (same logic as program.js)
|
|
189
|
+
*/
|
|
190
|
+
function prepareCommandName(name) {
|
|
191
|
+
name = name
|
|
192
|
+
.split(/(?=[A-Z])/)
|
|
193
|
+
.join("-")
|
|
194
|
+
.toLowerCase();
|
|
195
|
+
return name.replace("-", ":");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Detects the current shell from environment
|
|
200
|
+
*/
|
|
201
|
+
export function detectCurrentShell() {
|
|
202
|
+
// Check SHELL environment variable
|
|
203
|
+
const shellPath = process.env.SHELL;
|
|
204
|
+
if (shellPath) {
|
|
205
|
+
const shellName = path.basename(shellPath);
|
|
206
|
+
if (['bash', 'zsh', 'fish'].includes(shellName)) {
|
|
207
|
+
return shellName;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check parent process name (for cases where SHELL might not be set correctly)
|
|
212
|
+
try {
|
|
213
|
+
const { execSync } = require('child_process');
|
|
214
|
+
const parentProcess = execSync('ps -p $PPID -o comm=', { encoding: 'utf8' }).trim();
|
|
215
|
+
if (['bash', 'zsh', 'fish'].includes(parentProcess)) {
|
|
216
|
+
return parentProcess;
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
// Ignore errors, fall back to SHELL variable
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Gets the appropriate paths for shell completion files
|
|
227
|
+
*/
|
|
228
|
+
export function getCompletionPaths(shell) {
|
|
229
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
230
|
+
|
|
231
|
+
switch (shell) {
|
|
232
|
+
case 'bash':
|
|
233
|
+
return {
|
|
234
|
+
completionFile: path.join(homeDir, '.bunosh-completion.bash'),
|
|
235
|
+
configFiles: [
|
|
236
|
+
path.join(homeDir, '.bashrc'),
|
|
237
|
+
path.join(homeDir, '.bash_profile'),
|
|
238
|
+
path.join(homeDir, '.profile')
|
|
239
|
+
],
|
|
240
|
+
sourceCommand: 'source ~/.bunosh-completion.bash'
|
|
241
|
+
};
|
|
242
|
+
case 'zsh':
|
|
243
|
+
return {
|
|
244
|
+
completionFile: path.join(homeDir, '.bunosh-completion.zsh'),
|
|
245
|
+
configFiles: [
|
|
246
|
+
path.join(homeDir, '.zshrc')
|
|
247
|
+
],
|
|
248
|
+
sourceCommand: 'source ~/.bunosh-completion.zsh'
|
|
249
|
+
};
|
|
250
|
+
case 'fish':
|
|
251
|
+
const fishConfigDir = path.join(homeDir, '.config', 'fish');
|
|
252
|
+
return {
|
|
253
|
+
completionFile: path.join(fishConfigDir, 'completions', 'bunosh.fish'),
|
|
254
|
+
configFiles: [], // Fish doesn't need config file modification
|
|
255
|
+
sourceCommand: null // Fish loads completions automatically
|
|
256
|
+
};
|
|
257
|
+
default:
|
|
258
|
+
throw new Error(`Unsupported shell: ${shell}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Installs completion for the specified shell
|
|
264
|
+
*/
|
|
265
|
+
export function installCompletion(shell) {
|
|
266
|
+
const commands = getCompletionCommands();
|
|
267
|
+
const generator = new CompletionGenerator(commands);
|
|
268
|
+
const paths = getCompletionPaths(shell);
|
|
269
|
+
|
|
270
|
+
// Generate completion script
|
|
271
|
+
let completionScript;
|
|
272
|
+
switch (shell) {
|
|
273
|
+
case 'bash':
|
|
274
|
+
completionScript = generator.generateBashCompletion();
|
|
275
|
+
break;
|
|
276
|
+
case 'zsh':
|
|
277
|
+
completionScript = generator.generateZshCompletion();
|
|
278
|
+
break;
|
|
279
|
+
case 'fish':
|
|
280
|
+
completionScript = generator.generateFishCompletion();
|
|
281
|
+
break;
|
|
282
|
+
default:
|
|
283
|
+
throw new Error(`Unsupported shell: ${shell}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Ensure directory exists
|
|
287
|
+
const completionDir = path.dirname(paths.completionFile);
|
|
288
|
+
if (!fs.existsSync(completionDir)) {
|
|
289
|
+
fs.mkdirSync(completionDir, { recursive: true });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Write completion file
|
|
293
|
+
fs.writeFileSync(paths.completionFile, completionScript);
|
|
294
|
+
|
|
295
|
+
// For bash and zsh, add source command to config file if not present
|
|
296
|
+
if (paths.sourceCommand && paths.configFiles.length > 0) {
|
|
297
|
+
const sourceCommandWithComment = `
|
|
298
|
+
# Bunosh completion
|
|
299
|
+
if [ -f ${paths.completionFile} ]; then
|
|
300
|
+
${paths.sourceCommand}
|
|
301
|
+
fi`;
|
|
302
|
+
|
|
303
|
+
// Find the first existing config file, or use the first option
|
|
304
|
+
let configFile = paths.configFiles.find(f => fs.existsSync(f));
|
|
305
|
+
if (!configFile) {
|
|
306
|
+
configFile = paths.configFiles[0];
|
|
307
|
+
// Create the file if it doesn't exist
|
|
308
|
+
fs.writeFileSync(configFile, '');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Check if already configured
|
|
312
|
+
const configContent = fs.readFileSync(configFile, 'utf8');
|
|
313
|
+
if (!configContent.includes('bunosh-completion') && !configContent.includes(paths.sourceCommand)) {
|
|
314
|
+
fs.appendFileSync(configFile, sourceCommandWithComment);
|
|
315
|
+
return { configFile, added: true };
|
|
316
|
+
} else {
|
|
317
|
+
return { configFile, added: false };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return { configFile: null, added: false };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Main completion handler
|
|
326
|
+
*/
|
|
327
|
+
export function handleCompletion(shell) {
|
|
328
|
+
const commands = getCompletionCommands();
|
|
329
|
+
const generator = new CompletionGenerator(commands);
|
|
330
|
+
|
|
331
|
+
switch (shell) {
|
|
332
|
+
case 'bash':
|
|
333
|
+
return generator.generateBashCompletion();
|
|
334
|
+
case 'zsh':
|
|
335
|
+
return generator.generateZshCompletion();
|
|
336
|
+
case 'fish':
|
|
337
|
+
return generator.generateFishCompletion();
|
|
338
|
+
default:
|
|
339
|
+
throw new Error(`Unsupported shell: ${shell}. Supported shells: bash, zsh, fish`);
|
|
340
|
+
}
|
|
341
|
+
}
|
package/src/font.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
|
|
2
|
+
// 5x5 ASCII "font". Use '#' for pixels and '.' for background.
|
|
3
|
+
// Lowercase letters are rendered via their uppercase equivalents.
|
|
4
|
+
const FONT5 = {
|
|
5
|
+
'A': [
|
|
6
|
+
".###.",
|
|
7
|
+
"#...#",
|
|
8
|
+
"#####",
|
|
9
|
+
"#...#",
|
|
10
|
+
"#...#",
|
|
11
|
+
],
|
|
12
|
+
'B': [
|
|
13
|
+
"####.",
|
|
14
|
+
"#...#",
|
|
15
|
+
"####.",
|
|
16
|
+
"#...#",
|
|
17
|
+
"####.",
|
|
18
|
+
],
|
|
19
|
+
'C': [
|
|
20
|
+
".####",
|
|
21
|
+
"#....",
|
|
22
|
+
"#....",
|
|
23
|
+
"#....",
|
|
24
|
+
".####",
|
|
25
|
+
],
|
|
26
|
+
'D': [
|
|
27
|
+
"####.",
|
|
28
|
+
"#...#",
|
|
29
|
+
"#...#",
|
|
30
|
+
"#...#",
|
|
31
|
+
"####.",
|
|
32
|
+
],
|
|
33
|
+
'E': [
|
|
34
|
+
"#####",
|
|
35
|
+
"#....",
|
|
36
|
+
"####.",
|
|
37
|
+
"#....",
|
|
38
|
+
"#####",
|
|
39
|
+
],
|
|
40
|
+
'F': [
|
|
41
|
+
"#####",
|
|
42
|
+
"#....",
|
|
43
|
+
"####.",
|
|
44
|
+
"#....",
|
|
45
|
+
"#....",
|
|
46
|
+
],
|
|
47
|
+
'G': [
|
|
48
|
+
".####",
|
|
49
|
+
"#....",
|
|
50
|
+
"#.###",
|
|
51
|
+
"#...#",
|
|
52
|
+
".####",
|
|
53
|
+
],
|
|
54
|
+
'H': [
|
|
55
|
+
"#...#",
|
|
56
|
+
"#...#",
|
|
57
|
+
"#####",
|
|
58
|
+
"#...#",
|
|
59
|
+
"#...#",
|
|
60
|
+
],
|
|
61
|
+
'I': [
|
|
62
|
+
"#####",
|
|
63
|
+
"..#..",
|
|
64
|
+
"..#..",
|
|
65
|
+
"..#..",
|
|
66
|
+
"#####",
|
|
67
|
+
],
|
|
68
|
+
'J': [
|
|
69
|
+
"#####",
|
|
70
|
+
"...#.",
|
|
71
|
+
"...#.",
|
|
72
|
+
"#..#.",
|
|
73
|
+
".##..",
|
|
74
|
+
],
|
|
75
|
+
'K': [
|
|
76
|
+
"#...#",
|
|
77
|
+
"#..#.",
|
|
78
|
+
"###..",
|
|
79
|
+
"#..#.",
|
|
80
|
+
"#...#",
|
|
81
|
+
],
|
|
82
|
+
'L': [
|
|
83
|
+
"#....",
|
|
84
|
+
"#....",
|
|
85
|
+
"#....",
|
|
86
|
+
"#....",
|
|
87
|
+
"#####",
|
|
88
|
+
],
|
|
89
|
+
'M': [
|
|
90
|
+
"#...#",
|
|
91
|
+
"##.##",
|
|
92
|
+
"#.#.#",
|
|
93
|
+
"#...#",
|
|
94
|
+
"#...#",
|
|
95
|
+
],
|
|
96
|
+
'N': [
|
|
97
|
+
"#...#",
|
|
98
|
+
"##..#",
|
|
99
|
+
"#.#.#",
|
|
100
|
+
"#..##",
|
|
101
|
+
"#...#",
|
|
102
|
+
],
|
|
103
|
+
'O': [
|
|
104
|
+
".###.",
|
|
105
|
+
"#...#",
|
|
106
|
+
"#...#",
|
|
107
|
+
"#...#",
|
|
108
|
+
".###.",
|
|
109
|
+
],
|
|
110
|
+
'P': [
|
|
111
|
+
"####.",
|
|
112
|
+
"#...#",
|
|
113
|
+
"####.",
|
|
114
|
+
"#....",
|
|
115
|
+
"#....",
|
|
116
|
+
],
|
|
117
|
+
'Q': [
|
|
118
|
+
".###.",
|
|
119
|
+
"#...#",
|
|
120
|
+
"#...#",
|
|
121
|
+
"#..##",
|
|
122
|
+
".####",
|
|
123
|
+
],
|
|
124
|
+
'R': [
|
|
125
|
+
"####.",
|
|
126
|
+
"#...#",
|
|
127
|
+
"####.",
|
|
128
|
+
"#..#.",
|
|
129
|
+
"#...#",
|
|
130
|
+
],
|
|
131
|
+
'S': [
|
|
132
|
+
".####",
|
|
133
|
+
"#....",
|
|
134
|
+
".###.",
|
|
135
|
+
"....#",
|
|
136
|
+
"####.",
|
|
137
|
+
],
|
|
138
|
+
'T': [
|
|
139
|
+
"#####",
|
|
140
|
+
"..#..",
|
|
141
|
+
"..#..",
|
|
142
|
+
"..#..",
|
|
143
|
+
"..#..",
|
|
144
|
+
],
|
|
145
|
+
'U': [
|
|
146
|
+
"#...#",
|
|
147
|
+
"#...#",
|
|
148
|
+
"#...#",
|
|
149
|
+
"#...#",
|
|
150
|
+
".###.",
|
|
151
|
+
],
|
|
152
|
+
'V': [
|
|
153
|
+
"#...#",
|
|
154
|
+
"#...#",
|
|
155
|
+
"#...#",
|
|
156
|
+
".#.#.",
|
|
157
|
+
"..#..",
|
|
158
|
+
],
|
|
159
|
+
'W': [
|
|
160
|
+
"#...#",
|
|
161
|
+
"#...#",
|
|
162
|
+
"#.#.#",
|
|
163
|
+
"##.##",
|
|
164
|
+
"#...#",
|
|
165
|
+
],
|
|
166
|
+
'X': [
|
|
167
|
+
"#...#",
|
|
168
|
+
".#.#.",
|
|
169
|
+
"..#..",
|
|
170
|
+
".#.#.",
|
|
171
|
+
"#...#",
|
|
172
|
+
],
|
|
173
|
+
'Y': [
|
|
174
|
+
"#...#",
|
|
175
|
+
".#.#.",
|
|
176
|
+
"..#..",
|
|
177
|
+
"..#..",
|
|
178
|
+
"..#..",
|
|
179
|
+
],
|
|
180
|
+
'Z': [
|
|
181
|
+
"#####",
|
|
182
|
+
"...#.",
|
|
183
|
+
"..#..",
|
|
184
|
+
".#...",
|
|
185
|
+
"#####",
|
|
186
|
+
],
|
|
187
|
+
' ': [
|
|
188
|
+
".....",
|
|
189
|
+
".....",
|
|
190
|
+
".....",
|
|
191
|
+
".....",
|
|
192
|
+
".....",
|
|
193
|
+
],
|
|
194
|
+
'!': [
|
|
195
|
+
"..#..",
|
|
196
|
+
"..#..",
|
|
197
|
+
"..#..",
|
|
198
|
+
".....",
|
|
199
|
+
"..#..",
|
|
200
|
+
],
|
|
201
|
+
'.': [
|
|
202
|
+
".....",
|
|
203
|
+
".....",
|
|
204
|
+
".....",
|
|
205
|
+
".....",
|
|
206
|
+
"..#..",
|
|
207
|
+
],
|
|
208
|
+
'?': [
|
|
209
|
+
".###.",
|
|
210
|
+
"...#.",
|
|
211
|
+
"..#..",
|
|
212
|
+
".....",
|
|
213
|
+
"..#..",
|
|
214
|
+
],
|
|
215
|
+
'-': [
|
|
216
|
+
".....",
|
|
217
|
+
".....",
|
|
218
|
+
"#####",
|
|
219
|
+
".....",
|
|
220
|
+
".....",
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Optional fallback for unknown characters:
|
|
225
|
+
const UNKNOWN5 = [
|
|
226
|
+
".###.",
|
|
227
|
+
"#...#",
|
|
228
|
+
"..##.",
|
|
229
|
+
".....",
|
|
230
|
+
"..#..",
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Render text into a 5-line ASCII banner.
|
|
235
|
+
* @param {string} text - The text to render.
|
|
236
|
+
* @param {object} [opts]
|
|
237
|
+
* @param {string} [opts.symbol='#'] - Pixel character to use instead of '#'.
|
|
238
|
+
* @param {number} [opts.letterSpacing=1] - Spaces between characters (0+).
|
|
239
|
+
* @returns {string} - Multiline banner string.
|
|
240
|
+
*/
|
|
241
|
+
function cprint(text, { symbol = '#', letterSpacing = 1 } = {}) {
|
|
242
|
+
const rows = ["", "", "", "", ""];
|
|
243
|
+
const gap = " ".repeat(Math.max(0, letterSpacing));
|
|
244
|
+
|
|
245
|
+
for (const rawCh of text) {
|
|
246
|
+
const ch = /[a-z]/.test(rawCh) ? rawCh.toUpperCase() : rawCh;
|
|
247
|
+
const glyph = FONT5[ch] || UNKNOWN5;
|
|
248
|
+
for (let r = 0; r < 5; r++) {
|
|
249
|
+
// convert '.' -> space, '#' -> symbol
|
|
250
|
+
const line = glyph[r].replace(/\./g, ' ').replace(/#/g, symbol);
|
|
251
|
+
rows[r] += line + gap;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Trim trailing spaces on each row and join
|
|
255
|
+
return rows.map(r => r.replace(/\s+$/g, '')).join('\n');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export default cprint;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class BaseFormatter {
|
|
2
|
+
format(taskName, status, taskType, extra = {}) {
|
|
3
|
+
throw new Error('format method must be implemented by subclass');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
formatOutput(line, isError = false) {
|
|
7
|
+
return line;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
shouldDelayStart() {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getStartDelay() {
|
|
15
|
+
return 50;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { BaseFormatter } from './base.js';
|
|
3
|
+
|
|
4
|
+
const STATUS_CONFIG = {
|
|
5
|
+
start: { icon: '▶', color: 'blue' },
|
|
6
|
+
finish: { icon: '✓', color: 'green' },
|
|
7
|
+
error: { icon: '✗', color: 'red' },
|
|
8
|
+
output: { icon: ' ', color: 'white' },
|
|
9
|
+
info: { icon: ' ', color: 'dim' }
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class ConsoleFormatter extends BaseFormatter {
|
|
13
|
+
format(taskName, status, taskType, extra = {}) {
|
|
14
|
+
const config = STATUS_CONFIG[status];
|
|
15
|
+
if (!config) {
|
|
16
|
+
throw new Error(`Unknown status: ${status}. Valid statuses: ${Object.keys(STATUS_CONFIG).join(', ')}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const icon = chalk[config.color](config.icon);
|
|
20
|
+
const taskTypeFormatted = taskType ? chalk.bold(taskType) + ' ' : '';
|
|
21
|
+
const taskNameFormatted = chalk.yellow(taskName);
|
|
22
|
+
|
|
23
|
+
const extraParts = [];
|
|
24
|
+
Object.entries(extra).forEach(([key, value]) => {
|
|
25
|
+
if (value !== null && value !== undefined) {
|
|
26
|
+
if (key === 'duration') {
|
|
27
|
+
extraParts.push(chalk.dim(`${value}ms`));
|
|
28
|
+
} else if (key === 'error') {
|
|
29
|
+
extraParts.push(chalk.dim(value));
|
|
30
|
+
} else if (key === 'status') {
|
|
31
|
+
extraParts.push(chalk.dim(value));
|
|
32
|
+
} else if (key === 'exitCode') {
|
|
33
|
+
extraParts.push(chalk.dim(`exit code: ${value}`));
|
|
34
|
+
} else {
|
|
35
|
+
extraParts.push(chalk.dim(`${key}: ${value}`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const terminalWidth = process.stdout.columns || 100;
|
|
41
|
+
let leftContent = `${icon} ${taskTypeFormatted}${taskNameFormatted}`;
|
|
42
|
+
let rightContent = '';
|
|
43
|
+
|
|
44
|
+
if (extraParts.length > 0) {
|
|
45
|
+
rightContent = `(${extraParts.join(', ')})`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const leftLength = this._stripAnsi(leftContent).length;
|
|
49
|
+
const rightLength = this._stripAnsi(rightContent).length;
|
|
50
|
+
const padding = ' '.repeat(Math.max(1, terminalWidth - leftLength - rightLength));
|
|
51
|
+
|
|
52
|
+
let line = leftContent + padding + rightContent;
|
|
53
|
+
|
|
54
|
+
if (icon.trim()) {
|
|
55
|
+
line = chalk.bgGray(line);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let result = line;
|
|
59
|
+
if (status === 'error' && extra.error) {
|
|
60
|
+
result += '\n' + chalk.red(' Error:') + ' ' + extra.error;
|
|
61
|
+
}
|
|
62
|
+
if (status === 'finish' || status === 'error') {
|
|
63
|
+
result += '\n';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
formatOutput(line, isError = false) {
|
|
70
|
+
if (!line.trim()) return '';
|
|
71
|
+
return isError ? chalk.red(line) : line;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_stripAnsi(str) {
|
|
75
|
+
return str.replace(/\u001b\[[0-9;]*m/g, '');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static detect() {
|
|
79
|
+
return !process.env.CI;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { GitHubActionsFormatter } from './github-actions.js';
|
|
2
|
+
import { ConsoleFormatter } from './console.js';
|
|
3
|
+
|
|
4
|
+
const FORMATTERS = [
|
|
5
|
+
GitHubActionsFormatter,
|
|
6
|
+
ConsoleFormatter
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export function createFormatter() {
|
|
10
|
+
for (const FormatterClass of FORMATTERS) {
|
|
11
|
+
if (FormatterClass.detect && FormatterClass.detect()) {
|
|
12
|
+
return new FormatterClass();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return new ConsoleFormatter();
|
|
17
|
+
}
|