goke 6.4.0 → 6.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +261 -59
- package/dist/__test__/index.test.js +80 -0
- package/dist/__test__/just-bash.test.js +128 -0
- package/dist/__test__/types.test-d.js +9 -3
- package/dist/goke-fs.d.ts +25 -0
- package/dist/goke-fs.d.ts.map +1 -0
- package/dist/goke-fs.js +4 -0
- package/dist/goke.d.ts +22 -1
- package/dist/goke.d.ts.map +1 -1
- package/dist/goke.js +25 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/just-bash.d.ts +3 -1
- package/dist/just-bash.d.ts.map +1 -1
- package/dist/just-bash.js +163 -1
- package/dist/picocolors.d.ts +55 -0
- package/dist/picocolors.d.ts.map +1 -0
- package/dist/picocolors.js +78 -0
- package/dist/runtime-browser.d.ts +5 -1
- package/dist/runtime-browser.d.ts.map +1 -1
- package/dist/runtime-browser.js +28 -1
- package/dist/runtime-node.d.ts +3 -1
- package/dist/runtime-node.d.ts.map +1 -1
- package/dist/runtime-node.js +3 -1
- package/package.json +3 -4
- package/src/__test__/index.test.ts +93 -0
- package/src/__test__/just-bash.test.ts +171 -3
- package/src/__test__/types.test-d.ts +9 -3
- package/src/goke-fs.ts +26 -0
- package/src/goke.ts +40 -5
- package/src/index.ts +1 -1
- package/src/just-bash.ts +187 -2
- package/src/picocolors.ts +140 -0
- package/src/runtime-browser.ts +35 -1
- package/src/runtime-node.ts +4 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendored from picocolors by Alexey Raspopov (MIT license).
|
|
3
|
+
* Source: https://github.com/alexeyraspopov/picocolors/blob/main/picocolors.js
|
|
4
|
+
*/
|
|
5
|
+
type Formatter = (input: unknown) => string;
|
|
6
|
+
interface PicoColors {
|
|
7
|
+
isColorSupported: boolean;
|
|
8
|
+
reset: Formatter;
|
|
9
|
+
bold: Formatter;
|
|
10
|
+
dim: Formatter;
|
|
11
|
+
italic: Formatter;
|
|
12
|
+
underline: Formatter;
|
|
13
|
+
inverse: Formatter;
|
|
14
|
+
hidden: Formatter;
|
|
15
|
+
strikethrough: Formatter;
|
|
16
|
+
black: Formatter;
|
|
17
|
+
red: Formatter;
|
|
18
|
+
green: Formatter;
|
|
19
|
+
yellow: Formatter;
|
|
20
|
+
blue: Formatter;
|
|
21
|
+
magenta: Formatter;
|
|
22
|
+
cyan: Formatter;
|
|
23
|
+
white: Formatter;
|
|
24
|
+
gray: Formatter;
|
|
25
|
+
bgBlack: Formatter;
|
|
26
|
+
bgRed: Formatter;
|
|
27
|
+
bgGreen: Formatter;
|
|
28
|
+
bgYellow: Formatter;
|
|
29
|
+
bgBlue: Formatter;
|
|
30
|
+
bgMagenta: Formatter;
|
|
31
|
+
bgCyan: Formatter;
|
|
32
|
+
bgWhite: Formatter;
|
|
33
|
+
blackBright: Formatter;
|
|
34
|
+
redBright: Formatter;
|
|
35
|
+
greenBright: Formatter;
|
|
36
|
+
yellowBright: Formatter;
|
|
37
|
+
blueBright: Formatter;
|
|
38
|
+
magentaBright: Formatter;
|
|
39
|
+
cyanBright: Formatter;
|
|
40
|
+
whiteBright: Formatter;
|
|
41
|
+
bgBlackBright: Formatter;
|
|
42
|
+
bgRedBright: Formatter;
|
|
43
|
+
bgGreenBright: Formatter;
|
|
44
|
+
bgYellowBright: Formatter;
|
|
45
|
+
bgBlueBright: Formatter;
|
|
46
|
+
bgMagentaBright: Formatter;
|
|
47
|
+
bgCyanBright: Formatter;
|
|
48
|
+
bgWhiteBright: Formatter;
|
|
49
|
+
}
|
|
50
|
+
declare const createColors: (enabled?: boolean) => PicoColors;
|
|
51
|
+
declare const pc: PicoColors;
|
|
52
|
+
export { createColors };
|
|
53
|
+
export type { PicoColors };
|
|
54
|
+
export default pc;
|
|
55
|
+
//# sourceMappingURL=picocolors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picocolors.d.ts","sourceRoot":"","sources":["../src/picocolors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;AAE3C,UAAU,UAAU;IAClB,gBAAgB,EAAE,OAAO,CAAA;IACzB,KAAK,EAAE,SAAS,CAAA;IAChB,IAAI,EAAE,SAAS,CAAA;IACf,GAAG,EAAE,SAAS,CAAA;IACd,MAAM,EAAE,SAAS,CAAA;IACjB,SAAS,EAAE,SAAS,CAAA;IACpB,OAAO,EAAE,SAAS,CAAA;IAClB,MAAM,EAAE,SAAS,CAAA;IACjB,aAAa,EAAE,SAAS,CAAA;IACxB,KAAK,EAAE,SAAS,CAAA;IAChB,GAAG,EAAE,SAAS,CAAA;IACd,KAAK,EAAE,SAAS,CAAA;IAChB,MAAM,EAAE,SAAS,CAAA;IACjB,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,EAAE,SAAS,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;IACf,KAAK,EAAE,SAAS,CAAA;IAChB,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,EAAE,SAAS,CAAA;IAClB,KAAK,EAAE,SAAS,CAAA;IAChB,OAAO,EAAE,SAAS,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;IACnB,MAAM,EAAE,SAAS,CAAA;IACjB,SAAS,EAAE,SAAS,CAAA;IACpB,MAAM,EAAE,SAAS,CAAA;IACjB,OAAO,EAAE,SAAS,CAAA;IAClB,WAAW,EAAE,SAAS,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,SAAS,CAAA;IACtB,YAAY,EAAE,SAAS,CAAA;IACvB,UAAU,EAAE,SAAS,CAAA;IACrB,aAAa,EAAE,SAAS,CAAA;IACxB,UAAU,EAAE,SAAS,CAAA;IACrB,WAAW,EAAE,SAAS,CAAA;IACtB,aAAa,EAAE,SAAS,CAAA;IACxB,WAAW,EAAE,SAAS,CAAA;IACtB,aAAa,EAAE,SAAS,CAAA;IACxB,cAAc,EAAE,SAAS,CAAA;IACzB,YAAY,EAAE,SAAS,CAAA;IACvB,eAAe,EAAE,SAAS,CAAA;IAC1B,YAAY,EAAE,SAAS,CAAA;IACvB,aAAa,EAAE,SAAS,CAAA;CACzB;AAkCD,QAAA,MAAM,YAAY,GAAI,iBAA0B,KAAG,UA+ClD,CAAA;AAED,QAAA,MAAM,EAAE,YAAiB,CAAA;AAEzB,OAAO,EAAE,YAAY,EAAE,CAAA;AACvB,YAAY,EAAE,UAAU,EAAE,CAAA;AAC1B,eAAe,EAAE,CAAA"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendored from picocolors by Alexey Raspopov (MIT license).
|
|
3
|
+
* Source: https://github.com/alexeyraspopov/picocolors/blob/main/picocolors.js
|
|
4
|
+
*/
|
|
5
|
+
import { process } from '#runtime';
|
|
6
|
+
const argv = process.argv || [];
|
|
7
|
+
const env = process.env || {};
|
|
8
|
+
const isColorSupported = !(!!env.NO_COLOR || argv.includes('--no-color'))
|
|
9
|
+
&& (!!env.FORCE_COLOR
|
|
10
|
+
|| argv.includes('--color')
|
|
11
|
+
|| process.platform === 'win32'
|
|
12
|
+
|| (process.stdout.isTTY && env.TERM !== 'dumb')
|
|
13
|
+
|| !!env.CI);
|
|
14
|
+
const replaceClose = (string, close, replace, index) => {
|
|
15
|
+
let result = '';
|
|
16
|
+
let cursor = 0;
|
|
17
|
+
do {
|
|
18
|
+
result += string.substring(cursor, index) + replace;
|
|
19
|
+
cursor = index + close.length;
|
|
20
|
+
index = string.indexOf(close, cursor);
|
|
21
|
+
} while (~index);
|
|
22
|
+
return result + string.substring(cursor);
|
|
23
|
+
};
|
|
24
|
+
const formatter = (open, close, replace = open) => (input) => {
|
|
25
|
+
const string = String(input);
|
|
26
|
+
const index = string.indexOf(close, open.length);
|
|
27
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
28
|
+
};
|
|
29
|
+
const createColors = (enabled = isColorSupported) => {
|
|
30
|
+
const f = enabled ? formatter : () => String;
|
|
31
|
+
return {
|
|
32
|
+
isColorSupported: enabled,
|
|
33
|
+
reset: f('\x1b[0m', '\x1b[0m'),
|
|
34
|
+
bold: f('\x1b[1m', '\x1b[22m', '\x1b[22m\x1b[1m'),
|
|
35
|
+
dim: f('\x1b[2m', '\x1b[22m', '\x1b[22m\x1b[2m'),
|
|
36
|
+
italic: f('\x1b[3m', '\x1b[23m'),
|
|
37
|
+
underline: f('\x1b[4m', '\x1b[24m'),
|
|
38
|
+
inverse: f('\x1b[7m', '\x1b[27m'),
|
|
39
|
+
hidden: f('\x1b[8m', '\x1b[28m'),
|
|
40
|
+
strikethrough: f('\x1b[9m', '\x1b[29m'),
|
|
41
|
+
black: f('\x1b[30m', '\x1b[39m'),
|
|
42
|
+
red: f('\x1b[31m', '\x1b[39m'),
|
|
43
|
+
green: f('\x1b[32m', '\x1b[39m'),
|
|
44
|
+
yellow: f('\x1b[33m', '\x1b[39m'),
|
|
45
|
+
blue: f('\x1b[34m', '\x1b[39m'),
|
|
46
|
+
magenta: f('\x1b[35m', '\x1b[39m'),
|
|
47
|
+
cyan: f('\x1b[36m', '\x1b[39m'),
|
|
48
|
+
white: f('\x1b[37m', '\x1b[39m'),
|
|
49
|
+
gray: f('\x1b[90m', '\x1b[39m'),
|
|
50
|
+
bgBlack: f('\x1b[40m', '\x1b[49m'),
|
|
51
|
+
bgRed: f('\x1b[41m', '\x1b[49m'),
|
|
52
|
+
bgGreen: f('\x1b[42m', '\x1b[49m'),
|
|
53
|
+
bgYellow: f('\x1b[43m', '\x1b[49m'),
|
|
54
|
+
bgBlue: f('\x1b[44m', '\x1b[49m'),
|
|
55
|
+
bgMagenta: f('\x1b[45m', '\x1b[49m'),
|
|
56
|
+
bgCyan: f('\x1b[46m', '\x1b[49m'),
|
|
57
|
+
bgWhite: f('\x1b[47m', '\x1b[49m'),
|
|
58
|
+
blackBright: f('\x1b[90m', '\x1b[39m'),
|
|
59
|
+
redBright: f('\x1b[91m', '\x1b[39m'),
|
|
60
|
+
greenBright: f('\x1b[92m', '\x1b[39m'),
|
|
61
|
+
yellowBright: f('\x1b[93m', '\x1b[39m'),
|
|
62
|
+
blueBright: f('\x1b[94m', '\x1b[39m'),
|
|
63
|
+
magentaBright: f('\x1b[95m', '\x1b[39m'),
|
|
64
|
+
cyanBright: f('\x1b[96m', '\x1b[39m'),
|
|
65
|
+
whiteBright: f('\x1b[97m', '\x1b[39m'),
|
|
66
|
+
bgBlackBright: f('\x1b[100m', '\x1b[49m'),
|
|
67
|
+
bgRedBright: f('\x1b[101m', '\x1b[49m'),
|
|
68
|
+
bgGreenBright: f('\x1b[102m', '\x1b[49m'),
|
|
69
|
+
bgYellowBright: f('\x1b[103m', '\x1b[49m'),
|
|
70
|
+
bgBlueBright: f('\x1b[104m', '\x1b[49m'),
|
|
71
|
+
bgMagentaBright: f('\x1b[105m', '\x1b[49m'),
|
|
72
|
+
bgCyanBright: f('\x1b[106m', '\x1b[49m'),
|
|
73
|
+
bgWhiteBright: f('\x1b[107m', '\x1b[49m'),
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
const pc = createColors();
|
|
77
|
+
export { createColors };
|
|
78
|
+
export default pc;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Browser-safe runtime stubs for goke core.
|
|
3
3
|
*/
|
|
4
|
+
import type { GokeFs } from './goke-fs.js';
|
|
4
5
|
type Listener = (...args: any[]) => void;
|
|
5
6
|
declare class EventEmitter {
|
|
6
7
|
#private;
|
|
@@ -12,6 +13,8 @@ declare class EventEmitter {
|
|
|
12
13
|
declare const process: {
|
|
13
14
|
argv: string[];
|
|
14
15
|
arch: string;
|
|
16
|
+
cwd(): string;
|
|
17
|
+
env: Record<string, string | undefined>;
|
|
15
18
|
platform: string;
|
|
16
19
|
version: string;
|
|
17
20
|
stdout: {
|
|
@@ -26,6 +29,7 @@ declare const process: {
|
|
|
26
29
|
};
|
|
27
30
|
exit(code: number): never;
|
|
28
31
|
};
|
|
32
|
+
declare const fs: GokeFs;
|
|
29
33
|
declare function openInBrowser(_url: string): void;
|
|
30
|
-
export { EventEmitter, openInBrowser, process };
|
|
34
|
+
export { EventEmitter, fs, openInBrowser, process };
|
|
31
35
|
//# sourceMappingURL=runtime-browser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-browser.d.ts","sourceRoot":"","sources":["../src/runtime-browser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,KAAK,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;AAExC,cAAM,YAAY;;IAGhB,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,QAAQ;IAOjD,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAS/C,UAAU;IAIV,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAGrC;AAQD,QAAA,MAAM,OAAO;UACC,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"runtime-browser.d.ts","sourceRoot":"","sources":["../src/runtime-browser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,KAAK,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;AAExC,cAAM,YAAY;;IAGhB,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,QAAQ;IAOjD,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAS/C,UAAU;IAIV,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAGrC;AAQD,QAAA,MAAM,OAAO;UACC,MAAM,EAAE;;;SAKQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;;;;;;qBATjD,MAAM;;;;;qBAAN,MAAM;;eAcR,MAAM,GAAG,KAAK;CAK1B,CAAA;AAcD,QAAA,MAAM,EAAE,EAAE,MAcT,CAAA;AAED,iBAAS,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,CAAA"}
|
package/dist/runtime-browser.js
CHANGED
|
@@ -33,6 +33,10 @@ const createOutputStream = () => ({
|
|
|
33
33
|
const process = {
|
|
34
34
|
argv: [],
|
|
35
35
|
arch: 'browser',
|
|
36
|
+
cwd() {
|
|
37
|
+
return '/';
|
|
38
|
+
},
|
|
39
|
+
env: Object.create(null),
|
|
36
40
|
platform: 'browser',
|
|
37
41
|
version: 'browser',
|
|
38
42
|
stdout: createOutputStream(),
|
|
@@ -41,7 +45,30 @@ const process = {
|
|
|
41
45
|
throw new Error(`process.exit(${code}) is not available in the browser runtime. Pass a custom exit handler to goke(...).`);
|
|
42
46
|
},
|
|
43
47
|
};
|
|
48
|
+
function createBrowserFsError(methodName) {
|
|
49
|
+
return new Error(`fs.${methodName}() is not available in the browser runtime. Pass a custom fs implementation to goke(...).`);
|
|
50
|
+
}
|
|
51
|
+
function createUnsupportedFsMethod(methodName) {
|
|
52
|
+
return (async () => {
|
|
53
|
+
throw createBrowserFsError(methodName);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const fs = {
|
|
57
|
+
appendFile: createUnsupportedFsMethod('appendFile'),
|
|
58
|
+
chmod: createUnsupportedFsMethod('chmod'),
|
|
59
|
+
copyFile: createUnsupportedFsMethod('copyFile'),
|
|
60
|
+
link: createUnsupportedFsMethod('link'),
|
|
61
|
+
mkdir: createUnsupportedFsMethod('mkdir'),
|
|
62
|
+
readFile: createUnsupportedFsMethod('readFile'),
|
|
63
|
+
readlink: createUnsupportedFsMethod('readlink'),
|
|
64
|
+
realpath: createUnsupportedFsMethod('realpath'),
|
|
65
|
+
rename: createUnsupportedFsMethod('rename'),
|
|
66
|
+
rm: createUnsupportedFsMethod('rm'),
|
|
67
|
+
symlink: createUnsupportedFsMethod('symlink'),
|
|
68
|
+
utimes: createUnsupportedFsMethod('utimes'),
|
|
69
|
+
writeFile: createUnsupportedFsMethod('writeFile'),
|
|
70
|
+
};
|
|
44
71
|
function openInBrowser(_url) {
|
|
45
72
|
// Browser builds should decide how to surface URLs themselves.
|
|
46
73
|
}
|
|
47
|
-
export { EventEmitter, openInBrowser, process };
|
|
74
|
+
export { EventEmitter, fs, openInBrowser, process };
|
package/dist/runtime-node.d.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Node.js runtime bindings for goke core.
|
|
3
3
|
*/
|
|
4
4
|
import { EventEmitter } from 'events';
|
|
5
|
+
import type { GokeFs } from './goke-fs.js';
|
|
5
6
|
declare const process: NodeJS.Process;
|
|
7
|
+
declare const fs: GokeFs;
|
|
6
8
|
declare function openInBrowser(url: string): void;
|
|
7
|
-
export { EventEmitter, openInBrowser, process };
|
|
9
|
+
export { EventEmitter, fs, openInBrowser, process };
|
|
8
10
|
//# sourceMappingURL=runtime-node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-node.d.ts","sourceRoot":"","sources":["../src/runtime-node.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAErC,QAAA,MAAM,OAAO,gBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"runtime-node.d.ts","sourceRoot":"","sources":["../src/runtime-node.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAErC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,QAAA,MAAM,OAAO,gBAAqB,CAAA;AAClC,QAAA,MAAM,EAAE,EAAE,MAAe,CAAA;AAEzB,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAiBxC;AAED,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,CAAA"}
|
package/dist/runtime-node.js
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
5
|
import { EventEmitter } from 'events';
|
|
6
|
+
import * as nodeFs from 'node:fs/promises';
|
|
6
7
|
const process = globalThis.process;
|
|
8
|
+
const fs = nodeFs;
|
|
7
9
|
function openInBrowser(url) {
|
|
8
10
|
if (!process.stdout.isTTY) {
|
|
9
11
|
process.stderr.write(url + '\n');
|
|
@@ -24,4 +26,4 @@ function openInBrowser(url) {
|
|
|
24
26
|
process.stderr.write(url + '\n');
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
|
-
export { EventEmitter, openInBrowser, process };
|
|
29
|
+
export { EventEmitter, fs, openInBrowser, process };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goke",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Simple yet powerful framework for building command-line apps. Inspired by cac.",
|
|
6
6
|
"repository": {
|
|
@@ -48,13 +48,12 @@
|
|
|
48
48
|
"dist",
|
|
49
49
|
"src"
|
|
50
50
|
],
|
|
51
|
-
"dependencies": {
|
|
52
|
-
"picocolors": "^1.1.1"
|
|
53
|
-
},
|
|
51
|
+
"dependencies": {},
|
|
54
52
|
"author": "remorses, egoist <0x142857@gmail.com>",
|
|
55
53
|
"license": "MIT",
|
|
56
54
|
"devDependencies": {
|
|
57
55
|
"@types/node": "^25.2.2",
|
|
56
|
+
"just-bash": "^2.14.0",
|
|
58
57
|
"typescript": "^5.9.3",
|
|
59
58
|
"vitest": "^3.1.0",
|
|
60
59
|
"zod": "^4.3.6"
|
|
@@ -3,6 +3,9 @@ import goke, { createConsole } from '../index.js'
|
|
|
3
3
|
import type { GokeOutputStream, GokeOptions } from '../index.js'
|
|
4
4
|
import { coerceBySchema } from '../coerce.js'
|
|
5
5
|
import { z } from 'zod'
|
|
6
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises'
|
|
7
|
+
import { tmpdir } from 'node:os'
|
|
8
|
+
import { join } from 'node:path'
|
|
6
9
|
|
|
7
10
|
const ANSI_RE = /\x1B\[[0-9;]*m/g
|
|
8
11
|
|
|
@@ -157,6 +160,96 @@ describe('error formatting', () => {
|
|
|
157
160
|
})
|
|
158
161
|
})
|
|
159
162
|
|
|
163
|
+
describe('injected fs', () => {
|
|
164
|
+
test('command actions can use the default node fs for cli storage', async () => {
|
|
165
|
+
const stdout = createTestOutputStream()
|
|
166
|
+
const cli = gokeTestable('mycli', { stdout })
|
|
167
|
+
const originalCwd = process.cwd()
|
|
168
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'goke-fs-'))
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
process.chdir(tempDir)
|
|
172
|
+
|
|
173
|
+
cli
|
|
174
|
+
.command('login', 'Persist login state')
|
|
175
|
+
.option('--token <token>', z.string().describe('Token'))
|
|
176
|
+
.action(async (options, { fs, console }) => {
|
|
177
|
+
await fs.mkdir('.mycli', { recursive: true })
|
|
178
|
+
await fs.writeFile('.mycli/auth.json', JSON.stringify({ token: options.token }), 'utf8')
|
|
179
|
+
console.log('saved credentials')
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
cli.parse(['node', 'bin', 'login', '--token', 'abc123'], { run: false })
|
|
183
|
+
await cli.runMatchedCommand()
|
|
184
|
+
|
|
185
|
+
expect(stdout.text).toBe('saved credentials\n')
|
|
186
|
+
expect(await readFile(join(tempDir, '.mycli/auth.json'), 'utf8')).toBe('{"token":"abc123"}')
|
|
187
|
+
} finally {
|
|
188
|
+
process.chdir(originalCwd)
|
|
189
|
+
await rm(tempDir, { recursive: true, force: true })
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
describe('injected process context', () => {
|
|
195
|
+
test('command actions receive host cwd, env, and stdin defaults', async () => {
|
|
196
|
+
const stdout = createTestOutputStream()
|
|
197
|
+
const cli = gokeTestable('mycli', { stdout })
|
|
198
|
+
const originalCwd = process.cwd()
|
|
199
|
+
const originalEnv = process.env.GOKE_TEST_TOKEN
|
|
200
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'goke-process-'))
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
process.chdir(tempDir)
|
|
204
|
+
process.env.GOKE_TEST_TOKEN = 'abc123'
|
|
205
|
+
|
|
206
|
+
cli
|
|
207
|
+
.command('context', 'Inspect process context')
|
|
208
|
+
.action((options, { console, process }) => {
|
|
209
|
+
console.log(JSON.stringify({
|
|
210
|
+
cwd: process.cwd,
|
|
211
|
+
stdin: process.stdin,
|
|
212
|
+
token: process.env.GOKE_TEST_TOKEN,
|
|
213
|
+
}))
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
cli.parse(['node', 'bin', 'context'], { run: false })
|
|
217
|
+
await cli.runMatchedCommand()
|
|
218
|
+
|
|
219
|
+
expect(stdout.text).toBe(
|
|
220
|
+
`${JSON.stringify({ cwd: process.cwd(), stdin: '', token: 'abc123' })}\n`,
|
|
221
|
+
)
|
|
222
|
+
} finally {
|
|
223
|
+
process.chdir(originalCwd)
|
|
224
|
+
if (originalEnv === undefined) {
|
|
225
|
+
delete process.env.GOKE_TEST_TOKEN
|
|
226
|
+
} else {
|
|
227
|
+
process.env.GOKE_TEST_TOKEN = originalEnv
|
|
228
|
+
}
|
|
229
|
+
await rm(tempDir, { recursive: true, force: true })
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
test('custom injected env stays mutable inside command actions', async () => {
|
|
234
|
+
const stdout = createTestOutputStream()
|
|
235
|
+
const env: Record<string, string | undefined> = { TOKEN: 'before' }
|
|
236
|
+
const cli = gokeTestable('mycli', { env, stdout })
|
|
237
|
+
|
|
238
|
+
cli
|
|
239
|
+
.command('context', 'Mutate process env')
|
|
240
|
+
.action((options, { console, process }) => {
|
|
241
|
+
process.env.TOKEN = 'after'
|
|
242
|
+
console.log(process.env.TOKEN)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
cli.parse(['node', 'bin', 'context'], { run: false })
|
|
246
|
+
await cli.runMatchedCommand()
|
|
247
|
+
|
|
248
|
+
expect(stdout.text).toBe('after\n')
|
|
249
|
+
expect(env.TOKEN).toBe('after')
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
|
|
160
253
|
test('double dashes', () => {
|
|
161
254
|
const cli = goke()
|
|
162
255
|
|
|
@@ -72,7 +72,7 @@ describe('clone', () => {
|
|
|
72
72
|
|
|
73
73
|
cli.command('build', 'Build').action(() => {})
|
|
74
74
|
|
|
75
|
-
const cloned =
|
|
75
|
+
const cloned = cli.clone({ exit: () => {} })
|
|
76
76
|
|
|
77
77
|
cloned.parse(['node', 'bin', 'build'], { run: false })
|
|
78
78
|
|
|
@@ -93,7 +93,7 @@ describe('createJustBashCommand', () => {
|
|
|
93
93
|
console.log(`hello ${options.name}`)
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
-
const customCommand = await
|
|
96
|
+
const customCommand = await cli.createJustBashCommand()
|
|
97
97
|
const result = await customCommand.execute(['child', 'commandwithspaces', '--name', 'Tommy'])
|
|
98
98
|
|
|
99
99
|
expect(result).toEqual({
|
|
@@ -103,6 +103,174 @@ describe('createJustBashCommand', () => {
|
|
|
103
103
|
})
|
|
104
104
|
})
|
|
105
105
|
|
|
106
|
+
test('works through real just-bash exec with a goke custom command', async () => {
|
|
107
|
+
const { Bash } = await import('just-bash')
|
|
108
|
+
const cli = gokeTestable('parent')
|
|
109
|
+
|
|
110
|
+
cli
|
|
111
|
+
.command('child commandwithspaces', 'Run nested command')
|
|
112
|
+
.option('--name <name>', z.string().describe('Name'))
|
|
113
|
+
.action((options, { console }) => {
|
|
114
|
+
console.log(`hello ${options.name}`)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const bash = new Bash({
|
|
118
|
+
customCommands: [await cli.createJustBashCommand()],
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const result = await bash.exec('parent child commandwithspaces --name Tommy')
|
|
122
|
+
|
|
123
|
+
expect(result.stdout).toBe('hello Tommy\n')
|
|
124
|
+
expect(result.stderr).toBe('')
|
|
125
|
+
expect(result.exitCode).toBe(0)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('maps injected fs to the just-bash virtual filesystem', async () => {
|
|
129
|
+
const { Bash } = await import('just-bash')
|
|
130
|
+
const cli = gokeTestable('parent')
|
|
131
|
+
|
|
132
|
+
cli
|
|
133
|
+
.command('login', 'Persist login state')
|
|
134
|
+
.option('--token <token>', z.string().describe('Token'))
|
|
135
|
+
.action(async (options, { fs, console }) => {
|
|
136
|
+
await fs.mkdir('.mycli', { recursive: true })
|
|
137
|
+
await fs.writeFile('.mycli/auth.json', JSON.stringify({ token: options.token }), 'utf8')
|
|
138
|
+
console.log('saved credentials')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const bash = new Bash({
|
|
142
|
+
customCommands: [await cli.createJustBashCommand()],
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const loginResult = await bash.exec('mkdir project && cd project && parent login --token Tommy')
|
|
146
|
+
const catResult = await bash.exec('cd project && cat .mycli/auth.json')
|
|
147
|
+
|
|
148
|
+
expect(loginResult.stdout).toBe('saved credentials\n')
|
|
149
|
+
expect(loginResult.stderr).toBe('')
|
|
150
|
+
expect(loginResult.exitCode).toBe(0)
|
|
151
|
+
expect(catResult.stdout).toBe('{"token":"Tommy"}')
|
|
152
|
+
expect(catResult.stderr).toBe('')
|
|
153
|
+
expect(catResult.exitCode).toBe(0)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('real just-bash exec passes the configured in-memory fs to the goke command', async () => {
|
|
157
|
+
const { Bash, InMemoryFs } = await import('just-bash')
|
|
158
|
+
const cli = gokeTestable('parent')
|
|
159
|
+
|
|
160
|
+
cli
|
|
161
|
+
.command('login', 'Persist login state')
|
|
162
|
+
.option('--token <token>', z.string().describe('Token'))
|
|
163
|
+
.action(async (options, { fs, console }) => {
|
|
164
|
+
await fs.mkdir('.mycli', { recursive: true })
|
|
165
|
+
await fs.writeFile('.mycli/auth.json', JSON.stringify({ token: options.token }), 'utf8')
|
|
166
|
+
console.log('saved credentials')
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const virtualFs = new InMemoryFs()
|
|
170
|
+
await virtualFs.mkdir('/project', { recursive: true })
|
|
171
|
+
|
|
172
|
+
const bash = new Bash({
|
|
173
|
+
fs: virtualFs,
|
|
174
|
+
cwd: '/project',
|
|
175
|
+
customCommands: [await cli.createJustBashCommand()],
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const result = await bash.exec('parent login --token Tommy')
|
|
179
|
+
|
|
180
|
+
expect(result.stdout).toBe('saved credentials\n')
|
|
181
|
+
expect(result.stderr).toBe('')
|
|
182
|
+
expect(result.exitCode).toBe(0)
|
|
183
|
+
expect(await virtualFs.readFile('/project/.mycli/auth.json', 'utf8')).toBe('{"token":"Tommy"}')
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('real just-bash exec passes sandbox cwd, stdin, and env through process context', async () => {
|
|
187
|
+
const { Bash, InMemoryFs } = await import('just-bash')
|
|
188
|
+
const cli = gokeTestable('parent')
|
|
189
|
+
|
|
190
|
+
cli
|
|
191
|
+
.command('context', 'Inspect process context')
|
|
192
|
+
.action((options, { console, process }) => {
|
|
193
|
+
console.log(JSON.stringify({
|
|
194
|
+
cwd: process.cwd,
|
|
195
|
+
stdin: process.stdin,
|
|
196
|
+
token: process.env.TOKEN,
|
|
197
|
+
}))
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const virtualFs = new InMemoryFs()
|
|
201
|
+
await virtualFs.mkdir('/project', { recursive: true })
|
|
202
|
+
|
|
203
|
+
const bash = new Bash({
|
|
204
|
+
fs: virtualFs,
|
|
205
|
+
cwd: '/project',
|
|
206
|
+
env: { TOKEN: 'Tommy' },
|
|
207
|
+
customCommands: [await cli.createJustBashCommand()],
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const result = await bash.exec('parent context', { stdin: 'hello from stdin' })
|
|
211
|
+
|
|
212
|
+
expect(result.stdout).toBe(
|
|
213
|
+
`${JSON.stringify({ cwd: '/project', stdin: 'hello from stdin', token: 'Tommy' })}\n`,
|
|
214
|
+
)
|
|
215
|
+
expect(result.stderr).toBe('')
|
|
216
|
+
expect(result.exitCode).toBe(0)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('explicit just-bash context exposes a mutable env object backed by the sandbox env', async () => {
|
|
220
|
+
const { InMemoryFs } = await import('just-bash')
|
|
221
|
+
const cli = gokeTestable('parent')
|
|
222
|
+
|
|
223
|
+
cli
|
|
224
|
+
.command('mutate-env', 'Mutate sandbox env')
|
|
225
|
+
.action((options, { console, process }) => {
|
|
226
|
+
process.env.TOKEN = 'updated'
|
|
227
|
+
console.log(process.env.TOKEN)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
const virtualFs = new InMemoryFs()
|
|
231
|
+
await virtualFs.mkdir('/project', { recursive: true })
|
|
232
|
+
const env = new Map<string, string>([['TOKEN', 'before']])
|
|
233
|
+
const customCommand = await cli.createJustBashCommand()
|
|
234
|
+
|
|
235
|
+
const result = await customCommand.execute(
|
|
236
|
+
['mutate-env'],
|
|
237
|
+
{ fs: virtualFs, cwd: '/project', env, stdin: '' },
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
expect(result.stdout).toBe('updated\n')
|
|
241
|
+
expect(result.stderr).toBe('')
|
|
242
|
+
expect(result.exitCode).toBe(0)
|
|
243
|
+
expect(env.get('TOKEN')).toBe('updated')
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test('accepts an explicit just-bash fs context when executing the custom command', async () => {
|
|
247
|
+
const { InMemoryFs } = await import('just-bash')
|
|
248
|
+
const cli = gokeTestable('parent')
|
|
249
|
+
|
|
250
|
+
cli
|
|
251
|
+
.command('login', 'Persist login state')
|
|
252
|
+
.option('--token <token>', z.string().describe('Token'))
|
|
253
|
+
.action(async (options, { fs, console }) => {
|
|
254
|
+
await fs.mkdir('.mycli', { recursive: true })
|
|
255
|
+
await fs.writeFile('.mycli/auth.json', JSON.stringify({ token: options.token }), 'utf8')
|
|
256
|
+
console.log('saved credentials')
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const customCommand = await cli.createJustBashCommand()
|
|
260
|
+
const virtualFs = new InMemoryFs()
|
|
261
|
+
await virtualFs.mkdir('/project', { recursive: true })
|
|
262
|
+
|
|
263
|
+
const result = await customCommand.execute(
|
|
264
|
+
['login', '--token', 'Tommy'],
|
|
265
|
+
{ fs: virtualFs, cwd: '/project', env: new Map(), stdin: '' },
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
expect(result.stdout).toBe('saved credentials\n')
|
|
269
|
+
expect(result.stderr).toBe('')
|
|
270
|
+
expect(result.exitCode).toBe(0)
|
|
271
|
+
expect(await virtualFs.readFile('/project/.mycli/auth.json', 'utf8')).toBe('{"token":"Tommy"}')
|
|
272
|
+
})
|
|
273
|
+
|
|
106
274
|
test('maps injected process.exit to a command exit code', async () => {
|
|
107
275
|
const cli = gokeTestable('parent')
|
|
108
276
|
|
|
@@ -112,7 +280,7 @@ describe('createJustBashCommand', () => {
|
|
|
112
280
|
process.exit(7)
|
|
113
281
|
})
|
|
114
282
|
|
|
115
|
-
const customCommand = await
|
|
283
|
+
const customCommand = await cli.createJustBashCommand()
|
|
116
284
|
const result = await customCommand.execute(['fail'])
|
|
117
285
|
|
|
118
286
|
expect(result).toEqual({
|
|
@@ -119,10 +119,14 @@ describe('type-level: middleware use() callback inference', () => {
|
|
|
119
119
|
goke('test')
|
|
120
120
|
.option('--port <port>', schema1)
|
|
121
121
|
.option('--host <host>', schema2)
|
|
122
|
-
.use((options, { console, process }) => {
|
|
122
|
+
.use((options, { console, fs, process }) => {
|
|
123
123
|
expectTypeOf(options.port).toEqualTypeOf<number>()
|
|
124
124
|
expectTypeOf(options.host).toEqualTypeOf<string>()
|
|
125
|
+
expectTypeOf(fs.mkdir).toBeFunction()
|
|
125
126
|
expectTypeOf(process.argv).toEqualTypeOf<string[]>()
|
|
127
|
+
expectTypeOf(process.cwd).toEqualTypeOf<string>()
|
|
128
|
+
expectTypeOf(process.env).toEqualTypeOf<Record<string, string | undefined>>()
|
|
129
|
+
expectTypeOf(process.stdin).toEqualTypeOf<string>()
|
|
126
130
|
expectTypeOf(process.stdout.write).toEqualTypeOf<(data: string) => void>()
|
|
127
131
|
expectTypeOf(console.log).toBeFunction()
|
|
128
132
|
})
|
|
@@ -134,8 +138,9 @@ describe('type-level: middleware use() callback inference', () => {
|
|
|
134
138
|
|
|
135
139
|
goke('test')
|
|
136
140
|
.option('--verbose', schema1)
|
|
137
|
-
.use((options, { process }) => {
|
|
141
|
+
.use((options, { fs, process }) => {
|
|
138
142
|
expectTypeOf(options.verbose).toEqualTypeOf<boolean | undefined>()
|
|
143
|
+
expectTypeOf(fs.writeFile).toBeFunction()
|
|
139
144
|
expectTypeOf(process.exit).toEqualTypeOf<(code: number) => void>()
|
|
140
145
|
// @ts-expect-error port is not declared yet
|
|
141
146
|
options.port
|
|
@@ -154,7 +159,8 @@ describe('type-level: middleware use() callback inference', () => {
|
|
|
154
159
|
|
|
155
160
|
goke('test')
|
|
156
161
|
.option('--port <port>', schema)
|
|
157
|
-
.use((options, { process }) => {
|
|
162
|
+
.use((options, { fs, process }) => {
|
|
163
|
+
expectTypeOf(fs.readFile).toBeFunction()
|
|
158
164
|
expectTypeOf(process.stderr.write).toEqualTypeOf<(data: string) => void>()
|
|
159
165
|
// @ts-expect-error nonExistent was never defined
|
|
160
166
|
options.nonExistent
|
package/src/goke-fs.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-like filesystem types used by injected goke execution contexts.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { MakeDirectoryOptions, Mode, PathLike, RmOptions, TimeLike } from 'node:fs'
|
|
6
|
+
|
|
7
|
+
type GokeFsFileContent = string | NodeJS.ArrayBufferView
|
|
8
|
+
type GokeFsEncodingOption = BufferEncoding | { encoding?: BufferEncoding | null }
|
|
9
|
+
|
|
10
|
+
interface GokeFs {
|
|
11
|
+
appendFile(path: PathLike, data: GokeFsFileContent, options?: GokeFsEncodingOption): Promise<void>
|
|
12
|
+
chmod(path: PathLike, mode: Mode): Promise<void>
|
|
13
|
+
copyFile(src: PathLike, dest: PathLike): Promise<void>
|
|
14
|
+
link(existingPath: PathLike, newPath: PathLike): Promise<void>
|
|
15
|
+
mkdir(path: PathLike, options?: MakeDirectoryOptions): Promise<string | undefined>
|
|
16
|
+
readFile(path: PathLike, options?: GokeFsEncodingOption): Promise<string | Uint8Array>
|
|
17
|
+
readlink(path: PathLike): Promise<string>
|
|
18
|
+
realpath(path: PathLike): Promise<string>
|
|
19
|
+
rename(oldPath: PathLike, newPath: PathLike): Promise<void>
|
|
20
|
+
rm(path: PathLike, options?: RmOptions): Promise<void>
|
|
21
|
+
symlink(target: PathLike, path: PathLike): Promise<void>
|
|
22
|
+
utimes(path: PathLike, atime: TimeLike, mtime: TimeLike): Promise<void>
|
|
23
|
+
writeFile(path: PathLike, data: GokeFsFileContent, options?: GokeFsEncodingOption): Promise<void>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type { GokeFs, GokeFsEncodingOption, GokeFsFileContent }
|