goke 6.3.2 → 6.4.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 +101 -31
- package/dist/__test__/just-bash.test.d.ts +5 -0
- package/dist/__test__/just-bash.test.d.ts.map +1 -0
- package/dist/__test__/just-bash.test.js +97 -0
- package/dist/__test__/types.test-d.js +10 -4
- package/dist/goke.d.ts +31 -13
- package/dist/goke.d.ts.map +1 -1
- package/dist/goke.js +107 -40
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/just-bash.d.ts +23 -0
- package/dist/just-bash.d.ts.map +1 -0
- package/dist/just-bash.js +65 -0
- package/dist/runtime-browser.d.ts +31 -0
- package/dist/runtime-browser.d.ts.map +1 -0
- package/dist/runtime-browser.js +47 -0
- package/dist/runtime-node.d.ts +8 -0
- package/dist/runtime-node.d.ts.map +1 -0
- package/dist/runtime-node.js +27 -0
- package/package.json +9 -1
- package/src/__test__/just-bash.test.ts +124 -0
- package/src/__test__/types.test-d.ts +10 -4
- package/src/goke.ts +136 -43
- package/src/index.ts +2 -2
- package/src/just-bash.ts +90 -0
- package/src/runtime-browser.ts +59 -0
- package/src/runtime-node.ts +29 -0
package/dist/goke.js
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* - createConsole: factory for console-like objects from output streams
|
|
10
10
|
* - Utility functions: string helpers, bracket parsing, dot-prop access
|
|
11
11
|
*/
|
|
12
|
-
import { EventEmitter } from 'events';
|
|
13
12
|
import pc from 'picocolors';
|
|
14
13
|
import mri from "./mri.js";
|
|
15
14
|
import { GokeError, coerceBySchema, extractJsonSchema, extractSchemaMetadata, isStandardSchema } from "./coerce.js";
|
|
15
|
+
import { createJustBashCommand as createJustBashCommandBridge } from './just-bash.js';
|
|
16
|
+
import { EventEmitter, openInBrowser, process } from '#runtime';
|
|
16
17
|
// ─── Node.js platform constants ───
|
|
17
18
|
const processArgs = process.argv;
|
|
18
19
|
const platformInfo = `${process.platform}-${process.arch} node-${process.version}`;
|
|
@@ -238,6 +239,9 @@ class Option {
|
|
|
238
239
|
this.isBoolean = true;
|
|
239
240
|
}
|
|
240
241
|
}
|
|
242
|
+
clone() {
|
|
243
|
+
return new Option(this.rawName, this.schema ?? this.description);
|
|
244
|
+
}
|
|
241
245
|
}
|
|
242
246
|
class Command {
|
|
243
247
|
rawName;
|
|
@@ -551,6 +555,29 @@ class GlobalCommand extends Command {
|
|
|
551
555
|
super('@@global@@', '', {}, cli);
|
|
552
556
|
}
|
|
553
557
|
}
|
|
558
|
+
const cloneCommandInto = (source, cli) => {
|
|
559
|
+
const target = source instanceof GlobalCommand
|
|
560
|
+
? new GlobalCommand(cli)
|
|
561
|
+
: new Command(source.rawName, source.description, { ...source.config }, cli);
|
|
562
|
+
target.aliasNames = [...source.aliasNames];
|
|
563
|
+
target.usageText = source.usageText;
|
|
564
|
+
target.versionNumber = source.versionNumber;
|
|
565
|
+
target.examples = [...source.examples];
|
|
566
|
+
target.helpCallback = source.helpCallback;
|
|
567
|
+
target.commandAction = source.commandAction;
|
|
568
|
+
target._hidden = source._hidden;
|
|
569
|
+
target.options = source.options.map((option) => option.clone());
|
|
570
|
+
target.globalCommand = cli.globalCommand;
|
|
571
|
+
return target;
|
|
572
|
+
};
|
|
573
|
+
class GokeProcessExit extends Error {
|
|
574
|
+
code;
|
|
575
|
+
constructor(code) {
|
|
576
|
+
super(`process.exit(${code})`);
|
|
577
|
+
this.name = 'GokeProcessExit';
|
|
578
|
+
this.code = code;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
554
581
|
/**
|
|
555
582
|
* Creates a console-like object that writes to the given output streams.
|
|
556
583
|
*
|
|
@@ -566,6 +593,12 @@ function createConsole(stdout, stderr) {
|
|
|
566
593
|
error(...args) {
|
|
567
594
|
stderr.write(args.map(String).join(' ') + '\n');
|
|
568
595
|
},
|
|
596
|
+
warn(...args) {
|
|
597
|
+
stderr.write(args.map(String).join(' ') + '\n');
|
|
598
|
+
},
|
|
599
|
+
info(...args) {
|
|
600
|
+
stdout.write(args.map(String).join(' ') + '\n');
|
|
601
|
+
},
|
|
569
602
|
};
|
|
570
603
|
}
|
|
571
604
|
// ─── Error formatting ───
|
|
@@ -641,6 +674,46 @@ class Goke extends EventEmitter {
|
|
|
641
674
|
this.globalCommand = new GlobalCommand(this);
|
|
642
675
|
this.globalCommand.usage('<command> [options]');
|
|
643
676
|
}
|
|
677
|
+
clone(options) {
|
|
678
|
+
const cloned = new Goke(this.name, {
|
|
679
|
+
stdout: options?.stdout ?? this.stdout,
|
|
680
|
+
stderr: options?.stderr ?? this.stderr,
|
|
681
|
+
argv: options?.argv ?? this.#defaultArgv,
|
|
682
|
+
columns: options?.columns ?? this.columns,
|
|
683
|
+
exit: options?.exit ?? this.exit,
|
|
684
|
+
});
|
|
685
|
+
cloned.showHelpOnExit = this.showHelpOnExit;
|
|
686
|
+
cloned.showVersionOnExit = this.showVersionOnExit;
|
|
687
|
+
cloned.globalCommand = cloneCommandInto(this.globalCommand, cloned);
|
|
688
|
+
cloned.commands = this.commands.map((command) => cloneCommandInto(command, cloned));
|
|
689
|
+
for (const command of cloned.commands) {
|
|
690
|
+
command.globalCommand = cloned.globalCommand;
|
|
691
|
+
}
|
|
692
|
+
cloned.middlewares = this.middlewares.map((middleware) => ({ action: middleware.action }));
|
|
693
|
+
for (const eventName of this.eventNames()) {
|
|
694
|
+
for (const listener of this.listeners(eventName)) {
|
|
695
|
+
cloned.on(eventName, listener);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return cloned;
|
|
699
|
+
}
|
|
700
|
+
createExecutionContext(argv = this.rawArgs) {
|
|
701
|
+
return {
|
|
702
|
+
console: this.console,
|
|
703
|
+
process: {
|
|
704
|
+
argv,
|
|
705
|
+
stdout: this.stdout,
|
|
706
|
+
stderr: this.stderr,
|
|
707
|
+
exit: (code) => {
|
|
708
|
+
this.exit(code);
|
|
709
|
+
throw new GokeProcessExit(code);
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
async createJustBashCommand(options) {
|
|
715
|
+
return createJustBashCommandBridge(this, options);
|
|
716
|
+
}
|
|
644
717
|
/**
|
|
645
718
|
* Add a global usage text.
|
|
646
719
|
*
|
|
@@ -671,15 +744,16 @@ class Goke extends EventEmitter {
|
|
|
671
744
|
* options (e.g. setting up logging, initializing state).
|
|
672
745
|
*
|
|
673
746
|
* The callback receives the parsed options object, typed according to all
|
|
674
|
-
* `.option()` calls that precede this `.use()` in the chain
|
|
747
|
+
* `.option()` calls that precede this `.use()` in the chain, plus an injected
|
|
748
|
+
* execution context with `{ console, process }` for portable output and exits.
|
|
675
749
|
*
|
|
676
750
|
* @example
|
|
677
751
|
* ```ts
|
|
678
752
|
* cli
|
|
679
753
|
* .option('--verbose', z.boolean().default(false).describe('Verbose'))
|
|
680
|
-
* .use((options) => {
|
|
754
|
+
* .use((options, { console }) => {
|
|
681
755
|
* if (options.verbose) {
|
|
682
|
-
*
|
|
756
|
+
* console.log('verbose mode enabled')
|
|
683
757
|
* }
|
|
684
758
|
* })
|
|
685
759
|
* ```
|
|
@@ -1036,6 +1110,7 @@ class Goke extends EventEmitter {
|
|
|
1036
1110
|
}
|
|
1037
1111
|
runMatchedCommand() {
|
|
1038
1112
|
const { args, options, matchedCommand: command } = this;
|
|
1113
|
+
const executionContext = this.createExecutionContext();
|
|
1039
1114
|
if (!command || !command.commandAction)
|
|
1040
1115
|
return;
|
|
1041
1116
|
try {
|
|
@@ -1060,6 +1135,7 @@ class Goke extends EventEmitter {
|
|
|
1060
1135
|
}
|
|
1061
1136
|
});
|
|
1062
1137
|
actionArgs.push(options);
|
|
1138
|
+
actionArgs.push(executionContext);
|
|
1063
1139
|
const executeAction = () => command.commandAction.apply(this, actionArgs);
|
|
1064
1140
|
const handleAsyncError = (err) => {
|
|
1065
1141
|
if (err instanceof Error) {
|
|
@@ -1076,58 +1152,49 @@ class Goke extends EventEmitter {
|
|
|
1076
1152
|
let asyncChain = null;
|
|
1077
1153
|
for (const mw of this.middlewares) {
|
|
1078
1154
|
if (asyncChain) {
|
|
1079
|
-
asyncChain = asyncChain.then(() => mw.action(options));
|
|
1155
|
+
asyncChain = asyncChain.then(() => mw.action(options, executionContext));
|
|
1080
1156
|
}
|
|
1081
1157
|
else {
|
|
1082
1158
|
try {
|
|
1083
|
-
const mwResult = mw.action(options);
|
|
1159
|
+
const mwResult = mw.action(options, executionContext);
|
|
1084
1160
|
if (isPromiseLike(mwResult)) {
|
|
1085
1161
|
asyncChain = mwResult;
|
|
1086
1162
|
}
|
|
1087
1163
|
}
|
|
1088
1164
|
catch (err) {
|
|
1165
|
+
if (err instanceof GokeProcessExit) {
|
|
1166
|
+
throw err;
|
|
1167
|
+
}
|
|
1089
1168
|
handleAsyncError(err);
|
|
1090
1169
|
return;
|
|
1091
1170
|
}
|
|
1092
1171
|
}
|
|
1093
1172
|
}
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
// ─── openInBrowser ───
|
|
1105
|
-
/**
|
|
1106
|
-
* Open a URL in the default browser.
|
|
1107
|
-
* In non-TTY environments (CI, piped output, agents), prints the URL to stdout instead.
|
|
1108
|
-
*/
|
|
1109
|
-
function openInBrowser(url) {
|
|
1110
|
-
if (!process.stdout.isTTY) {
|
|
1111
|
-
console.error(url);
|
|
1112
|
-
return;
|
|
1113
|
-
}
|
|
1114
|
-
const { execSync } = require('child_process');
|
|
1115
|
-
const platform = process.platform;
|
|
1116
|
-
try {
|
|
1117
|
-
if (platform === 'darwin') {
|
|
1118
|
-
execSync(`open ${JSON.stringify(url)}`, { stdio: 'ignore' });
|
|
1173
|
+
const catchAsyncError = (err) => {
|
|
1174
|
+
if (err instanceof GokeProcessExit) {
|
|
1175
|
+
throw err;
|
|
1176
|
+
}
|
|
1177
|
+
handleAsyncError(err);
|
|
1178
|
+
};
|
|
1179
|
+
if (asyncChain) {
|
|
1180
|
+
return asyncChain
|
|
1181
|
+
.then(executeAction)
|
|
1182
|
+
.catch(catchAsyncError);
|
|
1119
1183
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1184
|
+
try {
|
|
1185
|
+
const result = executeAction();
|
|
1186
|
+
return isPromiseLike(result)
|
|
1187
|
+
? result.catch(catchAsyncError)
|
|
1188
|
+
: result;
|
|
1122
1189
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1190
|
+
catch (err) {
|
|
1191
|
+
if (err instanceof GokeProcessExit) {
|
|
1192
|
+
throw err;
|
|
1193
|
+
}
|
|
1194
|
+
handleAsyncError(err);
|
|
1195
|
+
return;
|
|
1125
1196
|
}
|
|
1126
1197
|
}
|
|
1127
|
-
catch {
|
|
1128
|
-
// fallback: print the URL if open fails
|
|
1129
|
-
console.error(url);
|
|
1130
|
-
}
|
|
1131
1198
|
}
|
|
1132
|
-
export { createConsole, Command, openInBrowser };
|
|
1199
|
+
export { createConsole, Command, GokeProcessExit, openInBrowser };
|
|
1133
1200
|
export default Goke;
|
package/dist/index.d.ts
CHANGED
|
@@ -8,8 +8,8 @@ import { Command } from "./goke.js";
|
|
|
8
8
|
declare const goke: (name?: string, options?: GokeOptions) => Goke<{}>;
|
|
9
9
|
export default goke;
|
|
10
10
|
export { goke, Goke, Command };
|
|
11
|
-
export { createConsole, openInBrowser } from "./goke.js";
|
|
12
|
-
export type { GokeOutputStream, GokeConsole, GokeOptions } from "./goke.js";
|
|
11
|
+
export { createConsole, GokeProcessExit, openInBrowser } from "./goke.js";
|
|
12
|
+
export type { GokeOutputStream, GokeConsole, GokeExecutionContext, GokeOptions, GokeProcess } from "./goke.js";
|
|
13
13
|
export type { StandardTypedV1, StandardJSONSchemaV1, JsonSchema } from "./coerce.js";
|
|
14
14
|
export { GokeError, coerceBySchema, extractJsonSchema, wrapJsonSchema, isStandardSchema, extractSchemaMetadata } from "./coerce.js";
|
|
15
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC;;;GAGG;AACH,QAAA,MAAM,IAAI,GAAI,aAAS,EAAE,UAAU,WAAW,aAA4B,CAAA;AAE1E,eAAe,IAAI,CAAA;AACnB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC;;;GAGG;AACH,QAAA,MAAM,IAAI,GAAI,aAAS,EAAE,UAAU,WAAW,aAA4B,CAAA;AAE1E,eAAe,IAAI,CAAA;AACnB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,oBAAoB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC9G,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACpF,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -7,5 +7,5 @@ import { Command } from "./goke.js";
|
|
|
7
7
|
const goke = (name = '', options) => new Goke(name, options);
|
|
8
8
|
export default goke;
|
|
9
9
|
export { goke, Goke, Command };
|
|
10
|
-
export { createConsole, openInBrowser } from "./goke.js";
|
|
10
|
+
export { createConsole, GokeProcessExit, openInBrowser } from "./goke.js";
|
|
11
11
|
export { GokeError, coerceBySchema, extractJsonSchema, wrapJsonSchema, isStandardSchema, extractSchemaMetadata } from "./coerce.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime adapter that exposes a goke CLI as a JustBash-compatible custom command.
|
|
3
|
+
*
|
|
4
|
+
* Structural types here are based on JustBash source definitions:
|
|
5
|
+
* - https://github.com/vercel-labs/just-bash/blob/main/src/custom-commands.ts
|
|
6
|
+
* - https://github.com/vercel-labs/just-bash/blob/main/src/types.ts
|
|
7
|
+
*/
|
|
8
|
+
import Goke from './goke.js';
|
|
9
|
+
interface JustBashExecResult {
|
|
10
|
+
stdout: string;
|
|
11
|
+
stderr: string;
|
|
12
|
+
exitCode: number;
|
|
13
|
+
}
|
|
14
|
+
interface JustBashCommand {
|
|
15
|
+
name: string;
|
|
16
|
+
trusted: true;
|
|
17
|
+
execute(args: string[]): Promise<JustBashExecResult>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createJustBashCommand(cli: Goke<any>, options?: {
|
|
20
|
+
name?: string;
|
|
21
|
+
}): JustBashCommand;
|
|
22
|
+
export type { JustBashCommand, JustBashExecResult };
|
|
23
|
+
//# sourceMappingURL=just-bash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"just-bash.d.ts","sourceRoot":"","sources":["../src/just-bash.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,IAAyB,MAAM,WAAW,CAAA;AAGjD,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,IAAI,CAAA;IACb,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;CACrD;AAcD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,EACd,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1B,eAAe,CAiDjB;AAED,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime adapter that exposes a goke CLI as a JustBash-compatible custom command.
|
|
3
|
+
*
|
|
4
|
+
* Structural types here are based on JustBash source definitions:
|
|
5
|
+
* - https://github.com/vercel-labs/just-bash/blob/main/src/custom-commands.ts
|
|
6
|
+
* - https://github.com/vercel-labs/just-bash/blob/main/src/types.ts
|
|
7
|
+
*/
|
|
8
|
+
import { GokeProcessExit } from './goke.js';
|
|
9
|
+
function createTextCaptureStream() {
|
|
10
|
+
const chunks = [];
|
|
11
|
+
return {
|
|
12
|
+
get text() {
|
|
13
|
+
return chunks.join('');
|
|
14
|
+
},
|
|
15
|
+
write(data) {
|
|
16
|
+
chunks.push(data);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function createJustBashCommand(cli, options) {
|
|
21
|
+
const name = options?.name ?? cli.name;
|
|
22
|
+
if (!name) {
|
|
23
|
+
throw new Error('createJustBashCommand() requires the CLI to have a name');
|
|
24
|
+
}
|
|
25
|
+
if (name.split(/\s+/).length > 1) {
|
|
26
|
+
throw new Error('JustBash custom command names must be a single token');
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
name,
|
|
30
|
+
trusted: true,
|
|
31
|
+
async execute(args) {
|
|
32
|
+
const stdout = createTextCaptureStream();
|
|
33
|
+
const stderr = createTextCaptureStream();
|
|
34
|
+
const argv = ['node', name, ...args];
|
|
35
|
+
const cloned = cli.clone({
|
|
36
|
+
stdout,
|
|
37
|
+
stderr,
|
|
38
|
+
argv,
|
|
39
|
+
exit: (code) => {
|
|
40
|
+
throw new GokeProcessExit(code);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
cloned.name = name;
|
|
44
|
+
try {
|
|
45
|
+
cloned.parse(argv, { run: false });
|
|
46
|
+
await cloned.runMatchedCommand();
|
|
47
|
+
return {
|
|
48
|
+
stdout: stdout.text,
|
|
49
|
+
stderr: stderr.text,
|
|
50
|
+
exitCode: 0,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error instanceof GokeProcessExit) {
|
|
55
|
+
return {
|
|
56
|
+
stdout: stdout.text,
|
|
57
|
+
stderr: stderr.text,
|
|
58
|
+
exitCode: error.code,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe runtime stubs for goke core.
|
|
3
|
+
*/
|
|
4
|
+
type Listener = (...args: any[]) => void;
|
|
5
|
+
declare class EventEmitter {
|
|
6
|
+
#private;
|
|
7
|
+
on(eventName: string | symbol, listener: Listener): this;
|
|
8
|
+
emit(eventName: string | symbol, ...args: any[]): boolean;
|
|
9
|
+
eventNames(): (string | symbol)[];
|
|
10
|
+
listeners(eventName: string | symbol): Listener[];
|
|
11
|
+
}
|
|
12
|
+
declare const process: {
|
|
13
|
+
argv: string[];
|
|
14
|
+
arch: string;
|
|
15
|
+
platform: string;
|
|
16
|
+
version: string;
|
|
17
|
+
stdout: {
|
|
18
|
+
columns: number;
|
|
19
|
+
isTTY: boolean;
|
|
20
|
+
write(_data: string): void;
|
|
21
|
+
};
|
|
22
|
+
stderr: {
|
|
23
|
+
columns: number;
|
|
24
|
+
isTTY: boolean;
|
|
25
|
+
write(_data: string): void;
|
|
26
|
+
};
|
|
27
|
+
exit(code: number): never;
|
|
28
|
+
};
|
|
29
|
+
declare function openInBrowser(_url: string): void;
|
|
30
|
+
export { EventEmitter, openInBrowser, process };
|
|
31
|
+
//# sourceMappingURL=runtime-browser.d.ts.map
|
|
@@ -0,0 +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;;;;;;;qBAJP,MAAM;;;;;qBAAN,MAAM;;eAUR,MAAM,GAAG,KAAK;CAK1B,CAAA;AAED,iBAAS,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe runtime stubs for goke core.
|
|
3
|
+
*/
|
|
4
|
+
class EventEmitter {
|
|
5
|
+
#listeners = new Map();
|
|
6
|
+
on(eventName, listener) {
|
|
7
|
+
const listeners = this.#listeners.get(eventName) ?? [];
|
|
8
|
+
listeners.push(listener);
|
|
9
|
+
this.#listeners.set(eventName, listeners);
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
emit(eventName, ...args) {
|
|
13
|
+
const listeners = this.#listeners.get(eventName);
|
|
14
|
+
if (!listeners || listeners.length === 0)
|
|
15
|
+
return false;
|
|
16
|
+
for (const listener of listeners) {
|
|
17
|
+
listener(...args);
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
eventNames() {
|
|
22
|
+
return [...this.#listeners.keys()];
|
|
23
|
+
}
|
|
24
|
+
listeners(eventName) {
|
|
25
|
+
return [...(this.#listeners.get(eventName) ?? [])];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const createOutputStream = () => ({
|
|
29
|
+
columns: Number.POSITIVE_INFINITY,
|
|
30
|
+
isTTY: false,
|
|
31
|
+
write(_data) { },
|
|
32
|
+
});
|
|
33
|
+
const process = {
|
|
34
|
+
argv: [],
|
|
35
|
+
arch: 'browser',
|
|
36
|
+
platform: 'browser',
|
|
37
|
+
version: 'browser',
|
|
38
|
+
stdout: createOutputStream(),
|
|
39
|
+
stderr: createOutputStream(),
|
|
40
|
+
exit(code) {
|
|
41
|
+
throw new Error(`process.exit(${code}) is not available in the browser runtime. Pass a custom exit handler to goke(...).`);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
function openInBrowser(_url) {
|
|
45
|
+
// Browser builds should decide how to surface URLs themselves.
|
|
46
|
+
}
|
|
47
|
+
export { EventEmitter, openInBrowser, process };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js runtime bindings for goke core.
|
|
3
|
+
*/
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
declare const process: NodeJS.Process;
|
|
6
|
+
declare function openInBrowser(url: string): void;
|
|
7
|
+
export { EventEmitter, openInBrowser, process };
|
|
8
|
+
//# sourceMappingURL=runtime-node.d.ts.map
|
|
@@ -0,0 +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;AAElC,iBAAS,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAiBxC;AAED,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,CAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js runtime bindings for goke core.
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
const process = globalThis.process;
|
|
7
|
+
function openInBrowser(url) {
|
|
8
|
+
if (!process.stdout.isTTY) {
|
|
9
|
+
process.stderr.write(url + '\n');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
if (process.platform === 'darwin') {
|
|
14
|
+
execSync(`open ${JSON.stringify(url)}`, { stdio: 'ignore' });
|
|
15
|
+
}
|
|
16
|
+
else if (process.platform === 'win32') {
|
|
17
|
+
execSync(`start "" ${JSON.stringify(url)}`, { stdio: 'ignore' });
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
execSync(`xdg-open ${JSON.stringify(url)}`, { stdio: 'ignore' });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
process.stderr.write(url + '\n');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export { EventEmitter, openInBrowser, process };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goke",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Simple yet powerful framework for building command-line apps. Inspired by cac.",
|
|
6
6
|
"repository": {
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
],
|
|
22
22
|
"main": "./dist/index.js",
|
|
23
23
|
"types": "./dist/index.d.ts",
|
|
24
|
+
"imports": {
|
|
25
|
+
"#runtime": {
|
|
26
|
+
"types": "./src/runtime-node.ts",
|
|
27
|
+
"development": "./src/runtime-node.ts",
|
|
28
|
+
"node": "./dist/runtime-node.js",
|
|
29
|
+
"default": "./dist/runtime-browser.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
24
32
|
"exports": {
|
|
25
33
|
"./package.json": "./package.json",
|
|
26
34
|
".": {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for injected execution context, clone isolation, and the JustBash bridge.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from 'vitest'
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
import goke from '../index.js'
|
|
8
|
+
import type { GokeOutputStream, GokeOptions } from '../index.js'
|
|
9
|
+
|
|
10
|
+
const ANSI_RE = /\x1B\[[0-9;]*m/g
|
|
11
|
+
|
|
12
|
+
const stripAnsi = (text: string) => text.replace(ANSI_RE, '')
|
|
13
|
+
|
|
14
|
+
function createTestOutputStream(): GokeOutputStream & { lines: string[]; readonly text: string } {
|
|
15
|
+
const lines: string[] = []
|
|
16
|
+
return {
|
|
17
|
+
lines,
|
|
18
|
+
get text() { return stripAnsi(lines.join('')) },
|
|
19
|
+
write(data: string) { lines.push(data) },
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function gokeTestable(name = '', options?: Partial<GokeOptions>) {
|
|
24
|
+
return goke(name, {
|
|
25
|
+
...options,
|
|
26
|
+
exit: () => {},
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('injected execution context', () => {
|
|
31
|
+
test('command action receives injected console and process', () => {
|
|
32
|
+
const stdout = createTestOutputStream()
|
|
33
|
+
const cli = gokeTestable('mycli', { stdout })
|
|
34
|
+
let seenArgv: string[] | undefined
|
|
35
|
+
|
|
36
|
+
cli
|
|
37
|
+
.command('status', 'Show status')
|
|
38
|
+
.action((options, { console, process }) => {
|
|
39
|
+
console.log('ready')
|
|
40
|
+
seenArgv = process.argv
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
cli.parse(['node', 'bin', 'status'], { run: true })
|
|
44
|
+
|
|
45
|
+
expect(stdout.text).toBe('ready\n')
|
|
46
|
+
expect(seenArgv).toEqual(['node', 'bin', 'status'])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('middleware receives injected console and process', () => {
|
|
50
|
+
const stdout = createTestOutputStream()
|
|
51
|
+
const cli = gokeTestable('mycli', { stdout })
|
|
52
|
+
let seenArgv: string[] | undefined
|
|
53
|
+
|
|
54
|
+
cli
|
|
55
|
+
.use((options, { console, process }) => {
|
|
56
|
+
console.log('middleware')
|
|
57
|
+
seenArgv = process.argv
|
|
58
|
+
})
|
|
59
|
+
.command('build', 'Build')
|
|
60
|
+
.action(() => {})
|
|
61
|
+
|
|
62
|
+
cli.parse(['node', 'bin', 'build'], { run: true })
|
|
63
|
+
|
|
64
|
+
expect(stdout.text).toBe('middleware\n')
|
|
65
|
+
expect(seenArgv).toEqual(['node', 'bin', 'build'])
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('clone', () => {
|
|
70
|
+
test('clone creates isolated parse state', () => {
|
|
71
|
+
const cli = gokeTestable('mycli')
|
|
72
|
+
|
|
73
|
+
cli.command('build', 'Build').action(() => {})
|
|
74
|
+
|
|
75
|
+
const cloned = (cli as any).clone({ exit: () => {} })
|
|
76
|
+
|
|
77
|
+
cloned.parse(['node', 'bin', 'build'], { run: false })
|
|
78
|
+
|
|
79
|
+
expect(cloned).not.toBe(cli)
|
|
80
|
+
expect(cloned.matchedCommandName).toBe('build')
|
|
81
|
+
expect(cli.matchedCommandName).toBeUndefined()
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe('createJustBashCommand', () => {
|
|
86
|
+
test('runs multi-word goke subcommands through one just-bash command', async () => {
|
|
87
|
+
const cli = gokeTestable('parent')
|
|
88
|
+
|
|
89
|
+
cli
|
|
90
|
+
.command('child commandwithspaces', 'Run nested command')
|
|
91
|
+
.option('--name <name>', z.string().describe('Name'))
|
|
92
|
+
.action((options, { console }) => {
|
|
93
|
+
console.log(`hello ${options.name}`)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const customCommand = await (cli as any).createJustBashCommand()
|
|
97
|
+
const result = await customCommand.execute(['child', 'commandwithspaces', '--name', 'Tommy'])
|
|
98
|
+
|
|
99
|
+
expect(result).toEqual({
|
|
100
|
+
stdout: 'hello Tommy\n',
|
|
101
|
+
stderr: '',
|
|
102
|
+
exitCode: 0,
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('maps injected process.exit to a command exit code', async () => {
|
|
107
|
+
const cli = gokeTestable('parent')
|
|
108
|
+
|
|
109
|
+
cli
|
|
110
|
+
.command('fail', 'Exit with custom code')
|
|
111
|
+
.action((options, { process }) => {
|
|
112
|
+
process.exit(7)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const customCommand = await (cli as any).createJustBashCommand()
|
|
116
|
+
const result = await customCommand.execute(['fail'])
|
|
117
|
+
|
|
118
|
+
expect(result).toEqual({
|
|
119
|
+
stdout: '',
|
|
120
|
+
stderr: '',
|
|
121
|
+
exitCode: 7,
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
})
|
|
@@ -119,9 +119,12 @@ 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) => {
|
|
122
|
+
.use((options, { console, process }) => {
|
|
123
123
|
expectTypeOf(options.port).toEqualTypeOf<number>()
|
|
124
124
|
expectTypeOf(options.host).toEqualTypeOf<string>()
|
|
125
|
+
expectTypeOf(process.argv).toEqualTypeOf<string[]>()
|
|
126
|
+
expectTypeOf(process.stdout.write).toEqualTypeOf<(data: string) => void>()
|
|
127
|
+
expectTypeOf(console.log).toBeFunction()
|
|
125
128
|
})
|
|
126
129
|
})
|
|
127
130
|
|
|
@@ -131,16 +134,18 @@ describe('type-level: middleware use() callback inference', () => {
|
|
|
131
134
|
|
|
132
135
|
goke('test')
|
|
133
136
|
.option('--verbose', schema1)
|
|
134
|
-
.use((options) => {
|
|
137
|
+
.use((options, { process }) => {
|
|
135
138
|
expectTypeOf(options.verbose).toEqualTypeOf<boolean | undefined>()
|
|
139
|
+
expectTypeOf(process.exit).toEqualTypeOf<(code: number) => void>()
|
|
136
140
|
// @ts-expect-error port is not declared yet
|
|
137
141
|
options.port
|
|
138
142
|
})
|
|
139
143
|
.option('--port <port>', schema2)
|
|
140
|
-
.use((options) => {
|
|
144
|
+
.use((options, { console }) => {
|
|
141
145
|
// Now both are visible
|
|
142
146
|
expectTypeOf(options.verbose).toEqualTypeOf<boolean | undefined>()
|
|
143
147
|
expectTypeOf(options.port).toEqualTypeOf<number>()
|
|
148
|
+
expectTypeOf(console.error).toBeFunction()
|
|
144
149
|
})
|
|
145
150
|
})
|
|
146
151
|
|
|
@@ -149,7 +154,8 @@ describe('type-level: middleware use() callback inference', () => {
|
|
|
149
154
|
|
|
150
155
|
goke('test')
|
|
151
156
|
.option('--port <port>', schema)
|
|
152
|
-
.use((options) => {
|
|
157
|
+
.use((options, { process }) => {
|
|
158
|
+
expectTypeOf(process.stderr.write).toEqualTypeOf<(data: string) => void>()
|
|
153
159
|
// @ts-expect-error nonExistent was never defined
|
|
154
160
|
options.nonExistent
|
|
155
161
|
})
|