tunli 0.0.19

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.
Files changed (63) hide show
  1. package/LICENSE.md +595 -0
  2. package/README.md +135 -0
  3. package/bin/tunli +11 -0
  4. package/client.js +31 -0
  5. package/package.json +51 -0
  6. package/src/cli-app/Dashboard.js +146 -0
  7. package/src/cli-app/Screen.js +135 -0
  8. package/src/cli-app/elements/ElementNode.js +97 -0
  9. package/src/cli-app/elements/Line.js +21 -0
  10. package/src/cli-app/elements/List/List.js +227 -0
  11. package/src/cli-app/elements/List/ListCell.js +83 -0
  12. package/src/cli-app/elements/List/ListColumn.js +52 -0
  13. package/src/cli-app/elements/List/ListRow.js +118 -0
  14. package/src/cli-app/elements/Row.js +38 -0
  15. package/src/cli-app/helper/utils.js +42 -0
  16. package/src/commands/Action/addDelValuesAction.js +56 -0
  17. package/src/commands/CommandAuth.js +32 -0
  18. package/src/commands/CommandClearAll.js +27 -0
  19. package/src/commands/CommandConfig.js +57 -0
  20. package/src/commands/CommandHTTP.js +131 -0
  21. package/src/commands/CommandInvite.js +38 -0
  22. package/src/commands/CommandRefresh.js +35 -0
  23. package/src/commands/CommandRegister.js +48 -0
  24. package/src/commands/Option/DeleteOption.js +6 -0
  25. package/src/commands/Option/SelectConfigOption.js +52 -0
  26. package/src/commands/SubCommand/AllowDenyCidrCommand.js +28 -0
  27. package/src/commands/SubCommand/HostCommand.js +22 -0
  28. package/src/commands/SubCommand/PortCommand.js +20 -0
  29. package/src/commands/helper/AliasResolver.js +13 -0
  30. package/src/commands/helper/BindArgs.js +53 -0
  31. package/src/commands/helper/SharedArg.js +32 -0
  32. package/src/commands/utils.js +96 -0
  33. package/src/config/ConfigAbstract.js +318 -0
  34. package/src/config/ConfigManager.js +70 -0
  35. package/src/config/GlobalConfig.js +14 -0
  36. package/src/config/GlobalLocalShardConfigAbstract.js +76 -0
  37. package/src/config/LocalConfig.js +7 -0
  38. package/src/config/PropertyConfig.js +122 -0
  39. package/src/config/SystemConfig.js +31 -0
  40. package/src/core/FS/utils.js +60 -0
  41. package/src/core/Ref.js +70 -0
  42. package/src/lib/Flow/getCurrentIp.js +18 -0
  43. package/src/lib/Flow/getLatestVersion.js +13 -0
  44. package/src/lib/Flow/proxyUrl.js +32 -0
  45. package/src/lib/Flow/validateAuthToken.js +19 -0
  46. package/src/lib/HttpClient.js +61 -0
  47. package/src/lib/defs.js +10 -0
  48. package/src/net/IPV4.js +139 -0
  49. package/src/net/http/IncomingMessage.js +92 -0
  50. package/src/net/http/ServerResponse.js +126 -0
  51. package/src/net/http/TunliRequest.js +1 -0
  52. package/src/net/http/TunliResponse.js +1 -0
  53. package/src/net/http/TunnelRequest.js +177 -0
  54. package/src/net/http/TunnelResponse.js +119 -0
  55. package/src/tunnel-client/TunnelClient.js +136 -0
  56. package/src/utils/arrayFunctions.js +45 -0
  57. package/src/utils/checkFunctions.js +161 -0
  58. package/src/utils/cliFunctions.js +62 -0
  59. package/src/utils/createRequest.js +12 -0
  60. package/src/utils/httpFunction.js +23 -0
  61. package/src/utils/npmFunctions.js +27 -0
  62. package/src/utils/stringFunctions.js +34 -0
  63. package/types/index.d.ts +112 -0
@@ -0,0 +1,57 @@
1
+ import {Command} from "commander";
2
+ import {ref} from "#src/core/Ref";
3
+ import {allowDenyCidrCommand} from "#commands/SubCommand/AllowDenyCidrCommand";
4
+ import {portCommand} from "#commands/SubCommand/PortCommand";
5
+ import {hostCommand} from "#commands/SubCommand/HostCommand";
6
+ import {selectConfigOption} from "#commands/Option/SelectConfigOption";
7
+ import {addExample, extendUsage} from "#commands/utils";
8
+
9
+ /**
10
+ *
11
+ * @param {Ref} configRef
12
+ * @param {Command} program
13
+ * @returns {(function(*, *, *): void)|*}
14
+ */
15
+ const exec = (configRef, program) => {
16
+
17
+ return async (key, value, options) => {
18
+
19
+ /** @type {LocalConfig|GlobalConfig} */
20
+ const config = configRef.value
21
+
22
+ const maxKeyLength = Math.max(...Object.keys(config.dump()).map(x => x.length))
23
+ console.log('location: ', config.configPath, "\n")
24
+ for (const [k, v] of Object.entries(config.dump())) {
25
+ console.log(k.padEnd(maxKeyLength, ' '), '=', v)
26
+ console.log(''.padEnd(maxKeyLength + 3 + (v?.length ?? 9), '-'))
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ *
32
+ * @param {Command} program
33
+ */
34
+ export const createCommandConfig = (program) => {
35
+
36
+ const configRef = ref()
37
+
38
+ const cmd = new Command('config')
39
+
40
+ cmd.addCommand(allowDenyCidrCommand('allowCidr', cmd, configRef))
41
+ cmd.addCommand(allowDenyCidrCommand('denyCidr', cmd, configRef))
42
+ cmd.addCommand(portCommand(configRef))
43
+ cmd.addCommand(hostCommand(configRef))
44
+
45
+ selectConfigOption(cmd, configRef, true)
46
+ .action(exec(configRef, program));
47
+
48
+ extendUsage(program, cmd)
49
+ //
50
+ addExample('config host localhost', 'Set the host for the local configuration')
51
+ addExample('config port 80', 'Set the port for the local configuration')
52
+ addExample('config', 'Show the local configuration')
53
+ addExample('config --global', 'Show the global configuration')
54
+
55
+ return cmd
56
+ }
57
+
@@ -0,0 +1,131 @@
1
+ import {Command} from "commander";
2
+ import {checkHost, checkPort} from "#src/utils/checkFunctions";
3
+ import {isSharedArg, sharedArg} from "#commands/helper/SharedArg";
4
+ import {addExample, validateArrayArguments, validateIpV4} from "#commands/utils";
5
+ import {bindArgs} from "#commands/helper/BindArgs";
6
+ import {ref} from "#src/core/Ref";
7
+ import {selectConfigOption} from "#commands/Option/SelectConfigOption";
8
+ import {TunnelClient} from "#src/tunnel-client/TunnelClient";
9
+ import {renewProxyUrlRegistration, requestNewProxyUrl} from "#lib/Flow/proxyUrl";
10
+ import {getCurrentIp} from "#lib/Flow/getCurrentIp";
11
+ import {arrayUnique} from "#src/utils/arrayFunctions";
12
+ import {initDashboard} from "#src/cli-app/Dashboard";
13
+
14
+ /**
15
+ * @callback httpCommandExec
16
+ * @param {number} port
17
+ * @param {string} host
18
+ * @param {tunnelClientOptions} options
19
+ * @returns {Promise<void>}
20
+ */
21
+
22
+ /**
23
+ * @param {Ref} configRef
24
+ * @returns {httpCommandExec}
25
+ */
26
+ const exec = (configRef, cmd, program) => {
27
+
28
+ return async (port, host, options) => {
29
+
30
+ let protocol
31
+
32
+ if (['http', 'https'].includes(cmd.parent.args[0])) {
33
+ protocol = cmd.parent.args[0]
34
+ }
35
+
36
+ /** @type {AppConfig} */
37
+ const config = configRef.value
38
+
39
+ if (options.self) {
40
+ options.allowCidr ??= []
41
+ options.allowCidr.push(await getCurrentIp())
42
+ options.allowCidr = arrayUnique(options.allowCidr)
43
+ }
44
+
45
+ if (config.proxyURL) {
46
+ config.proxyURL = await renewProxyUrlRegistration(config.proxyURL, config.authToken)
47
+ }
48
+
49
+ if (!config.proxyURL) {
50
+ config.proxyURL = await requestNewProxyUrl(config.authToken)
51
+ options.save ??= true
52
+ }
53
+
54
+ if (isSharedArg(port)) {
55
+ protocol ??= port.value.url?.protocol
56
+ host ??= port.value.host ?? port.value.url?.host
57
+ port = port.value.port ?? port.value.url?.port
58
+ options.port ??= port
59
+ options.host ??= host
60
+ }
61
+
62
+ options.port ??= port
63
+ options.host ??= host
64
+
65
+ const save = options.save
66
+ delete options.save
67
+
68
+ if (save && save !== true) {
69
+ config.copyCurrentProfileTo(save).use(save)
70
+ }
71
+
72
+ for (const [k, v] of Object.entries(options)) {
73
+ if (v !== undefined) {
74
+ config[k] = v
75
+ }
76
+ }
77
+
78
+ if (save) {
79
+ config.save()
80
+ }
81
+
82
+ /**
83
+ * @type {tunnelClientOptions}
84
+ */
85
+ const clientOptions = {
86
+ port: options.port ?? config.port,
87
+ host: options.host ?? config.host,
88
+ authToken: config.authToken,
89
+ server: config.proxyURL,
90
+ path: undefined,
91
+ allowCidr: options.allowCidr ?? config.allowCidr,
92
+ denyCidr: options.denyCidr ?? config.denyCidr,
93
+ protocol: protocol ?? 'http'
94
+ }
95
+
96
+ const client = new TunnelClient(clientOptions)
97
+ const dashboard = initDashboard(client, clientOptions, config)
98
+ await client.init(dashboard)
99
+ }
100
+ }
101
+ /**
102
+ * @param {Command} program
103
+ */
104
+ export const createCommandHTTP = (program) => {
105
+
106
+ const configRef = ref()
107
+ const cmd = new Command('http')
108
+ cmd.alias('https')
109
+
110
+ selectConfigOption(cmd, configRef)
111
+ // validateAuthToken(cmd, configRef)
112
+
113
+ const sharedArgument = sharedArg({})
114
+ cmd.argument('[PORT]', 'port welcher durch den proxy erreichbar sein soll (default: "80")', bindArgs(checkPort, sharedArgument, true))
115
+ cmd.argument('[HOST]', 'host welcher durch den proxy erreichbar sein soll (default: "localhost"/"config.value")', bindArgs(checkHost, sharedArgument, true))
116
+
117
+ cmd.option('--host <string>', 'setting hostname', bindArgs(checkHost, sharedArgument, false))
118
+ cmd.option('--port <string>', 'setting port', bindArgs(checkPort, sharedArgument, false))
119
+ cmd.option('--allow-cidr <string>', 'allow-cidr', validateArrayArguments(validateIpV4))
120
+ cmd.option('--deny-cidr <string>', 'deny-cidr', validateArrayArguments(validateIpV4))
121
+ cmd.option('--self', 'allow self only', false)
122
+ cmd.option('--save [alias]', 'save current settings as alias/local')
123
+ cmd.action(exec(configRef, cmd, program))
124
+
125
+ addExample('http localhost:80', 'HTTP Forward to localhost:80')
126
+ addExample('', 'Forward to port from default config, host from default config')
127
+ // extendUsage(program, cmd)
128
+ // .option('-o, --origin <string>', 'change request origin')
129
+
130
+ return cmd
131
+ }
@@ -0,0 +1,38 @@
1
+ import {Command} from "commander";
2
+ import {httpClient, securedHttpClient} from "#lib/HttpClient";
3
+ import {addExample, extendUsage} from "#commands/utils";
4
+ import {ConfigManager} from "#src/config/ConfigManager";
5
+
6
+ /**
7
+ *
8
+ * @returns {(function(): void)|*}
9
+ */
10
+ const exec = () => {
11
+
12
+ return async () => {
13
+ const config = ConfigManager.loadSystem()
14
+ const {data, error} = await securedHttpClient(config.authToken).get('/invite')
15
+
16
+ if (error) {
17
+ console.error(error)
18
+ process.exit(1)
19
+ }
20
+
21
+ console.log('Done.', data)
22
+ }
23
+ }
24
+
25
+ /**
26
+ *
27
+ * @param {Command} program
28
+ */
29
+ export const createCommandInvite = (program) => {
30
+
31
+ const cmd = new Command('invite')
32
+ .action(exec())
33
+
34
+ extendUsage(program, cmd)
35
+ addExample('invite', 'create sharable registration token')
36
+ return cmd
37
+
38
+ }
@@ -0,0 +1,35 @@
1
+ import {Command} from "commander";
2
+ import {selectConfigOption} from "#commands/Option/SelectConfigOption";
3
+ import {ref} from "#src/core/Ref";
4
+ import {validateAuthToken} from "#lib/Flow/validateAuthToken";
5
+ import {requestNewProxyUrl} from "#lib/Flow/proxyUrl";
6
+
7
+ /**
8
+ * @param {Ref} configRef
9
+ * @returns {(function(*, *): void)|*}
10
+ */
11
+ const exec = (configRef) => {
12
+ return async () => {
13
+ /** @type {LocalConfig|GlobalConfig} */
14
+ const config = configRef.value
15
+ config.proxyURL = await requestNewProxyUrl(config.authToken)
16
+ config.save()
17
+ console.log('done')
18
+ }
19
+ }
20
+ /**
21
+ * @param {Command} program
22
+ */
23
+ export const createCommandRefresh = (program) => {
24
+
25
+ const configRef = ref()
26
+ const cmd = new Command('refresh')
27
+
28
+ selectConfigOption(cmd, configRef)
29
+ validateAuthToken(cmd, configRef)
30
+
31
+ cmd.action(exec(configRef))
32
+ // extendUsage(program, cmd)
33
+
34
+ return cmd
35
+ }
@@ -0,0 +1,48 @@
1
+ import {Command} from "commander";
2
+ import {httpClient} from "#lib/HttpClient";
3
+ import {ConfigManager} from "#src/config/ConfigManager";
4
+
5
+ /**
6
+ *
7
+ * @returns {(function(): void)|*}
8
+ */
9
+ const exec = () => {
10
+
11
+ return async (options) => {
12
+
13
+ const forceRenew = options.f === true
14
+ const config = ConfigManager.loadSystem()
15
+
16
+ if (config.authToken && !forceRenew) {
17
+ console.log(`Auth token exists. use -f to renew: ${config.authToken}`)
18
+ process.exit()
19
+ }
20
+
21
+ const {data, error} = await httpClient.get('/register')
22
+
23
+ if (error) {
24
+ console.error(error)
25
+ process.exit(1)
26
+ }
27
+
28
+ config.authToken = data
29
+ config.save()
30
+ console.log('Done.',config.authToken)
31
+ }
32
+ }
33
+
34
+
35
+ /**
36
+ *
37
+ * @param {Command} program
38
+ */
39
+ export const createCommandRegister = (program) => {
40
+
41
+ const cmd = new Command('register')
42
+ .option('-f', 'fore renew auth token')
43
+ .action(exec())
44
+
45
+ return cmd
46
+ // extendUsage(program, cmd)
47
+ // addExample('register', 'register call')
48
+ }
@@ -0,0 +1,6 @@
1
+ import {Option} from "commander";
2
+
3
+ export const deleteOption = (desc = 'delete config entry') => {
4
+ const option = new Option('--del', desc)
5
+ return option
6
+ }
@@ -0,0 +1,52 @@
1
+ import {ConfigManager} from "#src/config/ConfigManager";
2
+
3
+ /**
4
+ * @template T
5
+ * @param {T} command
6
+ * @param {Ref} [configRef]
7
+ * @param {boolean} strictMode
8
+ * @returns {T}
9
+ */
10
+ export const selectConfigOption = (command, configRef, strictMode = false) => {
11
+
12
+ command.option('--global', 'Use the global configuration file (default)')
13
+ .option('--workdir', 'Use the configuration file for the current working directory')
14
+ .option('-p --alias <string>', 'setting alias name', 'default')
15
+
16
+ if (configRef) {
17
+ command.hook('preAction', (thisCommand, actionCommand) => {
18
+
19
+ let {alias, workdir, global} = thisCommand.opts()
20
+
21
+ if (strictMode) {
22
+ if (global) {
23
+ workdir = false
24
+ }
25
+ if (workdir) {
26
+ global = false
27
+ }
28
+ global ??= true
29
+ workdir ??= false
30
+ } else {
31
+ if (workdir) {
32
+ global ??= false
33
+ }
34
+ if (global) {
35
+ workdir ??= false
36
+ }
37
+ global ??= true
38
+ workdir ??= true
39
+ }
40
+
41
+ if (global && workdir) {
42
+ configRef.value = ConfigManager.loadCombined(alias)
43
+ } else if (global) {
44
+ configRef.value = ConfigManager.loadGlobalOnly(alias)
45
+ } else {
46
+ configRef.value = ConfigManager.loadLocalOnly(alias, !strictMode)
47
+ }
48
+ })
49
+ }
50
+
51
+ return command
52
+ }
@@ -0,0 +1,28 @@
1
+ import {Command} from "commander";
2
+ import {addDelValuesAction} from "#commands/Action/addDelValuesAction";
3
+ import {validateArrayArguments, validateIpV4} from "#commands/utils";
4
+
5
+ /**
6
+ * @param {string} commandName
7
+ * @param {Command} configCommand
8
+ * @param {Ref} configRef
9
+ * @returns {Command}
10
+ */
11
+ export const allowDenyCidrCommand = (commandName, configCommand, configRef) => {
12
+ const cmd = new Command(commandName)
13
+
14
+ cmd
15
+ .argument('[values...]', 'config value', validateArrayArguments(validateIpV4))
16
+ .option('-d --del [ip-address]', 'del values', validateArrayArguments(validateIpV4))
17
+ .option('-a --add [ip-address]', 'add values', validateArrayArguments(validateIpV4))
18
+ .action(addDelValuesAction(commandName, configRef))
19
+ .hook('preAction', (thisCommand, actionCommand) => {
20
+ const {del, add} = actionCommand.opts()
21
+
22
+ if (!actionCommand.args.length && !del && !add) {
23
+ // actionCommand.error("error: --add, --del oder values, einer muss mindestens gesetzt sein");
24
+ }
25
+ })
26
+
27
+ return cmd
28
+ }
@@ -0,0 +1,22 @@
1
+ import {Command} from "commander";
2
+
3
+ import {checkHost} from "#src/utils/checkFunctions";
4
+ import {deleteOption} from "#commands/Option/DeleteOption";
5
+ import {addGetDeleteValueAction} from "#commands/Action/addDelValuesAction";
6
+
7
+ export const hostCommand = (configRef) => {
8
+ const cmd = new Command('host')
9
+
10
+
11
+ cmd.description('default forweard host (127.0.0.1)')
12
+ .argument('[HOST]', 'füge eine kurze beschreibung ein', checkHost)
13
+ .hook('preAction', (thisCommand, actionCommand) => {
14
+ if (thisCommand.args.length && thisCommand.opts().del) {
15
+ actionCommand.error("error: wenn delete dann keine argumente");
16
+ }
17
+ })
18
+ .addOption(deleteOption())
19
+ .action(addGetDeleteValueAction('host', configRef))
20
+
21
+ return cmd
22
+ }
@@ -0,0 +1,20 @@
1
+ import {Command} from "commander";
2
+ import {checkPort} from "#src/utils/checkFunctions";
3
+ import {addGetDeleteValueAction} from "#commands/Action/addDelValuesAction";
4
+ import {deleteOption} from "#commands/Option/DeleteOption";
5
+
6
+ export const portCommand = (configRef) => {
7
+ const cmd = new Command('port')
8
+ cmd.description('default forweard port (80)')
9
+ .argument('[PORT]', 'set port for config', checkPort)
10
+ .action(addGetDeleteValueAction('port', configRef))
11
+ .addOption(deleteOption())
12
+
13
+ return cmd
14
+ }
15
+
16
+ //
17
+ // if (!config.isPublic(configKey)) {
18
+ // console.error('Ungültiger Konfigurationsdirektive')
19
+ // process.exit()
20
+ // }
@@ -0,0 +1,13 @@
1
+ export const argumentAliasResolver = () => {
2
+ let args = []
3
+ for (let i = 0; i < process.argv.length; i++) {
4
+ const arg = process.argv[i]
5
+ if (arg.charAt(0) === '@') {
6
+ args.push('--alias')
7
+ args.push(arg.substring(1))
8
+ } else {
9
+ args.push(arg)
10
+ }
11
+ }
12
+ return args
13
+ }
@@ -0,0 +1,53 @@
1
+ import {ipV4} from "#src/net/IPV4";
2
+
3
+ /**
4
+ * Binds arguments to a function.
5
+ * @param {Function} fn - The function to bind arguments to.
6
+ * @param {...any} args - The arguments to bind.
7
+ * @returns {Function} - The bound function.
8
+ */
9
+ export const bindArgs = (fn, ...args) => {
10
+ return fn.bind(null, ...args)
11
+ }
12
+
13
+ /**
14
+ * Validates a remote address against allowed and denied CIDR ranges.
15
+ * @param {string} remoteAddress - The remote address to check.
16
+ * @param {Object} options - The options containing allowCidr and denyCidr arrays.
17
+ * @param {ipCidr[]} options.allowCidr - Array of CIDR ranges that are allowed.
18
+ * @param {ipCidr[]} options.denyCidr - Array of CIDR ranges that are denied.
19
+ * @returns {boolean} - True if the address is valid, otherwise false.
20
+ */
21
+ export const isValidRemoteAddress = (() => {
22
+ const cache = {}
23
+
24
+ /**
25
+ * Checks if a remote address is within a given subnet.
26
+ * Uses a cache to store results for performance improvement.
27
+ * @param {string} remoteAddress - The remote address to check.
28
+ * @param {string} subnet - The CIDR subnet to check against.
29
+ * @returns {boolean} - True if the remote address is in the subnet, otherwise false.
30
+ */
31
+ const isInSubnet = (remoteAddress, subnet) => {
32
+ return cache[`${remoteAddress}#${subnet}`] ??= ipV4(remoteAddress).isInSubnet(subnet)
33
+ }
34
+
35
+ return (remoteAddress, {allowCidr, denyCidr}) => {
36
+ // Check against denied CIDR ranges
37
+ for (const deny of denyCidr) {
38
+ if (isInSubnet(remoteAddress, deny)) {
39
+ return false
40
+ }
41
+ }
42
+
43
+ // Check against allowed CIDR ranges
44
+ for (const allow of allowCidr) {
45
+ if (isInSubnet(remoteAddress, allow)) {
46
+ return true
47
+ }
48
+ }
49
+
50
+ // If there are no allowed CIDR ranges, allow the address
51
+ return allowCidr.length === 0
52
+ }
53
+ })();
@@ -0,0 +1,32 @@
1
+ import {Ref} from "#src/core/Ref";
2
+
3
+ /**
4
+ * Class representing a shared argument, extending Ref.
5
+ */
6
+ class SharedArg extends Ref {
7
+ /**
8
+ * Create a SharedArg instance.
9
+ * @param {*} value - The initial value for the shared argument.
10
+ */
11
+ constructor(value) {
12
+ super(value)
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Create a new SharedArg instance.
18
+ * @param {*} value - The initial value for the shared argument.
19
+ * @returns {SharedArg} - The created SharedArg instance.
20
+ */
21
+ export const sharedArg = (value) => {
22
+ return new SharedArg(value)
23
+ }
24
+
25
+ /**
26
+ * Check if a value is an instance of SharedArg.
27
+ * @param {SharedArg|*} value - The value to check.
28
+ * @returns {boolean} - True if the value is an instance of SharedArg, false otherwise.
29
+ */
30
+ export const isSharedArg = (value) => {
31
+ return value instanceof SharedArg
32
+ }
@@ -0,0 +1,96 @@
1
+ import {Command} from "commander";
2
+ import {checkIpV4Cidr} from "#src/utils/checkFunctions";
3
+
4
+ export const validateArrayArguments = (validationCallback) => {
5
+
6
+ return (value, previous) => {
7
+ const ref = {value}
8
+ validationCallback(ref)
9
+ previous ??= []
10
+ previous.push(ref.value)
11
+ return previous
12
+ }
13
+ }
14
+ export const validateIpV4 = (ref) => {
15
+ ref.value = checkIpV4Cidr(ref.value)
16
+ }
17
+
18
+ const extendedUsages = []
19
+
20
+ /**
21
+ * @param {Command} program
22
+ * @param {Command} command
23
+ */
24
+ export const extendUsage = (program, command) => {
25
+
26
+ extendedUsages.push({program, command})
27
+
28
+ }
29
+
30
+ export const addUsage = (program) => {
31
+
32
+ const help = program.createHelp()
33
+
34
+ for (const {program, command} of extendedUsages) {
35
+ const usage = help.commandUsage(command)
36
+ const before = program.usage()
37
+ program.usage(before + "\n" + ''.padEnd(7, ' ') + usage)
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Add examples to the command help text.
43
+ * @param {Command} program - The commander program instance.
44
+ */
45
+ export const addExamples = (program) => {
46
+
47
+ const usage = program.usage()
48
+ program.usage('')
49
+
50
+ const maxRowLength = {
51
+ length: 0
52
+ }
53
+
54
+ const rows = [].slice.call(program.helpInformation().split("\n").map(x => {
55
+ x = x.trim()
56
+ const rowLength = x.length
57
+ if (rowLength > maxRowLength.length) {
58
+ maxRowLength.length = rowLength
59
+ maxRowLength.row = x
60
+ }
61
+ return x
62
+ }).filter(Boolean), 2)
63
+ program.usage(usage)
64
+
65
+ const maxWhitespaceLength = {
66
+ length: 0
67
+ }
68
+
69
+ for (const match of maxRowLength.row.matchAll(/(\s)+/g)) {
70
+ const whiteSpacesCount = match[0].length
71
+ if (whiteSpacesCount > maxWhitespaceLength.length) {
72
+ maxWhitespaceLength.length = whiteSpacesCount
73
+ maxWhitespaceLength.index = match.index
74
+ }
75
+ }
76
+
77
+ const padLength = maxWhitespaceLength.index + maxWhitespaceLength.length - 6
78
+ program.addHelpText('after', "\nExamples:")
79
+
80
+ for (const {example, description} of examples) {
81
+ const whitespaces = ''.padStart(padLength - example.length, ' ')
82
+ program.addHelpText('after', ` tunli ${example}${whitespaces}${description}`)
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Adds an example to the list of examples.
88
+ * @param {string} example - The example command.
89
+ * @param {string} description - The description of the example.
90
+ */
91
+ export const addExample = (example, description) => {
92
+ examples.push({
93
+ example, description
94
+ })
95
+ }
96
+ const examples = []