bunosh 0.1.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/README.md +15 -0
- package/bunosh +0 -0
- package/bunosh.tasks.js +83 -0
- package/files.js +3 -0
- package/index.js +15 -0
- package/io.jsx +47 -0
- package/package.json +40 -0
- package/run.js +30 -0
- package/src/init.js +16 -0
- package/src/output.js +37 -0
- package/src/program.js +327 -0
- package/src/task.jsx +209 -0
- package/src/tasks/copyFile.jsx +14 -0
- package/src/tasks/exec.jsx +87 -0
- package/src/tasks/fetch.jsx +74 -0
- package/src/tasks/writeToFile.jsx +49 -0
- package/templates/banner.js +12 -0
- package/templates/init.js +29 -0
- package/test.txt +44 -0
package/README.md
ADDED
package/bunosh
ADDED
|
Binary file
|
package/bunosh.tasks.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { exec, fetch, task, ignoreFail } from "./index";
|
|
3
|
+
import { ask, say, yell } from "./io";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import { writeToFile } from "./files";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds binary file for Bunosh
|
|
9
|
+
*/
|
|
10
|
+
export async function buildBinary() {
|
|
11
|
+
await exec`bun build ./run.js --compile --outfile bunosh`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 🎉 Hello world
|
|
16
|
+
* @param {*} arg1
|
|
17
|
+
* @param {*} opts
|
|
18
|
+
*/
|
|
19
|
+
export async function helloWorld(
|
|
20
|
+
arg1,
|
|
21
|
+
opts = { user: null, val: "ok", flag: false },
|
|
22
|
+
) {
|
|
23
|
+
// console.log("Hello World!", arg1);
|
|
24
|
+
yell("I need all git status");
|
|
25
|
+
const pack = await task('read file', () => {
|
|
26
|
+
return fs.readFileSync("package.json").toString();
|
|
27
|
+
});
|
|
28
|
+
say(pack);
|
|
29
|
+
writeToFile('test.txt', l => {
|
|
30
|
+
l`lock file`
|
|
31
|
+
l('Hello world');
|
|
32
|
+
l.fromFile('package.json');
|
|
33
|
+
});
|
|
34
|
+
yell('done');
|
|
35
|
+
yell('ok');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Hello other
|
|
40
|
+
*/
|
|
41
|
+
export async function helloOther(opts = {
|
|
42
|
+
user: null,
|
|
43
|
+
val: "ok",
|
|
44
|
+
flag: false
|
|
45
|
+
}) {
|
|
46
|
+
|
|
47
|
+
yell('running everything!')
|
|
48
|
+
|
|
49
|
+
await fetch('https://reqres.in/api/users')
|
|
50
|
+
|
|
51
|
+
await Promise.all([
|
|
52
|
+
fetch('https://reqres.in/api/users/1'),
|
|
53
|
+
exec`ps aux | grep redis`,
|
|
54
|
+
fetch('https://reqres.in/api/users/2')
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
ignoreFail(true);
|
|
58
|
+
|
|
59
|
+
await exec`ps aux | grep node`,
|
|
60
|
+
await exec`git status`.cwd('/home/davert/projects/codeceptjs');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Deploys code to staging
|
|
65
|
+
*/
|
|
66
|
+
export async function updateToProduction() {
|
|
67
|
+
|
|
68
|
+
await Promise.all([
|
|
69
|
+
exec`ps aux | grep redis`,
|
|
70
|
+
exec`ps aux | grep clickhouse`,
|
|
71
|
+
fetch('https://reqres.in/api/users/2'),
|
|
72
|
+
])
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Deploys code to production
|
|
77
|
+
*/
|
|
78
|
+
export async function updateToStaging() {
|
|
79
|
+
// this is not ok
|
|
80
|
+
const env = await ask('Which environment we are in?');
|
|
81
|
+
say(env);
|
|
82
|
+
}
|
|
83
|
+
|
package/files.js
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import program from "./src/program";
|
|
2
|
+
import exec from "./src/tasks/exec";
|
|
3
|
+
import fetch from "./src/tasks/fetch";
|
|
4
|
+
import { task, stopOnFail, ignoreFail } from "./src/task";
|
|
5
|
+
|
|
6
|
+
export { program as bunosh };
|
|
7
|
+
|
|
8
|
+
export { exec, fetch, task, stopOnFail, ignoreFail};
|
|
9
|
+
export { exec as $ }
|
|
10
|
+
|
|
11
|
+
export function buildCmd(cmd) {
|
|
12
|
+
return function(args) {
|
|
13
|
+
return exec`${cmd} ${args}`
|
|
14
|
+
}
|
|
15
|
+
}
|
package/io.jsx
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React, {useState, useEffect} from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { renderOnce, isStaticOutput} from './src/output';
|
|
4
|
+
import Gradient from 'ink-gradient';
|
|
5
|
+
import BigText from 'ink-big-text';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
|
|
8
|
+
export function say(...args) {
|
|
9
|
+
if (isStaticOutput) {
|
|
10
|
+
console.log(...args);
|
|
11
|
+
return;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const colors = ['yellow', 'magenta', 'cyan', 'blue', 'blueBright', 'magentaBright', 'cyanBright', 'whiteBright'];
|
|
15
|
+
|
|
16
|
+
renderOnce(
|
|
17
|
+
<Box gap={1} height={20} overflow='hidden' >
|
|
18
|
+
<Text color='white'>!</Text>
|
|
19
|
+
{args.map((arg, i) => <Text color={colors[i]} key={i}>{arg}</Text>)}
|
|
20
|
+
</Box>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function ask(question, opts = {}) {
|
|
25
|
+
|
|
26
|
+
const answers = await inquirer.prompt({ name: question, message: question, ...opts })
|
|
27
|
+
|
|
28
|
+
return Object.values(answers)[0];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function yell(text) {
|
|
32
|
+
if (isStaticOutput) {
|
|
33
|
+
console.log();
|
|
34
|
+
console.log(text.toUpperCase());
|
|
35
|
+
console.log();
|
|
36
|
+
return;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
renderOnce(
|
|
40
|
+
<Box gap={1} marginLeft={1}>
|
|
41
|
+
<Gradient name="teen">
|
|
42
|
+
<BigText text={text}/>
|
|
43
|
+
</Gradient>
|
|
44
|
+
|
|
45
|
+
</Box>
|
|
46
|
+
);
|
|
47
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bunosh",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"module": "index.js",
|
|
5
|
+
"bin": {
|
|
6
|
+
"bunosh": "./run.js"
|
|
7
|
+
},
|
|
8
|
+
"devDependencies": {
|
|
9
|
+
"@types/bun": "latest",
|
|
10
|
+
"prettier": "^3.2.4",
|
|
11
|
+
"lint-staged": "^15.2.0"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@babel/parser": "^7.23.6",
|
|
15
|
+
"@babel/traverse": "^7.23.7",
|
|
16
|
+
"cfonts": "^3.2.0",
|
|
17
|
+
"chalk": "^5.3.0",
|
|
18
|
+
"commander": "^11.1.0",
|
|
19
|
+
"debug": "^4.3.4",
|
|
20
|
+
"fs-extra": "^11.2.0",
|
|
21
|
+
"ink": "^4.4.1",
|
|
22
|
+
"ink-big-text": "^2.0.0",
|
|
23
|
+
"ink-gradient": "^3.0.0",
|
|
24
|
+
"ink-spinner": "^5.0.0",
|
|
25
|
+
"inquirer": "^9.2.13",
|
|
26
|
+
"open-editor": "^4.1.1",
|
|
27
|
+
"picocolors": "^1.0.0",
|
|
28
|
+
"react": "^18.2.0",
|
|
29
|
+
"timer-node": "^5.0.7"
|
|
30
|
+
},
|
|
31
|
+
"lint-staged": {
|
|
32
|
+
"*.{js,css,md}": "prettier --write"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"hello:other": "bunosh hello:other",
|
|
36
|
+
"hello:world": "bunosh hello:world",
|
|
37
|
+
"update:to-production": "bunosh update:to-production",
|
|
38
|
+
"update:to-staging": "bunosh update:to-staging"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/run.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
import program, { BUNOSHFILE, banner } from "./src/program";
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import init from "./src/init";
|
|
5
|
+
import { say, yell } from './io';
|
|
6
|
+
|
|
7
|
+
const tasksFile = `./${BUNOSHFILE}`;
|
|
8
|
+
|
|
9
|
+
if (!existsSync(tasksFile)) {
|
|
10
|
+
console.log(banner);
|
|
11
|
+
|
|
12
|
+
if (process.argv.includes('init')) {
|
|
13
|
+
init();
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log();
|
|
18
|
+
console.error(`Bunosh file not found: ${tasksFile}`);
|
|
19
|
+
console.log("Run `bunosh init` to create a new bunosh tasks file here")
|
|
20
|
+
console.log();
|
|
21
|
+
process.ar
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
global.say = say;
|
|
26
|
+
global.yell = yell;
|
|
27
|
+
|
|
28
|
+
import(tasksFile).then((tasks) => {
|
|
29
|
+
program(tasks, readFileSync(tasksFile, "utf-8"));
|
|
30
|
+
});
|
package/src/init.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BUNOSHFILE } from "./program";
|
|
2
|
+
import color from "picocolors";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import templateFile from "../templates/init";
|
|
5
|
+
|
|
6
|
+
export default function init() {
|
|
7
|
+
if (!fs.existsSync(BUNOSHFILE)) {
|
|
8
|
+
fs.writeFileSync(BUNOSHFILE, templateFile());
|
|
9
|
+
console.log(color.bold(`🎉 Bunosh ${BUNOSHFILE} file created`));
|
|
10
|
+
console.log(' Edit it with "bunosh edit" command');
|
|
11
|
+
console.log(' Or open it in your favorite editor');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
console.error(`Bunosh file already exists: ${BUNOSHFILE}`);
|
|
16
|
+
}
|
package/src/output.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { render as inkRender } from 'ink';
|
|
2
|
+
import debug from 'debug';
|
|
3
|
+
|
|
4
|
+
export const isStaticOutput = process.env.CI || process.env.DEBUG || !process.stdout.isTTY;
|
|
5
|
+
|
|
6
|
+
if (isStaticOutput) debug.enable('bunosh:*');
|
|
7
|
+
|
|
8
|
+
let renderer = null;
|
|
9
|
+
|
|
10
|
+
export function render(comp = <></>) {
|
|
11
|
+
|
|
12
|
+
if (!renderer) {
|
|
13
|
+
renderer = inkRender(comp);
|
|
14
|
+
return renderer;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
renderer.rerender(comp);
|
|
18
|
+
return renderer;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function renderOnce(comp) {
|
|
22
|
+
if (renderer) await renderer.waitUntilExit();
|
|
23
|
+
render(comp);
|
|
24
|
+
clearRenderer();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function clearRenderer() {
|
|
28
|
+
if (!renderer) return;
|
|
29
|
+
renderer.unmount();
|
|
30
|
+
renderer = null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export function debugTask(task, line) {
|
|
35
|
+
const ns = `bunosh:${task}`;
|
|
36
|
+
debug(ns)(line);
|
|
37
|
+
}
|
package/src/program.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
const { Command } = require("commander");
|
|
2
|
+
import babelParser from "@babel/parser";
|
|
3
|
+
import traverse from "@babel/traverse";
|
|
4
|
+
import color from "chalk";
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import openEditor from 'open-editor';
|
|
7
|
+
import banner from '../templates/banner';
|
|
8
|
+
|
|
9
|
+
export const BUNOSHFILE = `bunosh.tasks.js`;
|
|
10
|
+
|
|
11
|
+
export { banner };
|
|
12
|
+
|
|
13
|
+
export default function bunosh(commands, source) {
|
|
14
|
+
const program = new Command();
|
|
15
|
+
|
|
16
|
+
const internalCommands = [];
|
|
17
|
+
|
|
18
|
+
program.configureHelp({
|
|
19
|
+
commandDescription: _cmd => `${banner}\n Commands are loaded from ${color.bold(BUNOSHFILE)} as JS functions`,
|
|
20
|
+
commandUsage: usg => 'bunosh <command> <args> [options]',
|
|
21
|
+
showGlobalOptions: false,
|
|
22
|
+
// visibleArguments: cmd => cmd.registeredArguments,
|
|
23
|
+
visibleGlobalOptions: _opt => [],
|
|
24
|
+
// Bunosh has no default options
|
|
25
|
+
visibleOptions: _opt => [],
|
|
26
|
+
visibleCommands: cmd => cmd.commands.filter(c => !internalCommands.includes(c)),
|
|
27
|
+
// commandDescription: _opt => '',
|
|
28
|
+
// argumentTerm: (arg) => color.gray("aaa"),
|
|
29
|
+
subcommandTerm: (cmd) => pickColorForColorName(cmd.name()),
|
|
30
|
+
subcommandDescription: (cmd) => cmd.description(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
program.showHelpAfterError();
|
|
34
|
+
program.showSuggestionAfterError(true);
|
|
35
|
+
program.addHelpCommand(false);
|
|
36
|
+
|
|
37
|
+
const completeAst = babelParser.parse(source, {
|
|
38
|
+
sourceType: "module",
|
|
39
|
+
plugins: ["jsx"],
|
|
40
|
+
ranges: true,
|
|
41
|
+
tokens: true,
|
|
42
|
+
comments: true,
|
|
43
|
+
attachComment: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const comments = fetchComments();
|
|
47
|
+
|
|
48
|
+
Object.keys(commands).forEach((fnName) => {
|
|
49
|
+
const fnBody = commands[fnName].toString();
|
|
50
|
+
|
|
51
|
+
const ast = fetchFnAst();
|
|
52
|
+
|
|
53
|
+
const args = parseArgs();
|
|
54
|
+
const opts = parseOpts();
|
|
55
|
+
const comment = comments[fnName];
|
|
56
|
+
|
|
57
|
+
const command = program.command(prepareCommandName(fnName));
|
|
58
|
+
command.hook('preAction', (thisCommand) => {
|
|
59
|
+
process.env.BUNOSH_COMMAND_STARTED = true;
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
args.filter(a => !!a).forEach((arg) => {
|
|
63
|
+
command.argument(`<${arg}>`);
|
|
64
|
+
});
|
|
65
|
+
Object.entries(opts).forEach(([opt, value]) => {
|
|
66
|
+
if (value === false || value === null) command.option(`--${opt}`);
|
|
67
|
+
command.option(`--${opt} [${opt}]`, "", value);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
let description = comment || '';
|
|
71
|
+
|
|
72
|
+
let argsAndOpts = '';
|
|
73
|
+
if (args.length > 0) argsAndOpts += `${args.map(a => color.blueBright(`<${a}>`)).join(' ')}`
|
|
74
|
+
if (Object.keys(opts).length > 0) argsAndOpts += ` ${Object.keys(opts).map(opt => color.blueBright(`--${opt}`)).join(' ')}`
|
|
75
|
+
if (comment && argsAndOpts) description += `\n ${argsAndOpts.trim()}\n`;
|
|
76
|
+
|
|
77
|
+
command.description(description);
|
|
78
|
+
command.action(commands[fnName].bind(commands));
|
|
79
|
+
|
|
80
|
+
// We either take the ast from the file or we parse the function body
|
|
81
|
+
function fetchFnAst() {
|
|
82
|
+
let hasFnInSource = false;
|
|
83
|
+
|
|
84
|
+
traverse(completeAst, {
|
|
85
|
+
FunctionDeclaration(path) {
|
|
86
|
+
if (path.node.id.name == fnName) {
|
|
87
|
+
hasFnInSource = true;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (hasFnInSource) return completeAst;
|
|
94
|
+
|
|
95
|
+
return babelParser.parse(fnBody, { comment: true, tokens: true });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// We parse command args from function args
|
|
99
|
+
function parseArgs() {
|
|
100
|
+
const functionArguments = [];
|
|
101
|
+
|
|
102
|
+
traverse(ast, {
|
|
103
|
+
FunctionDeclaration(path) {
|
|
104
|
+
if (path.node.id.name !== fnName) return;
|
|
105
|
+
|
|
106
|
+
const params = path.node.params
|
|
107
|
+
.filter((node) => {
|
|
108
|
+
return node?.right?.type !== "ObjectExpression";
|
|
109
|
+
})
|
|
110
|
+
.map((param) => param.name);
|
|
111
|
+
|
|
112
|
+
functionArguments.push(params);
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return functionArguments.flat();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// We parse command options from the object of last function args
|
|
120
|
+
function parseOpts() {
|
|
121
|
+
let functionOpts = {};
|
|
122
|
+
|
|
123
|
+
traverse(ast, {
|
|
124
|
+
FunctionDeclaration(path) {
|
|
125
|
+
if (path.node.id.name !== fnName) return;
|
|
126
|
+
|
|
127
|
+
const node = path.node.params.pop();
|
|
128
|
+
if (!node) return;
|
|
129
|
+
if (
|
|
130
|
+
!node.type === "AssignmentPattern" &&
|
|
131
|
+
node.right.type === "ObjectExpression"
|
|
132
|
+
)
|
|
133
|
+
return;
|
|
134
|
+
|
|
135
|
+
node.right.properties.forEach((p) => {
|
|
136
|
+
if (
|
|
137
|
+
["NumericLiteral", "StringLiteral", "BooleanLiteral"].includes(
|
|
138
|
+
p.value.type,
|
|
139
|
+
)
|
|
140
|
+
) {
|
|
141
|
+
functionOpts[camelToDasherize(p.key.name)] = p.value.value;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (p.value.type === "NullLiteral") {
|
|
146
|
+
functionOpts[camelToDasherize(p.key.name)] = null;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (p.value.type == "UnaryExpression" && p.value.operator == "!") {
|
|
151
|
+
functionOpts[camelToDasherize(p.key.name)] =
|
|
152
|
+
!p.value.argument.value;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// ignore other options for now
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return functionOpts;
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const editCmd = program.command('edit')
|
|
165
|
+
.description('Open the bunosh file in your editor. $EDITOR or \'code\' is used.')
|
|
166
|
+
.action(() => {
|
|
167
|
+
openEditor([{
|
|
168
|
+
file: BUNOSHFILE,
|
|
169
|
+
}], {
|
|
170
|
+
editor: process.env.EDITOR ? null : 'code',
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
internalCommands.push(editCmd);
|
|
175
|
+
|
|
176
|
+
const exoprtCmd = program.command('export:scripts')
|
|
177
|
+
.description('Export commands to "scripts" section of package.json.')
|
|
178
|
+
.action(() => {
|
|
179
|
+
|
|
180
|
+
exportFn(Object.keys(commands));
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
internalCommands.push(exoprtCmd);
|
|
184
|
+
|
|
185
|
+
program.addHelpText('after', `
|
|
186
|
+
Example call:
|
|
187
|
+
bunosh ${program.commands.find(c => !internalCommands.includes(c)).name()} ${color.gray('<args>')} ${color.gray('[options]')}
|
|
188
|
+
|
|
189
|
+
Special Commands:
|
|
190
|
+
📝 Edit bunosh file: ${color.bold('bunosh edit')}
|
|
191
|
+
📥 Export scripts to package.json: ${color.bold('bunosh export:scripts')}
|
|
192
|
+
|
|
193
|
+
`);
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
program.on("command:*", (cmd) => {
|
|
197
|
+
console.log(`\nUnknown command ${cmd}\n`);
|
|
198
|
+
program.outputHelp();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
program.parse(process.argv);
|
|
202
|
+
|
|
203
|
+
function fetchComments() {
|
|
204
|
+
const comments = {};
|
|
205
|
+
|
|
206
|
+
let startFromLine = 0;
|
|
207
|
+
|
|
208
|
+
traverse(completeAst, {
|
|
209
|
+
FunctionDeclaration(path) {
|
|
210
|
+
const functionName = path.node.id && path.node.id.name;
|
|
211
|
+
|
|
212
|
+
const commentSource = source
|
|
213
|
+
.split("\n")
|
|
214
|
+
.slice(startFromLine, path.node?.loc?.start?.line)
|
|
215
|
+
.join("\n");
|
|
216
|
+
const matches = commentSource.match(
|
|
217
|
+
/\/\*\*\s([\s\S]*)\\*\/\s*export/,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
if (matches && matches[1]) {
|
|
221
|
+
comments[functionName] = matches[1]
|
|
222
|
+
.replace(/^\s*\*\s*/gm, "") // remove * chars
|
|
223
|
+
.trim()
|
|
224
|
+
.replace(/^@.*$/gm, "") // remove params from description
|
|
225
|
+
.trim();
|
|
226
|
+
} else {
|
|
227
|
+
const innerComments = path.node?.body?.innerComments;
|
|
228
|
+
|
|
229
|
+
if (innerComments && innerComments.length > 0) {
|
|
230
|
+
comments[functionName] = innerComments[0].value.trim();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
startFromLine = path.node?.loc?.end?.line;
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return comments;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function prepareCommandName(name) {
|
|
243
|
+
name = name
|
|
244
|
+
.split(/(?=[A-Z])/)
|
|
245
|
+
.join("-")
|
|
246
|
+
.toLowerCase();
|
|
247
|
+
return name.replace("-", ":");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function camelToDasherize(camelCaseString) {
|
|
251
|
+
return camelCaseString.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function pickColorForColorName(commandName) {
|
|
255
|
+
const colors = [
|
|
256
|
+
color.red,
|
|
257
|
+
color.green,
|
|
258
|
+
color.yellow,
|
|
259
|
+
color.blue,
|
|
260
|
+
color.magenta,
|
|
261
|
+
color.cyan,
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
const prefixName = camelToDasherize(commandName).split("-")[0];
|
|
265
|
+
|
|
266
|
+
const index =
|
|
267
|
+
prefixName.split("").reduce((acc, char) => {
|
|
268
|
+
return acc + char.charCodeAt(0);
|
|
269
|
+
}, 0) % colors.length;
|
|
270
|
+
|
|
271
|
+
return color.bold(colors[index](commandName));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function parseDocBlock(funcName, code) {
|
|
275
|
+
const regex = new RegExp(
|
|
276
|
+
`\\/\\*\\*([\\s\\S]*?)\\*\\/\\s*export\\s+function\\s+${funcName}\\s*\\(`,
|
|
277
|
+
);
|
|
278
|
+
const match = code.match(regex);
|
|
279
|
+
|
|
280
|
+
if (match && match[1]) {
|
|
281
|
+
// Remove leading asterisks and trim the result
|
|
282
|
+
return match[1]
|
|
283
|
+
.replace(/^\s*\*\s*/gm, "")
|
|
284
|
+
.split("\n")[0]
|
|
285
|
+
.trim();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function exportFn(commands) {
|
|
292
|
+
if (!fs.existsSync(BUNOSHFILE)) {
|
|
293
|
+
console.error(`${BUNOSHFILE} file not found, can\'t export its commands.`);
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!fs.existsSync('package.json')) {
|
|
298
|
+
console.error('package.json now found, can\'t set scripts.');
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const pkg = JSON.parse(fs.readFileSync('package.json').toString());
|
|
303
|
+
if (!pkg.scripts) {
|
|
304
|
+
pkg.scripts = {};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// cleanup from previously exported
|
|
308
|
+
for (let s in pkg.scripts ) {
|
|
309
|
+
if (pkg[s] && pkg[s].startsWith('bunosh')) delete pkg[s];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const scripts = Object.fromEntries(commands.map(prepareCommandName).map(k => [k, 'bunosh '+k]));
|
|
313
|
+
|
|
314
|
+
pkg.scripts = {...pkg.scripts, ...scripts };
|
|
315
|
+
|
|
316
|
+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 4));
|
|
317
|
+
|
|
318
|
+
console.log('Added scripts:');
|
|
319
|
+
console.log();
|
|
320
|
+
|
|
321
|
+
Object.keys(scripts).forEach(k => console.log(' bun run ' + k));
|
|
322
|
+
|
|
323
|
+
console.log();
|
|
324
|
+
console.log('package.json updated');
|
|
325
|
+
console.log(`${Object.keys(scripts).length} scripts exported`);
|
|
326
|
+
return true;
|
|
327
|
+
}
|
package/src/task.jsx
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import React, {useState, useEffect} from 'react';
|
|
2
|
+
import { Text, Box } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import { Timer } from 'timer-node';
|
|
5
|
+
import { render, clearRenderer, renderOnce } from './output';
|
|
6
|
+
|
|
7
|
+
export const TaskStatus = {
|
|
8
|
+
RUNNING: 'running',
|
|
9
|
+
FAIL: 'fail',
|
|
10
|
+
SUCCESS: 'success'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const tasksExecuted = [];
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
let activeComponents = [];
|
|
17
|
+
let activeTasks = [];
|
|
18
|
+
|
|
19
|
+
let stopFailToggle = true;
|
|
20
|
+
|
|
21
|
+
export function stopOnFail(enable = true) {
|
|
22
|
+
stopFailToggle = enable;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ignoreFail(enable = true) {
|
|
26
|
+
stopFailToggle = !enable;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const globalTimer = new Timer({ label: 'global', precision: 'ms'});
|
|
30
|
+
globalTimer.start();
|
|
31
|
+
process.on('exit', (code) => {
|
|
32
|
+
// we don't need this banner if no tasks were executed
|
|
33
|
+
if (!process.env.BUNOSH_COMMAND_STARTED) return;
|
|
34
|
+
|
|
35
|
+
globalTimer.stop();
|
|
36
|
+
const success = code === 0;
|
|
37
|
+
const tasksFailed = tasksExecuted.filter(ti => ti.result?.status === TaskStatus.FAIL).length;
|
|
38
|
+
renderOnce(<Box flexDirection='row' gap={2}>
|
|
39
|
+
<Text bold backgroundColor={!success && 'red'}>🍲
|
|
40
|
+
{success ? '' : 'FAIL '}
|
|
41
|
+
</Text>
|
|
42
|
+
<Text dimColor >Exit Code: <Text bold color={code === 0 ? 'green' : 'red'}>{code}</Text></Text>
|
|
43
|
+
<Text dimColor>Tasks executed: <Text bold>{tasksExecuted.length}</Text></Text>
|
|
44
|
+
{!!tasksFailed && <Text dimColor>Tasks failed: <Text bold color="red">{tasksFailed}</Text></Text>}
|
|
45
|
+
<Text dimColor>Time: <Text bold>{globalTimer.ms()}</Text>ms</Text>
|
|
46
|
+
</Box>);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export async function task(name, fn) {
|
|
50
|
+
let fnResult = null;
|
|
51
|
+
|
|
52
|
+
if (!fn) {
|
|
53
|
+
fn = name;
|
|
54
|
+
name = fn.toString().slice(0, 50).replace(/\s+/g, ' ').trim();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const promise = Promise.resolve(fn()).then((ret) => {
|
|
58
|
+
fnResult = ret;
|
|
59
|
+
return TaskResult.success(ret);
|
|
60
|
+
}).catch((err) => {
|
|
61
|
+
return TaskResult.fail(err);
|
|
62
|
+
});
|
|
63
|
+
const taskInfo = new TaskInfo({ promise, kind: 'task', text: name });
|
|
64
|
+
|
|
65
|
+
const TaskOutput = () => {
|
|
66
|
+
const [output, setOutput] = useState(null);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!fnResult?.toString) return;
|
|
69
|
+
promise.then(_ => setOutput(fnResult.toString()));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return <Box overflow='hidden' height={10} borderStyle="round" >
|
|
73
|
+
<Text dimColor={true}>{output}</Text>
|
|
74
|
+
</Box>
|
|
75
|
+
}
|
|
76
|
+
renderTask(taskInfo, <TaskOutput />);
|
|
77
|
+
|
|
78
|
+
await promise;
|
|
79
|
+
return fnResult;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function addToRender(comp) {
|
|
83
|
+
activeComponents.push(comp);
|
|
84
|
+
activeTasks.push(comp.props.taskInfo);
|
|
85
|
+
|
|
86
|
+
if (activeComponents.length < 2) {
|
|
87
|
+
render(comp);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
render(<Box flexDirection='row' gap={1}>
|
|
91
|
+
{activeComponents}
|
|
92
|
+
</Box>);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function removeFromRender(taskInfo) {
|
|
96
|
+
activeTasks = activeTasks.filter((ti) => ti?.id !== taskInfo?.id);
|
|
97
|
+
|
|
98
|
+
if (activeTasks.length === 0) {
|
|
99
|
+
clearRenderer();
|
|
100
|
+
activeComponents = [];
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const renderTask = async (taskInfo, children) => {
|
|
106
|
+
if (tasksExecuted.map(t => t.id).includes(taskInfo.id)) return; // alraday executed
|
|
107
|
+
|
|
108
|
+
tasksExecuted.push(taskInfo);
|
|
109
|
+
addToRender(<Task key={`${tasksExecuted.length}_${taskInfo}`} taskInfo={taskInfo}>{children}</Task>);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
function Status({ taskStatus }) {
|
|
114
|
+
if (!taskStatus) return <Text dimColor>-</Text>;
|
|
115
|
+
if (taskStatus == TaskStatus.SUCCESS) return <Text color='green' bold>✓</Text>;
|
|
116
|
+
if (taskStatus == TaskStatus.FAIL) return <Text color='red' bold>×</Text>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const Task = ({ taskInfo, children }) => {
|
|
120
|
+
const timer = new Timer({ label: taskInfo.text, precision: 'ms'});
|
|
121
|
+
|
|
122
|
+
const [time, setTime] = useState(null);
|
|
123
|
+
const [status, setStatus] = useState(null);
|
|
124
|
+
|
|
125
|
+
const { promise } = taskInfo;
|
|
126
|
+
timer.start();
|
|
127
|
+
|
|
128
|
+
function updateTaskInfo(result) {
|
|
129
|
+
timer.stop();
|
|
130
|
+
taskInfo.result = result;
|
|
131
|
+
taskInfo.time = timer.ms();
|
|
132
|
+
setStatus(result.status);
|
|
133
|
+
setTime(timer.ms());
|
|
134
|
+
removeFromRender(taskInfo);
|
|
135
|
+
|
|
136
|
+
// hard exit, task has failed
|
|
137
|
+
if (result.status === TaskStatus.FAIL && stopFailToggle) {
|
|
138
|
+
process.exit(1);
|
|
139
|
+
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
promise.then((result) => {
|
|
145
|
+
updateTaskInfo(result)
|
|
146
|
+
}).catch((err) => {
|
|
147
|
+
updateTaskInfo(TaskResult.fail(err.toString()));
|
|
148
|
+
});
|
|
149
|
+
}, []);
|
|
150
|
+
|
|
151
|
+
return (<Box flexGrow={1} flexBasis="50%" flexDirection='column'>
|
|
152
|
+
<Box gap={1} flexDirection='row' alignItems='flex-start' justifyContent="flex-start">
|
|
153
|
+
<Status taskStatus={status} />
|
|
154
|
+
|
|
155
|
+
{taskInfo.titleComponent}
|
|
156
|
+
|
|
157
|
+
{taskInfo.extraText && <Text color='cyan' dimColor>{taskInfo.extraText}</Text>}
|
|
158
|
+
|
|
159
|
+
{time === null && <Spinner />}
|
|
160
|
+
{time !== null && <Text dimColor={true}>{time}ms</Text>}
|
|
161
|
+
|
|
162
|
+
</Box>
|
|
163
|
+
|
|
164
|
+
{children}
|
|
165
|
+
</Box>
|
|
166
|
+
);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export class TaskInfo {
|
|
170
|
+
constructor({ promise, kind, text, extraText }) {
|
|
171
|
+
if (!kind) throw new Error('TaskInfo: kind is required');
|
|
172
|
+
if (!text) throw new Error('TaskInfo: text is required');
|
|
173
|
+
|
|
174
|
+
this.id = `${kind}-${text.slice(0,30).replace(/\s/g, '-')}-${Math.random().toString(36).substring(7)}`;
|
|
175
|
+
this.kind = kind;
|
|
176
|
+
this.text = text;
|
|
177
|
+
this.extraText = extraText;
|
|
178
|
+
this.promise = promise;
|
|
179
|
+
this.result = null;
|
|
180
|
+
this.time = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
get titleComponent() {
|
|
184
|
+
return <>
|
|
185
|
+
<Text bold>{this.kind}</Text>
|
|
186
|
+
<Text color='yellow'>{this.text}</Text>
|
|
187
|
+
</>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
toString() {
|
|
191
|
+
return `${this.kind} ${this.text}`;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export class TaskResult {
|
|
196
|
+
constructor({ status, output }) {
|
|
197
|
+
this.status = status;
|
|
198
|
+
this.output = output;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
static fail(output = null) {
|
|
202
|
+
return new TaskResult({ status: TaskStatus.FAIL, output });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static success(output = null) {
|
|
206
|
+
return new TaskResult({ status: TaskStatus.SUCCESS, output });
|
|
207
|
+
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { $ } from "bun";
|
|
2
|
+
import React, {useState, useEffect} from 'react';
|
|
3
|
+
import { Text, Box } from 'ink';
|
|
4
|
+
import { debugTask, isStaticOutput } from '../output.js';
|
|
5
|
+
import { renderTask, TaskInfo, TaskResult } from '../task.jsx';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_OUTPUT_SIZE = 10;
|
|
8
|
+
|
|
9
|
+
export default function exec(strings, ...values) {
|
|
10
|
+
|
|
11
|
+
const cmd = strings.reduce((accumulator, str, i) => {
|
|
12
|
+
return accumulator + str + (values[i] || '');
|
|
13
|
+
}, '');
|
|
14
|
+
|
|
15
|
+
let resolve, reject;
|
|
16
|
+
const cmdPromise = new Promise((res, rej) => {
|
|
17
|
+
resolve = res;
|
|
18
|
+
reject = rej;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
let envs = null
|
|
22
|
+
cmdPromise.env = (newEnvs) => envs = newEnvs;
|
|
23
|
+
|
|
24
|
+
let cwd = null;
|
|
25
|
+
cmdPromise.cwd = (newCwd) => cwd = newCwd;
|
|
26
|
+
|
|
27
|
+
const ExecOutput = () => {
|
|
28
|
+
let shell = $(strings, ...values);
|
|
29
|
+
if (cwd) {
|
|
30
|
+
shell = shell.cwd(cwd);
|
|
31
|
+
}
|
|
32
|
+
if (envs) {
|
|
33
|
+
shell = shell.env(envs);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const stackOutput = [];
|
|
37
|
+
|
|
38
|
+
const [printedLines, setPrintedLines] = useState([]);
|
|
39
|
+
const [exitCode, setExitCode] = useState(null);
|
|
40
|
+
|
|
41
|
+
const readNextChunk = async () => {
|
|
42
|
+
for await (let line of shell.lines()) {
|
|
43
|
+
debugTask(cmd, line);
|
|
44
|
+
stackOutput.push(line);
|
|
45
|
+
setPrintedLines(stackOutput.slice(-DEFAULT_OUTPUT_SIZE));
|
|
46
|
+
}
|
|
47
|
+
const code = parseInt((await shell).exitCode, 10);
|
|
48
|
+
setExitCode(code);
|
|
49
|
+
if (code == 0) resolve(TaskResult.success(stackOutput.join('\n')));
|
|
50
|
+
if (code !== 0) resolve(TaskResult.fail(stackOutput.join('\n')));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
readNextChunk();
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
if (isStaticOutput) return <></>
|
|
58
|
+
|
|
59
|
+
return <>
|
|
60
|
+
<Box marginBottom={1} flexDirection="column">
|
|
61
|
+
{printedLines.length > 0 && <Box borderStyle="round" padding={1} flexDirection="column" >
|
|
62
|
+
{printedLines.map((line, i) => <Text wrap="truncate" key={i}>{line.trim()}</Text>)}
|
|
63
|
+
</Box>}
|
|
64
|
+
<Box>
|
|
65
|
+
{exitCode === 0 && <Text dimColor>Success! Exit code: {exitCode}</Text>}
|
|
66
|
+
{exitCode !== null && exitCode != 0 && <Text dimColor color="red">Failure! Exit code: <Text bold>{exitCode}</Text></Text>}
|
|
67
|
+
</Box>
|
|
68
|
+
</Box>
|
|
69
|
+
</>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
let extraText = '';
|
|
74
|
+
if (cwd) extraText += `at ${cwd}`;
|
|
75
|
+
if (envs) extraText += ` with ${envs}`;
|
|
76
|
+
|
|
77
|
+
renderTask(new TaskInfo({
|
|
78
|
+
promise: cmdPromise,
|
|
79
|
+
kind: 'exec',
|
|
80
|
+
text: cmd,
|
|
81
|
+
extraText: extraText,
|
|
82
|
+
}), <ExecOutput />);
|
|
83
|
+
}, 0);
|
|
84
|
+
|
|
85
|
+
return cmdPromise;
|
|
86
|
+
}
|
|
87
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, {useState, useEffect} from 'react';
|
|
2
|
+
import { Text, Box } from 'ink';
|
|
3
|
+
import { debugTask, isStaticOutput } from '../output.js';
|
|
4
|
+
import { renderTask, TaskInfo, TaskResult } from '../task.jsx';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const DEFAULT_OUTPUT_SIZE = 10;
|
|
8
|
+
|
|
9
|
+
export default async function httpFetch() {
|
|
10
|
+
const textDecoder = new TextDecoder();
|
|
11
|
+
|
|
12
|
+
const url = arguments[0];
|
|
13
|
+
const method = arguments[1]?.method || 'GET';
|
|
14
|
+
const text = `${method} ${url}`;
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
let resolve, reject;
|
|
18
|
+
let promise = new Promise((res, rej) => {
|
|
19
|
+
resolve = res;
|
|
20
|
+
reject = rej;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const FetchOutput = () => {
|
|
24
|
+
const fetchPromise = fetch(...arguments);
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const stackOutput = [];
|
|
28
|
+
|
|
29
|
+
const [response, setResponse] = useState(null);
|
|
30
|
+
const [printedLines, setPrintedLines] = useState([]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
fetchPromise.then(async (response) => {
|
|
34
|
+
setResponse(response);
|
|
35
|
+
|
|
36
|
+
setPrintedLines(['a', 'b', 'c']);
|
|
37
|
+
for await (const chunk of response.body) {
|
|
38
|
+
|
|
39
|
+
const lines = textDecoder.decode(chunk, { stream: true }).toString().split('\n');
|
|
40
|
+
lines.forEach((line) => {
|
|
41
|
+
debugTask(text, line);
|
|
42
|
+
stackOutput.push(line);
|
|
43
|
+
setPrintedLines(stackOutput.slice(-DEFAULT_OUTPUT_SIZE));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
resolve(TaskResult.success(stackOutput.join('\n')));
|
|
48
|
+
resolve(TaskResult.fail(stackOutput.join('\n')));
|
|
49
|
+
});
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
if (isStaticOutput) return <></>
|
|
53
|
+
|
|
54
|
+
return <>
|
|
55
|
+
<Box marginBottom={1} flexDirection="column">
|
|
56
|
+
{printedLines && <Box borderStyle="round" padding={1} flexDirection="column" >
|
|
57
|
+
{printedLines.map((line, i) => <Text wrap="truncate" key={i}>{line.trim()}</Text>)}
|
|
58
|
+
</Box>}
|
|
59
|
+
<Box>
|
|
60
|
+
{response && response.ok && <Text dimColor>Success. Status: {response.statusText} ({response.status}) {response.headers.get('Content-Type')}</Text>}
|
|
61
|
+
{response && !response.ok && <Text dimColor color="red">Failure! Status: <Text bold>{response.statusText} ({response.status})</Text></Text>}
|
|
62
|
+
</Box>
|
|
63
|
+
</Box>
|
|
64
|
+
</>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
renderTask(new TaskInfo({
|
|
68
|
+
promise,
|
|
69
|
+
kind: 'fetch',
|
|
70
|
+
text,
|
|
71
|
+
}), <FetchOutput />);
|
|
72
|
+
|
|
73
|
+
return promise;
|
|
74
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { task } from '../task.jsx';
|
|
4
|
+
|
|
5
|
+
export default function writeToFile(fileName, lineBuilderFn) {
|
|
6
|
+
|
|
7
|
+
let text = '';
|
|
8
|
+
|
|
9
|
+
const fileLine = function(strings, ...values) {
|
|
10
|
+
if (strings instanceof Array) {
|
|
11
|
+
const line = strings.reduce((accumulator, str, i) => {
|
|
12
|
+
return accumulator + str + (values[i] || '');
|
|
13
|
+
}, '');
|
|
14
|
+
text += `${line}\n`;
|
|
15
|
+
return;
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
text += `${strings}\n`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fileLine.fromFile = function(file) {
|
|
23
|
+
text += fs.readFileSync(path.join(process.cwd(), file));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fileLine.currentFile = function() {
|
|
27
|
+
text += fs.readFileSync(path.join(process.cwd(), fileName));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
task(`write to file ${fileName}`, () => {
|
|
31
|
+
|
|
32
|
+
if (lineBuilderFn instanceof Function) {
|
|
33
|
+
lineBuilderFn(fileLine);
|
|
34
|
+
} else {
|
|
35
|
+
text += lineBuilderFn;
|
|
36
|
+
}
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
fs.writeFileSync(fileName, text);
|
|
39
|
+
}, 0);
|
|
40
|
+
// render text first before writing to file
|
|
41
|
+
return text;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return text;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import color from "picocolors";
|
|
2
|
+
|
|
3
|
+
export default `
|
|
4
|
+
[38;2;0;0;255m─[39m[38;2;0;21;255m╔[39m[38;2;0;42;255m═[39m[38;2;0;63;255m═[39m[38;2;0;85;255m╗[39m[38;2;0;106;255m─[39m[38;2;0;127;255m─[39m[38;2;0;148;255m╔[39m[38;2;0;169;255m╗[39m[38;2;0;191;255m─[39m[38;2;0;212;255m╔[39m[38;2;0;233;255m╗[39m[38;2;0;255;255m─[39m[38;2;0;255;233m╔[39m[38;2;0;255;212m═[39m[38;2;0;255;191m╗[39m[38;2;0;255;169m─[39m[38;2;0;255;148m╔[39m[38;2;0;255;127m╗[39m[38;2;0;255;106m─[39m[38;2;0;255;85m╔[39m[38;2;0;255;63m═[39m[38;2;0;255;42m═[39m[38;2;0;255;21m═[39m[38;2;0;255;0m╗[39m[38;2;21;255;0m─[39m[38;2;42;255;0m╔[39m[38;2;63;255;0m═[39m[38;2;84;255;0m═[39m[38;2;106;255;0m═[39m[38;2;127;255;0m╗[39m[38;2;148;255;0m─[39m[38;2;170;255;0m╔[39m[38;2;191;255;0m╗[39m[38;2;212;255;0m─[39m[38;2;233;255;0m╔[39m[38;2;255;255;0m╗[39m
|
|
5
|
+
[38;2;0;0;255m─[39m[38;2;0;21;255m║[39m[38;2;0;42;255m╔[39m[38;2;0;63;255m╗[39m[38;2;0;85;255m║[39m[38;2;0;106;255m─[39m[38;2;0;127;255m─[39m[38;2;0;148;255m║[39m[38;2;0;169;255m║[39m[38;2;0;191;255m─[39m[38;2;0;212;255m║[39m[38;2;0;233;255m║[39m[38;2;0;255;255m─[39m[38;2;0;255;233m║[39m[38;2;0;255;212m║[39m[38;2;0;255;191m╚[39m[38;2;0;255;169m╗[39m[38;2;0;255;148m║[39m[38;2;0;255;127m║[39m[38;2;0;255;106m─[39m[38;2;0;255;85m║[39m[38;2;0;255;63m╔[39m[38;2;0;255;42m═[39m[38;2;0;255;21m╗[39m[38;2;0;255;0m║[39m[38;2;21;255;0m─[39m[38;2;42;255;0m║[39m[38;2;63;255;0m╔[39m[38;2;84;255;0m═[39m[38;2;106;255;0m╗[39m[38;2;127;255;0m║[39m[38;2;148;255;0m─[39m[38;2;170;255;0m║[39m[38;2;191;255;0m║[39m[38;2;212;255;0m─[39m[38;2;233;255;0m║[39m[38;2;255;255;0m║[39m
|
|
6
|
+
[38;2;0;0;255m─[39m[38;2;0;21;255m║[39m[38;2;0;42;255m╚[39m[38;2;0;63;255m╝[39m[38;2;0;85;255m╚[39m[38;2;0;106;255m╗[39m[38;2;0;127;255m─[39m[38;2;0;148;255m║[39m[38;2;0;169;255m║[39m[38;2;0;191;255m─[39m[38;2;0;212;255m║[39m[38;2;0;233;255m║[39m[38;2;0;255;255m─[39m[38;2;0;255;233m║[39m[38;2;0;255;212m╔[39m[38;2;0;255;191m╗[39m[38;2;0;255;169m╚[39m[38;2;0;255;148m╝[39m[38;2;0;255;127m║[39m[38;2;0;255;106m─[39m[38;2;0;255;85m║[39m[38;2;0;255;63m║[39m[38;2;0;255;42m─[39m[38;2;0;255;21m║[39m[38;2;0;255;0m║[39m[38;2;21;255;0m─[39m[38;2;42;255;0m║[39m[38;2;63;255;0m╚[39m[38;2;84;255;0m═[39m[38;2;106;255;0m═[39m[38;2;127;255;0m╗[39m[38;2;148;255;0m─[39m[38;2;170;255;0m║[39m[38;2;191;255;0m╚[39m[38;2;212;255;0m═[39m[38;2;233;255;0m╝[39m[38;2;255;255;0m║[39m
|
|
7
|
+
[38;2;0;0;255m─[39m[38;2;0;21;255m║[39m[38;2;0;42;255m╔[39m[38;2;0;63;255m═[39m[38;2;0;85;255m╗[39m[38;2;0;106;255m║[39m[38;2;0;127;255m─[39m[38;2;0;148;255m║[39m[38;2;0;169;255m║[39m[38;2;0;191;255m─[39m[38;2;0;212;255m║[39m[38;2;0;233;255m║[39m[38;2;0;255;255m─[39m[38;2;0;255;233m║[39m[38;2;0;255;212m║[39m[38;2;0;255;191m╚[39m[38;2;0;255;169m╗[39m[38;2;0;255;148m║[39m[38;2;0;255;127m║[39m[38;2;0;255;106m─[39m[38;2;0;255;85m║[39m[38;2;0;255;63m║[39m[38;2;0;255;42m─[39m[38;2;0;255;21m║[39m[38;2;0;255;0m║[39m[38;2;21;255;0m─[39m[38;2;42;255;0m╚[39m[38;2;63;255;0m═[39m[38;2;84;255;0m═[39m[38;2;106;255;0m╗[39m[38;2;127;255;0m║[39m[38;2;148;255;0m─[39m[38;2;170;255;0m║[39m[38;2;191;255;0m╔[39m[38;2;212;255;0m═[39m[38;2;233;255;0m╗[39m[38;2;255;255;0m║[39m
|
|
8
|
+
[38;2;0;0;255m─[39m[38;2;0;21;255m║[39m[38;2;0;42;255m╚[39m[38;2;0;63;255m═[39m[38;2;0;85;255m╝[39m[38;2;0;106;255m║[39m[38;2;0;127;255m─[39m[38;2;0;148;255m║[39m[38;2;0;169;255m╚[39m[38;2;0;191;255m═[39m[38;2;0;212;255m╝[39m[38;2;0;233;255m║[39m[38;2;0;255;255m─[39m[38;2;0;255;233m║[39m[38;2;0;255;212m║[39m[38;2;0;255;191m─[39m[38;2;0;255;169m║[39m[38;2;0;255;148m║[39m[38;2;0;255;127m║[39m[38;2;0;255;106m─[39m[38;2;0;255;85m║[39m[38;2;0;255;63m╚[39m[38;2;0;255;42m═[39m[38;2;0;255;21m╝[39m[38;2;0;255;0m║[39m[38;2;21;255;0m─[39m[38;2;42;255;0m║[39m[38;2;63;255;0m╚[39m[38;2;84;255;0m═[39m[38;2;106;255;0m╝[39m[38;2;127;255;0m║[39m[38;2;148;255;0m─[39m[38;2;170;255;0m║[39m[38;2;191;255;0m║[39m[38;2;212;255;0m─[39m[38;2;233;255;0m║[39m[38;2;255;255;0m║[39m
|
|
9
|
+
[38;2;0;0;255m─[39m[38;2;0;21;255m╚[39m[38;2;0;42;255m═[39m[38;2;0;63;255m═[39m[38;2;0;85;255m═[39m[38;2;0;106;255m╝[39m[38;2;0;127;255m─[39m[38;2;0;148;255m╚[39m[38;2;0;169;255m═[39m[38;2;0;191;255m═[39m[38;2;0;212;255m═[39m[38;2;0;233;255m╝[39m[38;2;0;255;255m─[39m[38;2;0;255;233m╚[39m[38;2;0;255;212m╝[39m[38;2;0;255;191m─[39m[38;2;0;255;169m╚[39m[38;2;0;255;148m═[39m[38;2;0;255;127m╝[39m[38;2;0;255;106m─[39m[38;2;0;255;85m╚[39m[38;2;0;255;63m═[39m[38;2;0;255;42m═[39m[38;2;0;255;21m═[39m[38;2;0;255;0m╝[39m[38;2;21;255;0m─[39m[38;2;42;255;0m╚[39m[38;2;63;255;0m═[39m[38;2;84;255;0m═[39m[38;2;106;255;0m═[39m[38;2;127;255;0m╝[39m[38;2;148;255;0m─[39m[38;2;170;255;0m╚[39m[38;2;191;255;0m╝[39m[38;2;212;255;0m─[39m[38;2;233;255;0m╚[39m[38;2;255;255;0m╝[39m
|
|
10
|
+
|
|
11
|
+
🍲 ${color.bold(color.white('Bunosh'))} - your ${color.bold('exceptional')} task runner powered by Bun
|
|
12
|
+
`.trim();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
export default () => `import { exec, fetch, ignoreFail } from "bunosh";
|
|
3
|
+
import { say, yell } from "bunosh/io";
|
|
4
|
+
import { writeToFile } from "bunosh/files";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 🎉 Hello world command
|
|
8
|
+
*/
|
|
9
|
+
export async function helloWorld() {
|
|
10
|
+
// use say() to print to the console
|
|
11
|
+
// say("Hello World!");
|
|
12
|
+
|
|
13
|
+
// use exec`` to execute shell scripts:
|
|
14
|
+
// await exec\`git status\`
|
|
15
|
+
|
|
16
|
+
// use fetch() to make HTTP requests
|
|
17
|
+
// await fetch('https://reqres.in/api/users')
|
|
18
|
+
|
|
19
|
+
// add arguments and options to this function if needed
|
|
20
|
+
// export async function helloWorld(userName, opts = { force: false })
|
|
21
|
+
//
|
|
22
|
+
// bunosh hello:world 'bob' --force
|
|
23
|
+
|
|
24
|
+
// use ignoreFail(true) to prevent the command from stopping on error
|
|
25
|
+
|
|
26
|
+
yell("Heloo Bunosh!");
|
|
27
|
+
say('Edit me with "bunosh edit"');
|
|
28
|
+
}
|
|
29
|
+
`;
|
package/test.txt
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
lock file
|
|
2
|
+
Hello world
|
|
3
|
+
{
|
|
4
|
+
"name": "bunosh",
|
|
5
|
+
"module": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bunosh": "./run.js"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@types/bun": "latest",
|
|
11
|
+
"prettier": "^3.2.4",
|
|
12
|
+
"lint-staged": "^15.2.0"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"typescript": "^5.0.0"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@babel/parser": "^7.23.6",
|
|
19
|
+
"@babel/traverse": "^7.23.7",
|
|
20
|
+
"cfonts": "^3.2.0",
|
|
21
|
+
"chalk": "^5.3.0",
|
|
22
|
+
"commander": "^11.1.0",
|
|
23
|
+
"debug": "^4.3.4",
|
|
24
|
+
"fs-extra": "^11.2.0",
|
|
25
|
+
"ink": "^4.4.1",
|
|
26
|
+
"ink-big-text": "^2.0.0",
|
|
27
|
+
"ink-gradient": "^3.0.0",
|
|
28
|
+
"ink-spinner": "^5.0.0",
|
|
29
|
+
"inquirer": "^9.2.13",
|
|
30
|
+
"open-editor": "^4.1.1",
|
|
31
|
+
"picocolors": "^1.0.0",
|
|
32
|
+
"react": "^18.2.0",
|
|
33
|
+
"timer-node": "^5.0.7"
|
|
34
|
+
},
|
|
35
|
+
"lint-staged": {
|
|
36
|
+
"*.{js,css,md}": "prettier --write"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"hello:other": "bunosh hello:other",
|
|
40
|
+
"hello:world": "bunosh hello:world",
|
|
41
|
+
"update:to-production": "bunosh update:to-production",
|
|
42
|
+
"update:to-staging": "bunosh update:to-staging"
|
|
43
|
+
}
|
|
44
|
+
}
|