• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gittrends-app / github-proxy-server / 21078838775

16 Jan 2026 07:47PM UTC coverage: 78.144% (-2.4%) from 80.499%
21078838775

push

github

hsborges
v12.0.0

133 of 184 branches covered (72.28%)

Branch coverage included in aggregate %.

246 of 301 relevant lines covered (81.73%)

93.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

8.62
/src/cli.ts
1
#!/usr/bin/env node
2
/* Author: Hudson S. Borges */
3
import EventEmitter from 'node:events';
4
import { pathToFileURL } from 'node:url';
5

6
import chalk from 'chalk';
7
import { Command, Option } from 'commander';
8
import consola from 'consola';
9
import ip from 'ip';
10
import isNil from 'lodash/isNil.js';
11
import isObjectLike from 'lodash/isObjectLike.js';
12
import omit from 'lodash/omit.js';
13
import omitBy from 'lodash/omitBy.js';
14

15
import packageJson from '../package.json' with { type: 'json' };
16
import { type CliOpts, concatTokens, createProxyServer, readTokensFile } from './server.js';
17

18
export function createCli(): Command {
19
  const program = new Command();
27✔
20

21
  return program
27✔
22
    .addOption(
23
      new Option('-p, --port [port]', 'Port to start the proxy server')
24
        .argParser(Number)
25
        .default(3000)
26
        .env('PORT')
27
    )
28
    .addOption(
29
      new Option('-t, --token [token]', 'GitHub token to be used')
30
        .argParser(concatTokens)
31
        .default([])
32
    )
33
    .addOption(
34
      new Option('--tokens [file]', 'File containing a list of tokens')
35
        .argParser(readTokensFile)
36
        .env('GPS_TOKENS_FILE')
37
    )
38
    .addOption(
39
      new Option('--request-timeout [timeout]', 'Request timeout (ms)')
40
        .argParser(Number)
41
        .default(30000)
42
        .env('GPS_REQUEST_TIMEOUT')
43
    )
44
    .addOption(
45
      new Option('--min-remaining <number>', 'Stop using token on a minimum of')
46
        .argParser(Number)
47
        .default(100)
48
        .env('GPS_MIN_REMAINING')
49
    )
50
    .addOption(new Option('--silent', 'Dont show requests outputs'))
51
    .addOption(
52
      new Option(
53
        '--no-override-authorization',
54
        'By default, the authorization header is overrided with a configured token'
55
      )
56
    )
57
    .addOption(
58
      new Option('--auth-username [username]', 'Proxy authentication username').env(
59
        'GPS_AUTH_USERNAME'
60
      )
61
    )
62
    .addOption(
63
      new Option('--auth-password [password]', 'Proxy authentication password').env(
64
        'GPS_AUTH_PASSWORD'
65
      )
66
    )
67
    .addOption(new Option('--no-status-monitor', 'Disable requests monitoring on /status'))
68
    .version(packageJson.version || '?', '-v, --version', 'output the current version')
27!
69
    .action(async (options) => {
70
      if (!options.token.length && !options.tokens?.length) {
×
71
        consola.info(`${program.helpInformation()}`);
×
72
        consola.error(`Arguments missing ("--token" or "--tokens" is mandatory).\n\n`);
×
73
        process.exit(1);
×
74
      }
75

76
      EventEmitter.defaultMaxListeners = Number.MAX_SAFE_INTEGER;
×
77

78
      const tokens = [...options.token, ...(options.tokens || [])].reduce(
×
79
        (memo: string[], token: string) => concatTokens(token, memo),
×
80
        []
81
      );
82

83
      const appOptions: CliOpts = {
×
84
        requestTimeout: options.requestTimeout,
85
        silent: options.silent,
86
        overrideAuthorization: options.overrideAuthorization,
87
        tokens: tokens,
88
        minRemaining: options.minRemaining,
89
        statusMonitor: options.statusMonitor,
90
        auth:
91
          options.authUsername && options.authPassword
×
92
            ? {
93
                username: options.authUsername,
94
                password: options.authPassword
95
              }
96
            : undefined
97
      };
98

99
      const app = createProxyServer(appOptions);
×
100

101
      app
×
102
        .on('log', (data) => process.stdout.write(data.toString()))
×
103
        .on('warn', consola.warn)
104
        .on('error', consola.error);
105

106
      const server = app.listen({ host: '0.0.0.0', port: options.port }, (error?: Error) => {
×
107
        if (error) {
×
108
          consola.error(error);
×
109
          process.exit(1);
×
110
        }
111

112
        const host = `http://${ip.address()}:${options.port}`;
×
113
        consola.success(
×
114
          `Proxy server running on ${host} (tokens: ${chalk.greenBright(tokens.length)})`
115
        );
116

117
        function formatObject(object: Record<string, unknown>): string {
118
          return Object.entries(omitBy(object, (value) => isNil(value)))
×
119
            .sort((a: [string, unknown], b: [string, unknown]) => (a[0] > b[0] ? 1 : -1))
×
120
            .map(
121
              ([k, v]) =>
122
                `${k}: ${
×
123
                  isObjectLike(v)
124
                    ? `{ ${formatObject(v as Record<string, unknown>)} }`
125
                    : chalk.greenBright(v)
126
                }`
127
            )
128
            .join(', ');
129
        }
130

131
        consola.success(
×
132
          `${chalk.bold('Options')}: %s`,
133
          formatObject(omit(appOptions, ['token', 'tokens']))
134
        );
135
      });
136

137
      const shutdown = async () => {
×
138
        server.close((err?: Error) => {
×
139
          if (err) {
×
140
            consola.error(err);
×
141
            process.exit(1);
×
142
          }
143

144
          consola.success('Server closed');
×
145
          process.exit(0);
×
146
        });
147
      };
148

149
      ['SIGTERM', 'SIGINT'].forEach((signal) => {
×
150
        process.on(signal, async () => {
×
151
          consola.info(`${signal} signal received: closing HTTP server`);
×
152
          await shutdown();
×
153
        });
154
      });
155
    });
156
}
157

158
// parse arguments from command line
159
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
1!
160
  createCli().parse(process.argv);
×
161
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc