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
package/src/program.js
CHANGED
|
@@ -1,33 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
import { Command } from "commander";
|
|
2
2
|
import babelParser from "@babel/parser";
|
|
3
|
-
import
|
|
3
|
+
import traverseDefault from "@babel/traverse";
|
|
4
|
+
const traverse = traverseDefault.default || traverseDefault;
|
|
4
5
|
import color from "chalk";
|
|
5
6
|
import fs from 'fs';
|
|
6
7
|
import openEditor from 'open-editor';
|
|
7
|
-
import
|
|
8
|
+
import { yell } from './io.js';
|
|
9
|
+
import cprint from "./font.js";
|
|
10
|
+
import { handleCompletion, detectCurrentShell, installCompletion, getCompletionPaths } from './completion.js';
|
|
11
|
+
import { upgradeExecutable, isExecutable, getCurrentVersion } from './upgrade.js';
|
|
8
12
|
|
|
9
|
-
export const BUNOSHFILE = `
|
|
13
|
+
export const BUNOSHFILE = `Bunoshfile.js`;
|
|
10
14
|
|
|
11
|
-
export
|
|
15
|
+
export const banner = () => {
|
|
16
|
+
console.log(cprint('Bunosh', { symbol: '⯀' }));
|
|
17
|
+
console.log(color.gray('🍲 Your exceptional task runner'));
|
|
18
|
+
console.log();
|
|
19
|
+
};
|
|
12
20
|
|
|
13
21
|
export default function bunosh(commands, source) {
|
|
14
22
|
const program = new Command();
|
|
23
|
+
program.option('--bunoshfile <path>', 'Path to the Bunoshfile');
|
|
15
24
|
|
|
16
25
|
const internalCommands = [];
|
|
17
26
|
|
|
27
|
+
// Load npm scripts from package.json
|
|
28
|
+
const npmScripts = loadNpmScripts();
|
|
29
|
+
|
|
18
30
|
program.configureHelp({
|
|
19
|
-
commandDescription: _cmd =>
|
|
31
|
+
commandDescription: _cmd => {
|
|
32
|
+
// Show banner and description
|
|
33
|
+
banner();
|
|
34
|
+
return ` Commands are loaded from exported functions in ${color.bold(BUNOSHFILE)}`;
|
|
35
|
+
},
|
|
20
36
|
commandUsage: usg => 'bunosh <command> <args> [options]',
|
|
21
37
|
showGlobalOptions: false,
|
|
22
|
-
// visibleArguments: cmd => cmd.registeredArguments,
|
|
23
38
|
visibleGlobalOptions: _opt => [],
|
|
24
|
-
// Bunosh has no default options
|
|
25
39
|
visibleOptions: _opt => [],
|
|
26
40
|
visibleCommands: cmd => cmd.commands.filter(c => !internalCommands.includes(c)),
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
subcommandTerm: (cmd) => pickColorForColorName(cmd.name()),
|
|
30
|
-
subcommandDescription: (cmd) => cmd.description(),
|
|
41
|
+
subcommandTerm: (cmd) => color.white.bold(cmd.name()),
|
|
42
|
+
subcommandDescription: (cmd) => color.gray(cmd.description()),
|
|
31
43
|
});
|
|
32
44
|
|
|
33
45
|
program.showHelpAfterError();
|
|
@@ -36,7 +48,6 @@ export default function bunosh(commands, source) {
|
|
|
36
48
|
|
|
37
49
|
const completeAst = babelParser.parse(source, {
|
|
38
50
|
sourceType: "module",
|
|
39
|
-
plugins: ["jsx"],
|
|
40
51
|
ranges: true,
|
|
41
52
|
tokens: true,
|
|
42
53
|
comments: true,
|
|
@@ -45,150 +56,189 @@ export default function bunosh(commands, source) {
|
|
|
45
56
|
|
|
46
57
|
const comments = fetchComments();
|
|
47
58
|
|
|
59
|
+
// Collect all commands (bunosh + npm scripts) and sort them
|
|
60
|
+
const allCommands = [];
|
|
61
|
+
|
|
62
|
+
// Add bunosh commands
|
|
48
63
|
Object.keys(commands).forEach((fnName) => {
|
|
49
|
-
|
|
64
|
+
allCommands.push({ type: 'bunosh', name: fnName, data: commands[fnName] });
|
|
65
|
+
});
|
|
50
66
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
// Add npm scripts
|
|
68
|
+
Object.entries(npmScripts).forEach(([scriptName, scriptCommand]) => {
|
|
69
|
+
allCommands.push({ type: 'npm', name: `npm:${scriptName}`, data: { scriptName, scriptCommand } });
|
|
70
|
+
});
|
|
54
71
|
|
|
55
|
-
|
|
72
|
+
// Sort all commands alphabetically by name
|
|
73
|
+
allCommands.sort((a, b) => a.name.localeCompare(b.name));
|
|
56
74
|
|
|
57
|
-
|
|
75
|
+
// Process all commands in sorted order
|
|
76
|
+
allCommands.forEach((cmdData) => {
|
|
77
|
+
if (cmdData.type === 'bunosh') {
|
|
78
|
+
const fnName = cmdData.name;
|
|
79
|
+
const fnBody = commands[fnName].toString();
|
|
58
80
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
let argsAndOptsDescription = [];
|
|
81
|
+
const ast = fetchFnAst();
|
|
82
|
+
const args = parseArgs();
|
|
83
|
+
const opts = parseOpts();
|
|
65
84
|
|
|
66
|
-
|
|
67
|
-
if (value === undefined) {
|
|
68
|
-
argsAndOptsDescription.push(`<${arg}>`);
|
|
69
|
-
return command.argument(`<${arg}>`);
|
|
70
|
-
}
|
|
85
|
+
const comment = comments[fnName];
|
|
71
86
|
|
|
72
|
-
|
|
73
|
-
argsAndOptsDescription.push(`[${arg}]`);
|
|
74
|
-
return command.argument(`[${arg}]`, '', null);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
argsAndOptsDescription.push(`[${arg}=${value}]`);
|
|
78
|
-
command.argument(`[${arg}]`, ``, value);
|
|
79
|
-
});
|
|
87
|
+
const commandName = prepareCommandName(fnName);
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
argsAndOptsDescription.push(`--${opt}=${value}`);
|
|
88
|
-
command.option(`--${opt} [${opt}]`, "", value);
|
|
89
|
+
const command = program.command(commandName);
|
|
90
|
+
command.hook('preAction', (_thisCommand) => {
|
|
91
|
+
process.env.BUNOSH_COMMAND_STARTED = true;
|
|
92
|
+
})
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
let argsAndOptsDescription = [];
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
Object.entries(args).forEach(([arg, value]) => {
|
|
97
|
+
if (value === undefined) {
|
|
98
|
+
argsAndOptsDescription.push(`<${arg}>`);
|
|
99
|
+
return command.argument(`<${arg}>`);
|
|
100
|
+
}
|
|
93
101
|
|
|
102
|
+
if (value === null) {
|
|
103
|
+
argsAndOptsDescription.push(`[${arg}]`);
|
|
104
|
+
return command.argument(`[${arg}]`, '', null);
|
|
105
|
+
}
|
|
94
106
|
|
|
95
|
-
|
|
107
|
+
argsAndOptsDescription.push(`[${arg}=${value}]`);
|
|
108
|
+
command.argument(`[${arg}]`, ``, value);
|
|
109
|
+
});
|
|
96
110
|
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
Object.entries(opts).forEach(([opt, value]) => {
|
|
112
|
+
if (value === false || value === null) {
|
|
113
|
+
argsAndOptsDescription.push(`--${opt}`);
|
|
114
|
+
return command.option(`--${opt}`);
|
|
115
|
+
}
|
|
99
116
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
let hasFnInSource = false;
|
|
117
|
+
argsAndOptsDescription.push(`--${opt}=${value}`);
|
|
118
|
+
command.option(`--${opt} [${opt}]`, "", value);
|
|
103
119
|
|
|
104
|
-
traverse(completeAst, {
|
|
105
|
-
FunctionDeclaration(path) {
|
|
106
|
-
if (path.node.id.name == fnName) {
|
|
107
|
-
hasFnInSource = true;
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
120
|
});
|
|
112
121
|
|
|
113
|
-
|
|
122
|
+
let description = comment?.split('\n')[0] || '';
|
|
114
123
|
|
|
115
|
-
|
|
116
|
-
}
|
|
124
|
+
if (comment && argsAndOptsDescription.length) description += `\n ${color.gray(`bunosh ${commandName}`)} ${color.blue(argsAndOptsDescription.join(' ').trim())}`;
|
|
117
125
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const functionArguments = {};
|
|
121
|
-
|
|
122
|
-
traverse(ast, {
|
|
123
|
-
FunctionDeclaration(path) {
|
|
124
|
-
if (path.node.id.name !== fnName) return;
|
|
125
|
-
|
|
126
|
-
const params = path.node.params
|
|
127
|
-
.filter((node) => {
|
|
128
|
-
return node?.right?.type !== "ObjectExpression";
|
|
129
|
-
})
|
|
130
|
-
.forEach((param) => {
|
|
131
|
-
if (param.type === "AssignmentPattern") {
|
|
132
|
-
functionArguments[param.left.name] = param.right.value;
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
if (!param.name) return;
|
|
126
|
+
command.description(description);
|
|
127
|
+
command.action(commands[fnName].bind(commands));
|
|
136
128
|
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
function fetchFnAst() {
|
|
130
|
+
let hasFnInSource = false;
|
|
139
131
|
|
|
140
|
-
|
|
141
|
-
|
|
132
|
+
traverse(completeAst, {
|
|
133
|
+
FunctionDeclaration(path) {
|
|
134
|
+
if (path.node.id.name == fnName) {
|
|
135
|
+
hasFnInSource = true;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
});
|
|
142
140
|
|
|
143
|
-
|
|
144
|
-
}
|
|
141
|
+
if (hasFnInSource) return completeAst;
|
|
145
142
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
let functionOpts = {};
|
|
143
|
+
return babelParser.parse(fnBody, { comment: true, tokens: true });
|
|
144
|
+
}
|
|
149
145
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (path.node.id.name !== fnName) return;
|
|
146
|
+
function parseArgs() {
|
|
147
|
+
const functionArguments = {};
|
|
153
148
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
!node.type === "AssignmentPattern" &&
|
|
158
|
-
node.right.type === "ObjectExpression"
|
|
159
|
-
)
|
|
160
|
-
return;
|
|
149
|
+
traverse(ast, {
|
|
150
|
+
FunctionDeclaration(path) {
|
|
151
|
+
if (path.node.id.name !== fnName) return;
|
|
161
152
|
|
|
153
|
+
const params = path.node.params
|
|
154
|
+
.filter((node) => {
|
|
155
|
+
return node?.right?.type !== "ObjectExpression";
|
|
156
|
+
})
|
|
157
|
+
.forEach((param) => {
|
|
158
|
+
if (param.type === "AssignmentPattern") {
|
|
159
|
+
functionArguments[param.left.name] = param.right.value;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (!param.name) return;
|
|
162
163
|
|
|
163
|
-
|
|
164
|
+
return functionArguments[param.name] = null;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return functionArguments;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parseOpts() {
|
|
174
|
+
let functionOpts = {};
|
|
175
|
+
|
|
176
|
+
traverse(ast, {
|
|
177
|
+
FunctionDeclaration(path) {
|
|
178
|
+
if (path.node.id.name !== fnName) return;
|
|
179
|
+
|
|
180
|
+
const node = path.node.params.pop();
|
|
181
|
+
if (!node) return;
|
|
164
182
|
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
) {
|
|
169
|
-
functionOpts[camelToDasherize(p.key.name)] = p.value.value;
|
|
183
|
+
!node.type === "AssignmentPattern" &&
|
|
184
|
+
node.right.type === "ObjectExpression"
|
|
185
|
+
)
|
|
170
186
|
return;
|
|
171
|
-
}
|
|
172
187
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
188
|
+
node?.right?.properties?.forEach((p) => {
|
|
189
|
+
if (
|
|
190
|
+
["NumericLiteral", "StringLiteral", "BooleanLiteral"].includes(
|
|
191
|
+
p.value.type,
|
|
192
|
+
)
|
|
193
|
+
) {
|
|
194
|
+
functionOpts[camelToDasherize(p.key.name)] = p.value.value;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
177
197
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
// ignore other options for now
|
|
184
|
-
});
|
|
185
|
-
},
|
|
186
|
-
});
|
|
198
|
+
if (p.value.type === "NullLiteral") {
|
|
199
|
+
functionOpts[camelToDasherize(p.key.name)] = null;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
187
202
|
|
|
188
|
-
|
|
203
|
+
if (p.value.type == "UnaryExpression" && p.value.operator == "!") {
|
|
204
|
+
functionOpts[camelToDasherize(p.key.name)] =
|
|
205
|
+
!p.value.argument.value;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return functionOpts;
|
|
213
|
+
}
|
|
214
|
+
} else if (cmdData.type === 'npm') {
|
|
215
|
+
// Handle npm scripts
|
|
216
|
+
const { scriptName, scriptCommand } = cmdData.data;
|
|
217
|
+
const commandName = `npm:${scriptName}`;
|
|
218
|
+
const command = program.command(commandName);
|
|
219
|
+
command.description(color.gray(scriptCommand)); // Use script command as description
|
|
220
|
+
|
|
221
|
+
// Create action with proper closure to capture scriptName
|
|
222
|
+
command.action(createNpmScriptAction(scriptName));
|
|
189
223
|
}
|
|
190
224
|
});
|
|
191
225
|
|
|
226
|
+
// Helper function to create npm script action with proper closure
|
|
227
|
+
function createNpmScriptAction(scriptName) {
|
|
228
|
+
return async () => {
|
|
229
|
+
// Execute npm script using Bunosh's exec task
|
|
230
|
+
const { exec } = await import('../index.js');
|
|
231
|
+
try {
|
|
232
|
+
// Call exec with proper template literal simulation
|
|
233
|
+
const result = await exec(['npm run ', ''], scriptName);
|
|
234
|
+
return result;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.error(`Failed to run npm script: ${scriptName}`);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
192
242
|
const editCmd = program.command('edit')
|
|
193
243
|
.description('Open the bunosh file in your editor. $EDITOR or \'code\' is used.')
|
|
194
244
|
.action(() => {
|
|
@@ -204,20 +254,185 @@ export default function bunosh(commands, source) {
|
|
|
204
254
|
const exoprtCmd = program.command('export:scripts')
|
|
205
255
|
.description('Export commands to "scripts" section of package.json.')
|
|
206
256
|
.action(() => {
|
|
207
|
-
|
|
208
257
|
exportFn(Object.keys(commands));
|
|
209
258
|
});
|
|
210
|
-
|
|
211
|
-
internalCommands.push(exoprtCmd);
|
|
259
|
+
|
|
260
|
+
internalCommands.push(exoprtCmd);
|
|
261
|
+
|
|
262
|
+
const completionCmd = program.command('completion <shell>')
|
|
263
|
+
.description('Generate shell completion scripts')
|
|
264
|
+
.argument('<shell>', 'Shell type: bash, zsh, or fish')
|
|
265
|
+
.action((shell) => {
|
|
266
|
+
try {
|
|
267
|
+
const completionScript = handleCompletion(shell);
|
|
268
|
+
console.log(completionScript);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error(error.message);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
internalCommands.push(completionCmd);
|
|
276
|
+
|
|
277
|
+
const setupCompletionCmd = program.command('setup-completion')
|
|
278
|
+
.description('Automatically setup shell completion for your current shell')
|
|
279
|
+
.option('-f, --force', 'Overwrite existing completion setup')
|
|
280
|
+
.option('-s, --shell <shell>', 'Specify shell instead of auto-detection (bash, zsh, fish)')
|
|
281
|
+
.action((options) => {
|
|
282
|
+
try {
|
|
283
|
+
// Detect current shell or use specified shell
|
|
284
|
+
const shell = options.shell || detectCurrentShell();
|
|
285
|
+
|
|
286
|
+
if (!shell) {
|
|
287
|
+
console.error('❌ Could not detect your shell. Please specify one:');
|
|
288
|
+
console.log(' bunosh setup-completion --shell bash');
|
|
289
|
+
console.log(' bunosh setup-completion --shell zsh');
|
|
290
|
+
console.log(' bunosh setup-completion --shell fish');
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
console.log(`🐚 Detected shell: ${color.bold(shell)}`);
|
|
295
|
+
console.log();
|
|
296
|
+
|
|
297
|
+
// Get paths for this shell
|
|
298
|
+
const paths = getCompletionPaths(shell);
|
|
299
|
+
|
|
300
|
+
// Check if already installed
|
|
301
|
+
if (!options.force && fs.existsSync(paths.completionFile)) {
|
|
302
|
+
console.log(`⚠️ Completion already installed at: ${paths.completionFile}`);
|
|
303
|
+
console.log(' Use --force to overwrite, or run:');
|
|
304
|
+
console.log(` ${color.dim('rm')} ${paths.completionFile}`);
|
|
305
|
+
process.exit(0);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Install completion
|
|
309
|
+
console.log('🔧 Installing completion...');
|
|
310
|
+
const result = installCompletion(shell);
|
|
311
|
+
|
|
312
|
+
// Report success
|
|
313
|
+
console.log(`✅ Completion installed: ${color.green(paths.completionFile)}`);
|
|
314
|
+
|
|
315
|
+
if (result.configFile && result.added) {
|
|
316
|
+
console.log(`📝 Updated shell config: ${color.green(result.configFile)}`);
|
|
317
|
+
console.log();
|
|
318
|
+
console.log(`💡 ${color.bold('Restart your terminal')} or run:`);
|
|
319
|
+
if (shell === 'bash') {
|
|
320
|
+
console.log(` ${color.dim('source ~/.bashrc')}`);
|
|
321
|
+
} else if (shell === 'zsh') {
|
|
322
|
+
console.log(` ${color.dim('source ~/.zshrc')}`);
|
|
323
|
+
}
|
|
324
|
+
} else if (shell === 'fish') {
|
|
325
|
+
console.log('🐟 Fish completion is ready! No restart needed.');
|
|
326
|
+
} else if (result.configFile && !result.added) {
|
|
327
|
+
console.log(`ℹ️ Shell config already has completion setup: ${result.configFile}`);
|
|
328
|
+
console.log(' Restart your terminal if completion isn\'t working.');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log();
|
|
332
|
+
console.log('🎯 Test completion by typing: ' + color.bold('bunosh <TAB>'));
|
|
333
|
+
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error(`❌ Setup failed: ${error.message}`);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
internalCommands.push(setupCompletionCmd);
|
|
341
|
+
|
|
342
|
+
const upgradeCmd = program.command('upgrade')
|
|
343
|
+
.description('Upgrade bunosh to the latest version (single executable only)')
|
|
344
|
+
.option('-f, --force', 'Force upgrade even if already on latest version')
|
|
345
|
+
.option('--check', 'Check for updates without upgrading')
|
|
346
|
+
.action(async (options) => {
|
|
347
|
+
try {
|
|
348
|
+
if (!isExecutable()) {
|
|
349
|
+
console.log('📦 Bunosh is installed via npm.');
|
|
350
|
+
console.log('To upgrade, run: ' + color.bold('npm update -g bunosh'));
|
|
351
|
+
process.exit(0);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const currentVersion = getCurrentVersion();
|
|
355
|
+
console.log(`📍 Current version: ${color.bold(currentVersion)}`);
|
|
356
|
+
|
|
357
|
+
if (options.check) {
|
|
358
|
+
console.log('🔍 Checking for updates...');
|
|
359
|
+
try {
|
|
360
|
+
const { getLatestRelease, isNewerVersion } = await import('./upgrade.js');
|
|
361
|
+
const release = await getLatestRelease();
|
|
362
|
+
const latestVersion = release.tag_name;
|
|
363
|
+
|
|
364
|
+
console.log(`📦 Latest version: ${color.bold(latestVersion)}`);
|
|
365
|
+
|
|
366
|
+
if (isNewerVersion(latestVersion, currentVersion)) {
|
|
367
|
+
console.log(`✨ ${color.green('Update available!')} ${currentVersion} → ${latestVersion}`);
|
|
368
|
+
console.log('Run ' + color.bold('bunosh upgrade') + ' to update.');
|
|
369
|
+
} else {
|
|
370
|
+
console.log(`✅ ${color.green('You are on the latest version!')}`);
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error(`❌ Failed to check for updates: ${error.message}`);
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
console.log('⬆️ Starting upgrade process...');
|
|
380
|
+
console.log();
|
|
381
|
+
|
|
382
|
+
let lastMessage = '';
|
|
383
|
+
const result = await upgradeExecutable({
|
|
384
|
+
force: options.force,
|
|
385
|
+
onProgress: (message) => {
|
|
386
|
+
if (message !== lastMessage) {
|
|
387
|
+
console.log(` ${message}`);
|
|
388
|
+
lastMessage = message;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
console.log();
|
|
394
|
+
if (result.updated) {
|
|
395
|
+
console.log(`🎉 ${color.green('Upgrade successful!')}`);
|
|
396
|
+
console.log(` ${result.currentVersion} → ${color.bold(result.latestVersion)}`);
|
|
397
|
+
console.log();
|
|
398
|
+
console.log(`💡 Run ${color.bold('bunosh --version')} to verify the new version.`);
|
|
399
|
+
} else {
|
|
400
|
+
console.log(`✅ ${color.green(result.message)}`);
|
|
401
|
+
if (!options.force) {
|
|
402
|
+
console.log(` Use ${color.bold('--force')} to reinstall the current version.`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
} catch (error) {
|
|
407
|
+
console.error(`❌ Upgrade failed: ${error.message}`);
|
|
408
|
+
|
|
409
|
+
if (error.message.includes('Unsupported platform')) {
|
|
410
|
+
console.log();
|
|
411
|
+
console.log('💡 Supported platforms:');
|
|
412
|
+
console.log(' • Linux x64');
|
|
413
|
+
console.log(' • macOS ARM64 (Apple Silicon)');
|
|
414
|
+
console.log(' • Windows x64');
|
|
415
|
+
} else if (error.message.includes('GitHub API')) {
|
|
416
|
+
console.log();
|
|
417
|
+
console.log('💡 Try again later or check your internet connection.');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
internalCommands.push(upgradeCmd);
|
|
212
425
|
|
|
213
426
|
program.addHelpText('after', `
|
|
214
427
|
|
|
215
428
|
Special Commands:
|
|
216
429
|
📝 Edit bunosh file: ${color.bold('bunosh edit')}
|
|
217
430
|
📥 Export scripts to package.json: ${color.bold('bunosh export:scripts')}
|
|
431
|
+
🔤 Generate shell completion: ${color.bold('bunosh completion bash|zsh|fish')}
|
|
432
|
+
⚡ Auto-setup completion: ${color.bold('bunosh setup-completion')}
|
|
433
|
+
⬆️ Upgrade bunosh: ${color.bold('bunosh upgrade')}
|
|
218
434
|
`);
|
|
219
435
|
|
|
220
|
-
|
|
221
436
|
program.on("command:*", (cmd) => {
|
|
222
437
|
console.log(`\nUnknown command ${cmd}\n`);
|
|
223
438
|
program.outputHelp();
|
|
@@ -244,16 +459,18 @@ Special Commands:
|
|
|
244
459
|
|
|
245
460
|
if (matches && matches[1]) {
|
|
246
461
|
comments[functionName] = matches[1]
|
|
247
|
-
.replace(/^\s*\*\s*/gm, "")
|
|
248
|
-
.replace(/\s*\*\*\s*$/gm, "")
|
|
462
|
+
.replace(/^\s*\*\s*/gm, "")
|
|
463
|
+
.replace(/\s*\*\*\s*$/gm, "")
|
|
249
464
|
.trim()
|
|
250
|
-
.replace(/^@.*$/gm, "")
|
|
465
|
+
.replace(/^@.*$/gm, "")
|
|
251
466
|
.trim();
|
|
252
467
|
} else {
|
|
253
|
-
|
|
468
|
+
// Check for comments attached to the first statement in the function body
|
|
469
|
+
const firstStatement = path.node?.body?.body?.[0];
|
|
470
|
+
const leadingComments = firstStatement?.leadingComments;
|
|
254
471
|
|
|
255
|
-
if (
|
|
256
|
-
comments[functionName] =
|
|
472
|
+
if (leadingComments && leadingComments.length > 0) {
|
|
473
|
+
comments[functionName] = leadingComments[0].value.trim();
|
|
257
474
|
}
|
|
258
475
|
}
|
|
259
476
|
|
|
@@ -277,25 +494,6 @@ function camelToDasherize(camelCaseString) {
|
|
|
277
494
|
return camelCaseString.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
278
495
|
}
|
|
279
496
|
|
|
280
|
-
function pickColorForColorName(commandName) {
|
|
281
|
-
const colors = [
|
|
282
|
-
color.red,
|
|
283
|
-
color.green,
|
|
284
|
-
color.yellow,
|
|
285
|
-
color.blue,
|
|
286
|
-
color.magenta,
|
|
287
|
-
color.cyan,
|
|
288
|
-
];
|
|
289
|
-
|
|
290
|
-
const prefixName = camelToDasherize(commandName).split("-")[0];
|
|
291
|
-
|
|
292
|
-
const index =
|
|
293
|
-
prefixName.split("").reduce((acc, char) => {
|
|
294
|
-
return acc + char.charCodeAt(0);
|
|
295
|
-
}, 0) % colors.length;
|
|
296
|
-
|
|
297
|
-
return color.bold(colors[index](commandName));
|
|
298
|
-
}
|
|
299
497
|
|
|
300
498
|
function parseDocBlock(funcName, code) {
|
|
301
499
|
const regex = new RegExp(
|
|
@@ -304,7 +502,6 @@ function parseDocBlock(funcName, code) {
|
|
|
304
502
|
const match = code.match(regex);
|
|
305
503
|
|
|
306
504
|
if (match && match[1]) {
|
|
307
|
-
// Remove leading asterisks and trim the result
|
|
308
505
|
return match[1]
|
|
309
506
|
.replace(/^\s*\*\s*/gm, "")
|
|
310
507
|
.split("\n")[0]
|
|
@@ -329,8 +526,7 @@ function exportFn(commands) {
|
|
|
329
526
|
if (!pkg.scripts) {
|
|
330
527
|
pkg.scripts = {};
|
|
331
528
|
}
|
|
332
|
-
|
|
333
|
-
// cleanup from previously exported
|
|
529
|
+
|
|
334
530
|
for (let s in pkg.scripts ) {
|
|
335
531
|
if (pkg[s] && pkg[s].startsWith('bunosh')) delete pkg[s];
|
|
336
532
|
}
|
|
@@ -351,3 +547,27 @@ function exportFn(commands) {
|
|
|
351
547
|
console.log(`${Object.keys(scripts).length} scripts exported`);
|
|
352
548
|
return true;
|
|
353
549
|
}
|
|
550
|
+
|
|
551
|
+
function loadNpmScripts() {
|
|
552
|
+
try {
|
|
553
|
+
if (!fs.existsSync('package.json')) {
|
|
554
|
+
return {};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
558
|
+
const scripts = pkg.scripts || {};
|
|
559
|
+
|
|
560
|
+
// Filter out bunosh scripts (scripts that contain "bunosh")
|
|
561
|
+
const npmScripts = {};
|
|
562
|
+
Object.entries(scripts).forEach(([name, command]) => {
|
|
563
|
+
if (!command.includes('bunosh')) {
|
|
564
|
+
npmScripts[name] = command;
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
return npmScripts;
|
|
569
|
+
} catch (error) {
|
|
570
|
+
console.warn('Warning: Could not load npm scripts from package.json:', error.message);
|
|
571
|
+
return {};
|
|
572
|
+
}
|
|
573
|
+
}
|