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

keplergl / kepler.gl / 28970700046

08 Jul 2026 07:42PM UTC coverage: 56.11% (-0.04%) from 56.147%
28970700046

push

github

web-flow
chore: upgrade to React 19 (#3514)

* chore: upgrade to React 19

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* remove unused radius by zoom

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix linechart

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix transform-origin

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* upgrade react-router

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* bump styled components

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix react warnings

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* try to fix CI lint

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* try to fix CI lint

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* try to fix tests

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* try to fix tests

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix netlify builds

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix testS

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix testS

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix tests

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix tests

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* try to fix browser tests

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* try to fix netlify build

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* follow up

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* revert ts change

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* bump website

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fix swipe mode regression

Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>

* fixes for website

Signed-off-by: Ihor Dykhta <iho... (continued)

7736 of 16484 branches covered (46.93%)

Branch coverage included in aggregate %.

17 of 90 new or added lines in 12 files covered. (18.89%)

142 existing lines in 6 files now uncovered.

15540 of 24999 relevant lines covered (62.16%)

73.81 hits per line

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

14.29
/src/utils/src/export-map-html.ts
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
import {EXPORT_HTML_MAP_MODES} from '@kepler.gl/constants';
5

6
/**
7
 * This method is used to create an html file which will inlcude kepler and map data
8
 * @param {Object} options Object that collects all necessary data to  create the html file
9
 * @param {string} options.mapboxApiAccessToken Mapbox token used to fetch mapbox tiles
10
 * @param {Array<Object>} options.datasets Data to include in the map
11
 * @param {Object} options.config this object will contain the full kepler.gl instance configuration {mapState, mapStyle, visState}
12
 * @param {string} version which version of Kepler.gl to load.
13
 */
14
// TODO: revert to KEPLER_GL_VERSION once the React 19 release is published to npm with a UMD bundle
15
export const exportMapToHTML = (options, version = '3.3.0-alpha.1') => {
17!
UNCOV
16
  return `
×
17
    <!DOCTYPE html>
18
    <html>
19
      <head>
20
        <meta charset="UTF-8"/>
21
        <title>Kepler.gl embedded map</title>
22

23
        <!--Uber Font-->
24
        <link rel="stylesheet" href="https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/uber-fonts/4.0.0/superfine.css">
25

26
        <!--Kepler css-->
27
        <link href="https://unpkg.com/kepler.gl@${version}/umd/keplergl.min.css" rel="stylesheet">
28

29
        <!--MapBox css-->
30
        <link href="https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css" rel="stylesheet">
31
        <link href="https://unpkg.com/maplibre-gl@^3/dist/maplibre-gl.css" rel="stylesheet">
32

33
        <!-— facebook open graph tags -->
34
        <meta property="og:url" content="http://kepler.gl/" />
35
        <meta property="og:title" content="Large-scale WebGL-powered Geospatial Data Visualization Tool" />
36
        <meta property="og:description" content="Kepler.gl is a powerful web-based geospatial data analysis tool. Built on a high performance rendering engine and designed for large-scale data sets." />
37
        <meta property="og:site_name" content="kepler.gl" />
38
        <meta property="og:image" content="https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/kepler.gl-meta-tag.png" />
39
        <meta property="og:image:type" content="image/png" />
40
        <meta property="og:image:width" content="800" />
41
        <meta property="og:image:height" content="800" />
42

43
        <!-— twitter card tags -->
44
        <meta name="twitter:card" content="summary_large_image">
45
        <meta name="twitter:site" content="@openjsf">
46
        <meta name="twitter:creator" content="@openjsf">
47
        <meta name="twitter:title" content="Large-scale WebGL-powered Geospatial Data Visualization Tool">
48
        <meta name="twitter:description" content="Kepler.gl is a powerful web-based geospatial data analysis tool. Built on a high performance rendering engine and designed for large-scale data sets.">
49
        <meta name="twitter:image" content="https://d1a3f4spazzrp4.cloudfront.net/kepler.gl/kepler.gl-meta-tag.png" />
50

51
        <!-- Load React/Redux -->
52
        <script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js" crossorigin></script>
53
        <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js" crossorigin></script>
54
        <script src="https://unpkg.com/redux@4.2.1/dist/redux.js" crossorigin></script>
55
        <script src="https://unpkg.com/react-redux@8.1.2/dist/react-redux.min.js" crossorigin></script>
56
        <script src="https://unpkg.com/styled-components@6.1.8/dist/styled-components.min.js" crossorigin></script>
57

58
        <!-- Load Kepler.gl -->
59
        <script src="https://unpkg.com/kepler.gl@${version}/umd/keplergl.min.js" crossorigin></script>
60

61
        <style type="text/css">
62
          body {margin: 0; padding: 0; overflow: hidden;}
63
        </style>
64

65
        <!--MapBox token-->
66
        <!--
67
          SECURITY NOTE: Your Mapbox access token is embedded below in plain text.
68
          Anyone with access to this HTML file can see and use this token.
69
          Consider using a scoped token with URL restrictions to limit misuse.
70
          See: https://docs.mapbox.com/accounts/guides/tokens/#url-restrictions
71
        -->
72
        <script>
73
          /**
74
           * Provide your MapBox Token
75
           **/
76
          const MAPBOX_TOKEN = '${options.mapboxApiAccessToken || 'PROVIDE_MAPBOX_TOKEN'}';
×
77
          const WARNING_MESSAGE = 'Please Provide a Mapbox Token in order to use Kepler.gl. Edit this file and fill out MAPBOX_TOKEN with your access key';
78
        </script>
79

80
        <!-- GA: Delete this as you wish, However to pat ourselves on the back, we only track anonymous pageview to understand how many people are using kepler.gl. -->
81
        <script>
82
          (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
83
          (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
84
          m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
85
          })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
86
          ga('create', 'UA-64694404-19', {
87
            'storage': 'none',
88
            'clientId': localStorage.getItem('ga:clientId')
89
          });
90
          ga(function(tracker) {
91
              localStorage.setItem('ga:clientId', tracker.get('clientId'));
92
          });
93
          ga('set', 'checkProtocolTask', null); // Disable file protocol checking.
94
          ga('set', 'checkStorageTask', null); // Disable cookie storage checking.
95
          ga('set', 'historyImportTask', null); // Disable history checking (requires reading from cookies).
96
          ga('set', 'page', 'keplergl-html');
97
          ga('send', 'pageview');
98
        </script>
99
      </head>
100
      <body>
101
        <!-- We will put our React component inside this div. -->
102
        <div id="app">
103
          <!-- Kepler.gl map will be placed here-->
104
        </div>
105

106
        <!-- Load our React component. -->
107
        <script>
108
          /* Validate Mapbox Token */
109
          if ((MAPBOX_TOKEN || '') === '' || MAPBOX_TOKEN === 'PROVIDE_MAPBOX_TOKEN') {
110
            alert(WARNING_MESSAGE);
111
          }
112

113
          /** STORE **/
114
          const reducers = (function createReducers(redux, keplerGl) {
115
            return redux.combineReducers({
116
              // mount keplerGl reducer
117
              keplerGl: keplerGl.keplerGlReducer.initialState({
118
                uiState: {
119
                  readOnly: ${options.mode === EXPORT_HTML_MAP_MODES.READ},
120
                  currentModal: null
121
                }
122
              })
123
            });
124
          }(Redux, KeplerGl));
125

126
          const middleWares = (function createMiddlewares(keplerGl) {
127
            return keplerGl.enhanceReduxMiddleware([
128
              // Add other middlewares here
129
            ]);
130
          }(KeplerGl));
131

132
          const enhancers = (function craeteEnhancers(redux, middles) {
133
            return redux.applyMiddleware(...middles);
134
          }(Redux, middleWares));
135

136
          const store = (function createStore(redux, enhancers) {
137
            const initialState = {};
138

139
            return redux.createStore(
140
              reducers,
141
              initialState,
142
              redux.compose(enhancers)
143
            );
144
          }(Redux, enhancers));
145
          /** END STORE **/
146

147
          /** COMPONENTS **/
148
          /** Custom map control factory to add Effects button + panel **/
149
          var CustomMapControlFactory = (function createCustomMapControl(react, keplerGl) {
150
            var EffectControlFactory = keplerGl.EffectControlFactory;
151
            var EffectManagerFactory = keplerGl.EffectManagerFactory;
152
            var MapControlFactory = keplerGl.MapControlFactory;
153

154
            if (!EffectControlFactory || !EffectManagerFactory || !MapControlFactory) {
155
              console.warn('kepler.gl: Effect factories not available, skipping effect control injection');
156
              return null;
157
            }
158

159
            function EffectMapControlFactory(EffectControl, EffectManager) {
160
              var args = Array.prototype.slice.call(arguments, 2);
161
              var MapControl = MapControlFactory.apply(null, args);
162
              var actionComponents = (MapControl.defaultActionComponents || []).concat([EffectControl]);
163

164
              var EffectMapControl = function EffectMapControl(props) {
165
                var showEffects = Boolean(props.mapControls && props.mapControls.effect && props.mapControls.effect.active);
166
                return react.createElement(
167
                  'div',
168
                  {style: {
169
                    position: 'absolute',
170
                    display: 'flex',
171
                    top: (props.top || 0) + 'px',
172
                    right: 0,
173
                    zIndex: 1,
174
                    maxHeight: '100%',
175
                    pointerEvents: 'none'
176
                  }},
177
                  react.createElement(
178
                    'div',
179
                    {style: {position: 'relative', pointerEvents: 'all'}},
180
                    react.createElement(MapControl, Object.assign({}, props, {top: 0, actionComponents: actionComponents}))
181
                  ),
182
                  showEffects
183
                    ? react.createElement('div', {
184
                        style: {
185
                          maxHeight: '100%',
186
                          overflow: 'hidden',
187
                          display: 'flex',
188
                          flexDirection: 'column',
189
                          pointerEvents: 'all',
190
                          marginTop: '10px'
191
                        }
192
                      }, react.createElement(EffectManager, null))
193
                    : null
194
                );
195
              };
196

197
              return EffectMapControl;
198
            }
199
            EffectMapControlFactory.deps = [EffectControlFactory, EffectManagerFactory].concat(MapControlFactory.deps);
200

201
            return [MapControlFactory, EffectMapControlFactory];
202
          }(React, KeplerGl));
203

204
          var KeplerGlComponent = CustomMapControlFactory
205
            ? KeplerGl.injectComponents([CustomMapControlFactory])
206
            : KeplerGl.KeplerGl;
207

208
          var KeplerElement = (function makeKeplerElement(react, keplerGl, mapboxToken) {
209
            var LogoSvg = function LogoSvg() {
210
              return react.createElement(
211
                "div",
212
                { className: "logo-container", style: {position: 'fixed', zIndex: 10000, padding: '4px'} },
213
                  react.createElement(
214
                    "svg",
215
                    {
216
                      className: "kepler_gl__logo",
217
                      width: "107px",
218
                      height: "21px",
219
                      viewBox: "0 0 124 24"
220
                    },
221
                    react.createElement(
222
                      "g",
223
                      { transform: "translate(13.500000, 13.500000) rotate(45.000000) translate(-13.500000, -13.500000) translate(4.000000, 4.000000)" },
224
                      react.createElement("rect", { x: "0", y: "6", transform: "matrix(2.535181e-06 1 -1 2.535181e-06 18.1107 6.0369)", fill: "#535C6C", width: "12.1", height: "12.1" }),
225
                      react.createElement("rect", { x: "6", y: "0", transform: "matrix(2.535182e-06 1 -1 2.535182e-06 18.1107 -6.0369)", fill:"#1FBAD6", width: "12.1", height: "12.1" })
226
                    ),
227
                    react.createElement(
228
                      "g",
229
                      {},
230
                      react.createElement("path", { fill:"#1FBAD6", d: "M39,8.7h2.2l-2.8,4.2l2.9,5.1H39l-2.4-4.2h-1.3V18h-2V5l2-0.1v7.3h1.3L39,8.7z" }),
231
                      react.createElement("path", { fill:"#1FBAD6", d: "M42.4,13.3c0-1.5,0.4-2.7,1.1-3.5s1.8-1.2,3.1-1.2c1.3,0,2.2,0.4,2.8,1.1c0.6,0.7,0.9,1.8,0.9,3.3 c0,0.4,0,0.8,0,1.1h-5.8c0,1.6,0.8,2.4,2.4,2.4c1,0,2-0.2,2.9-0.6l0.2,1.7c-0.4,0.2-0.9,0.4-1.4,0.5s-1.1,0.2-1.7,0.2 c-1.5,0-2.6-0.4-3.3-1.2C42.8,16.1,42.4,14.9,42.4,13.3z M46.6,10.1c-0.7,0-1.2,0.2-1.5,0.5c-0.4,0.4-0.6,0.9-0.6,1.7h4 c0-0.8-0.2-1.4-0.5-1.7S47.2,10.1,46.6,10.1z" }),
232
                      react.createElement("path", { fill:"#1FBAD6", d: "M57.1,18.2c-1,0-1.8-0.3-2.3-0.9l0,0l0,1.3v2.5h-2V8.7h1.5l0.3,0.9h0c0.3-0.3,0.7-0.6,1.2-0.7 c0.4-0.2,0.9-0.3,1.4-0.3c1.2,0,2.1,0.4,2.7,1.1c0.6,0.7,0.9,2,0.9,3.7c0,1.6-0.3,2.8-1,3.7C59.2,17.8,58.3,18.2,57.1,18.2z M56.7,10.3c-0.4,0-0.8,0.1-1.1,0.2c-0.3,0.2-0.6,0.4-0.8,0.7v4.3c0.2,0.3,0.4,0.5,0.7,0.7c0.3,0.2,0.7,0.3,1.1,0.3 c0.7,0,1.2-0.2,1.6-0.7c0.4-0.5,0.5-1.3,0.5-2.5c0-0.8-0.1-1.4-0.2-1.8s-0.4-0.7-0.7-0.9C57.6,10.4,57.2,10.3,56.7,10.3z" }),
233
                      react.createElement("path", { fill:"#1FBAD6", d: "M63.2,16V5l2-0.1v10.8c0,0.3,0.1,0.5,0.2,0.6c0.1,0.1,0.3,0.2,0.6,0.2c0.3,0,0.6,0,0.9-0.1V18 c-0.4,0.1-1,0.2-1.6,0.2c-0.8,0-1.3-0.2-1.7-0.5S63.2,16.8,63.2,16z" }),
234
                      react.createElement("path", { fill:"#1FBAD6", d: "M68.2,13.3c0-1.5,0.4-2.7,1.1-3.5c0.7-0.8,1.8-1.2,3.1-1.2c1.3,0,2.2,0.4,2.8,1.1c0.6,0.7,0.9,1.8,0.9,3.3 c0,0.4,0,0.8,0,1.1h-5.8c0,1.6,0.8,2.4,2.4,2.4c1,0,2-0.2,2.9-0.6l0.2,1.7c-0.4,0.2-0.9,0.4-1.4,0.5s-1.1,0.2-1.7,0.2 c-1.5,0-2.6-0.4-3.3-1.2C68.6,16.1,68.2,14.9,68.2,13.3z M72.4,10.1c-0.7,0-1.2,0.2-1.5,0.5c-0.4,0.4-0.6,0.9-0.6,1.7h4 c0-0.8-0.2-1.4-0.5-1.7S73,10.1,72.4,10.1z" }),
235
                      react.createElement("path", { fill:"#1FBAD6", d: "M80.2,8.7l0.1,1.7h0c0.3-0.6,0.7-1.1,1.1-1.4c0.4-0.3,1-0.5,1.6-0.5c0.4,0,0.7,0,1,0.1l-0.1,2 c-0.3-0.1-0.7-0.2-1-0.2c-0.7,0-1.3,0.3-1.7,0.8c-0.4,0.5-0.7,1.2-0.7,2.1V18h-2V8.7H80.2z" }),
236
                      react.createElement("path", { fill:"#1FBAD6", d: "M83.8,17c0-0.8,0.4-1.2,1.2-1.2c0.8,0,1.2,0.4,1.2,1.2c0,0.8-0.4,1.1-1.2,1.1C84.2,18.2,83.8,17.8,83.8,17z" }),
237
                      react.createElement("path", { fill:"#1FBAD6", d: "M88.5,18.7c0-0.8,0.4-1.4,1.2-1.8c-0.6-0.3-0.9-0.8-0.9-1.5c0-0.7,0.4-1.2,1.1-1.6c-0.3-0.3-0.6-0.6-0.7-0.9 c-0.2-0.4-0.2-0.8-0.2-1.3c0-1,0.3-1.8,0.9-2.3c0.6-0.5,1.6-0.8,2.8-0.8c0.5,0,1,0,1.4,0.1c0.4,0.1,0.8,0.2,1.1,0.4l2.4-0.2v1.5 h-1.5c0.2,0.4,0.2,0.8,0.2,1.3c0,1-0.3,1.7-0.9,2.2s-1.5,0.8-2.7,0.8c-0.7,0-1.2-0.1-1.6-0.2c-0.1,0.1-0.2,0.2-0.3,0.3 c-0.1,0.1-0.1,0.2-0.1,0.4c0,0.2,0.1,0.3,0.2,0.4c0.1,0.1,0.3,0.2,0.6,0.2l2.7,0.2c1,0.1,1.7,0.3,2.2,0.6c0.5,0.3,0.8,0.9,0.8,1.7 c0,0.6-0.2,1.1-0.5,1.5c-0.4,0.4-0.9,0.8-1.5,1c-0.7,0.2-1.5,0.4-2.4,0.4c-1.3,0-2.3-0.2-3-0.6C88.8,20.1,88.5,19.5,88.5,18.7z M95.1,18.4c0-0.3-0.1-0.5-0.3-0.7s-0.6-0.2-1.1-0.3l-2.7-0.3c-0.2,0.1-0.4,0.3-0.5,0.5c-0.1,0.2-0.2,0.4-0.2,0.6 c0,0.4,0.2,0.8,0.5,1c0.4,0.2,1,0.3,1.8,0.3C94.2,19.5,95.1,19.2,95.1,18.4z M94.3,11.5c0-0.6-0.1-1-0.4-1.2 c-0.3-0.2-0.7-0.3-1.3-0.3c-0.7,0-1.1,0.1-1.4,0.3c-0.3,0.2-0.4,0.6-0.4,1.2s0.1,1,0.4,1.2c0.3,0.2,0.7,0.3,1.4,0.3 c0.6,0,1.1-0.1,1.3-0.4S94.3,12,94.3,11.5z" }),
238
                      react.createElement("path", { fill:"#1FBAD6", d: "M99.4,16V5l2-0.1v10.8c0,0.3,0.1,0.5,0.2,0.6c0.1,0.1,0.3,0.2,0.6,0.2c0.3,0,0.6,0,0.9-0.1V18 c-0.4,0.1-1,0.2-1.6,0.2c-0.8,0-1.3-0.2-1.7-0.5S99.4,16.8,99.4,16z" })
239
                    )
240
                  )
241
                );
242
              };
243

244
            return function App() {
245
              var rootElm = react.useRef(null);
246
              var _useState = react.useState({
247
                width: window.innerWidth,
248
                height: window.innerHeight
249
              });
250
              var windowDimension = _useState[0];
251
              var setDimension = _useState[1];
252
              react.useEffect(function sideEffect(){
253
                function handleResize() {
254
                  setDimension({width: window.innerWidth, height: window.innerHeight});
255
                };
256
                window.addEventListener('resize', handleResize);
257
                return function() {window.removeEventListener('resize', handleResize);};
258
              }, []);
259
              return react.createElement(
260
                'div',
261
                {style: {position: 'absolute', left: 0, width: '100vw', height: '100vh'}},
262
                ${options.mode === EXPORT_HTML_MAP_MODES.READ ? 'LogoSvg(),' : ''}
×
263
                react.createElement(KeplerGlComponent, {
264
                  mapboxApiAccessToken: mapboxToken,
265
                  id: "map",
266
                  width: windowDimension.width,
267
                  height: windowDimension.height
268
                })
269
              )
270
            }
271
          }(React, KeplerGl, MAPBOX_TOKEN));
272

273
          const app = (function createReactReduxProvider(react, reactRedux, KeplerElement) {
274
            return react.createElement(
275
              reactRedux.Provider,
276
              {store},
277
              react.createElement(KeplerElement, null)
278
            )
279
          }(React, ReactRedux, KeplerElement));
280
          /** END COMPONENTS **/
281

282
          /** Render **/
283
          (function render(react, reactDOM, app) {
284
            const container = document.getElementById('app');
285
            const root = reactDOM.createRoot(container);
286
            root.render(app);
287
          }(React, ReactDOM, app));
288
        </script>
289
        <!-- The next script will show how to interact directly with Kepler map store -->
290
        <script>
291
          /**
292
           * Customize map.
293
           * In the following section you can use the store object to dispatch Kepler.gl actions
294
           * to add new data and customize behavior
295
           */
296
          (function customize(keplerGl, store) {
297
            const datasets = ${JSON.stringify(options.datasets)};
298
            const config = ${JSON.stringify(options.config)};
299

300
            const loadedData = keplerGl.KeplerGlSchema.load(
301
              datasets,
302
              config
303
            );
304

305
            // For some reason Kepler overwrites the config without extra wait time
306
            window.setTimeout(() => {
307
              store.dispatch(
308
                keplerGl.addDataToMap({
309
                  datasets: loadedData.datasets,
310
                  config: loadedData.config,
311
                  options: {
312
                    centerMap: false,
313
                  },
314
                })
315
              );
316
            }, 500);
317
          }(KeplerGl, store))
318
        </script>
319
      </body>
320
    </html>
321
  `;
322
};
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