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

gittrends-app / github-proxy-server / 20376377211

19 Dec 2025 04:41PM UTC coverage: 81.014% (-0.6%) from 81.606%
20376377211

push

github

hsborges
refactor: fix signals handling and remove tests from docker image build

132 of 144 branches covered (91.67%)

Branch coverage included in aggregate %.

0 of 7 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

427 of 546 relevant lines covered (78.21%)

70.88 hits per line

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

49.09
/src/cli.ts
1
#!/usr/bin/env node
1✔
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 {
1✔
19
  const program = new Command();
34✔
20

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

101
      EventEmitter.defaultMaxListeners = Number.MAX_SAFE_INTEGER;
×
102

103
      const tokens = [...options.token, ...(options.tokens || [])].reduce(
×
104
        (memo: string[], token: string) => concatTokens(token, memo),
×
105
        []
×
106
      );
×
107

108
      const appOptions: CliOpts = {
×
109
        requestTimeout: options.requestTimeout,
×
110
        silent: options.silent,
×
111
        overrideAuthorization: options.overrideAuthorization,
×
112
        tokens: tokens,
×
113
        clustering: options.clustering
×
114
          ? {
×
115
              host: options.clusteringHost,
×
116
              port: options.clusteringPort,
×
117
              db: options.clusteringDb
×
118
            }
×
119
          : undefined,
×
120
        minRemaining: options.minRemaining,
×
121
        statusMonitor: options.statusMonitor,
×
122
        auth:
×
123
          options.authUsername && options.authPassword
×
124
            ? {
×
125
                username: options.authUsername,
×
126
                password: options.authPassword
×
127
              }
×
128
            : undefined
×
129
      };
×
130

131
      const app = createProxyServer(appOptions);
×
132

133
      app
×
134
        .on('log', (data) => process.stdout.write(data.toString()))
×
135
        .on('warn', consola.warn)
×
136
        .on('error', consola.error);
×
137

138
      const server = app.listen({ host: '0.0.0.0', port: options.port }, (error?: Error) => {
×
139
        if (error) {
×
140
          consola.error(error);
×
141
          process.exit(1);
×
142
        }
×
143

144
        const host = `http://${ip.address()}:${options.port}`;
×
145
        consola.success(
×
146
          `Proxy server running on ${host} (tokens: ${chalk.greenBright(tokens.length)})`
×
147
        );
×
148

149
        function formatObject(object: Record<string, unknown>): string {
×
150
          return Object.entries(omitBy(object, (value) => isNil(value)))
×
151
            .sort((a: [string, unknown], b: [string, unknown]) => (a[0] > b[0] ? 1 : -1))
×
152
            .map(
×
153
              ([k, v]) =>
×
154
                `${k}: ${
×
155
                  isObjectLike(v)
×
156
                    ? `{ ${formatObject(v as Record<string, unknown>)} }`
×
157
                    : chalk.greenBright(v)
×
158
                }`
×
159
            )
×
160
            .join(', ');
×
161
        }
×
162

163
        consola.success(
×
164
          `${chalk.bold('Options')}: %s`,
×
165
          formatObject(omit(appOptions, ['token', 'tokens']))
×
166
        );
×
167
      });
×
168

NEW
169
      const shutdown = async () => {
×
170
        server.close((err?: Error) => {
×
171
          if (err) {
×
172
            consola.error(err);
×
173
            process.exit(1);
×
174
          }
×
175

176
          consola.success('Server closed');
×
177
          process.exit(0);
×
178
        });
×
NEW
179
      };
×
180

NEW
181
      ['SIGTERM', 'SIGINT'].forEach((signal) => {
×
NEW
182
        process.on(signal, async () => {
×
NEW
183
          consola.info(`${signal} signal received: closing HTTP server`);
×
NEW
184
          await shutdown();
×
NEW
185
        });
×
UNCOV
186
      });
×
187
    });
34✔
188
}
34✔
189

190
// parse arguments from command line
191
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
1!
192
  createCli().parse(process.argv);
×
193
}
×
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