@tryghost/prometheus-metrics 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2013-2025 Ghost Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # Metrics Server
2
+
3
+ A standalone server for exporting prometheus metrics from Ghost
4
+
5
+
6
+ ## Usage
7
+
8
+
9
+ ## Develop
10
+
11
+ This is a monorepo package.
12
+
13
+ Follow the instructions for the top-level repo.
14
+ 1. `git clone` this repo & `cd` into it as usual
15
+ 2. Run `yarn` to install top-level dependencies.
16
+
17
+
18
+
19
+ ## Test
20
+
21
+ - `yarn lint` run just eslint
22
+ - `yarn test` run lint and tests
23
+
@@ -0,0 +1,24 @@
1
+ import express from 'express';
2
+ import stoppable from 'stoppable';
3
+ type ServerConfig = {
4
+ host: string;
5
+ port: number;
6
+ };
7
+ export declare class MetricsServer {
8
+ private serverConfig;
9
+ private handler;
10
+ private app;
11
+ private httpServer;
12
+ private isShuttingDown;
13
+ constructor({ serverConfig, handler }: {
14
+ serverConfig: ServerConfig;
15
+ handler: express.Handler;
16
+ });
17
+ start(): Promise<{
18
+ app: express.Application;
19
+ httpServer: stoppable.StoppableServer;
20
+ }>;
21
+ stop(): Promise<void>;
22
+ shutdown(): Promise<void>;
23
+ }
24
+ export {};
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MetricsServer = void 0;
7
+ const debug_1 = __importDefault(require("@tryghost/debug"));
8
+ const express_1 = __importDefault(require("express"));
9
+ const stoppable_1 = __importDefault(require("stoppable"));
10
+ const debug = (0, debug_1.default)('metrics-server');
11
+ class MetricsServer {
12
+ serverConfig;
13
+ handler;
14
+ app;
15
+ httpServer;
16
+ isShuttingDown;
17
+ constructor({ serverConfig, handler }) {
18
+ // initialize local variables
19
+ this.serverConfig = serverConfig;
20
+ this.handler = handler;
21
+ this.app = null;
22
+ this.httpServer = null;
23
+ this.isShuttingDown = false;
24
+ }
25
+ async start() {
26
+ // start the server
27
+ debug('Starting metrics server');
28
+ this.app = (0, express_1.default)();
29
+ this.app.get('/metrics', this.handler);
30
+ const httpServer = this.app.listen(this.serverConfig.port, this.serverConfig.host, () => {
31
+ debug(`Metrics server listening at ${this.serverConfig.host}:${this.serverConfig.port}`);
32
+ });
33
+ this.httpServer = (0, stoppable_1.default)(httpServer, 0);
34
+ process.on('SIGINT', () => this.shutdown());
35
+ process.on('SIGTERM', () => this.shutdown());
36
+ return { app: this.app, httpServer: this.httpServer };
37
+ }
38
+ async stop() {
39
+ // stop the server
40
+ debug('Stopping metrics server');
41
+ if (this.httpServer && this.httpServer.listening) {
42
+ await this.httpServer.stop();
43
+ }
44
+ }
45
+ async shutdown() {
46
+ if (this.isShuttingDown) {
47
+ return;
48
+ }
49
+ this.isShuttingDown = true;
50
+ await this.stop();
51
+ this.isShuttingDown = false;
52
+ }
53
+ }
54
+ exports.MetricsServer = MetricsServer;
@@ -0,0 +1,148 @@
1
+ import { Request, Response } from 'express';
2
+ import client from 'prom-client';
3
+ import type { Metric, MetricObjectWithValues, MetricValue } from 'prom-client';
4
+ import type { Knex } from 'knex';
5
+ type PrometheusClientConfig = {
6
+ register?: client.Registry;
7
+ pushgateway?: {
8
+ enabled: boolean;
9
+ url?: string;
10
+ interval?: number;
11
+ jobName?: string;
12
+ };
13
+ };
14
+ /**
15
+ * A client for exporting metrics to Prometheus, based on prom-client
16
+ */
17
+ export declare class PrometheusClient {
18
+ /**
19
+ * Creates a new PrometheusClient instance
20
+ * @param prometheusConfig - The configuration for the PrometheusClient
21
+ */
22
+ constructor(prometheusConfig?: PrometheusClientConfig, logger?: any);
23
+ client: typeof client;
24
+ gateway: client.Pushgateway<client.RegistryContentType> | undefined;
25
+ queries: Map<string, () => void>;
26
+ acquires: Map<number, () => void>;
27
+ creates: Map<number, () => void>;
28
+ private config;
29
+ private prefix;
30
+ private pushInterval;
31
+ private pushRetries;
32
+ private logger;
33
+ /**
34
+ * Initializes the prometheus client, setting up the pushgateway if enabled
35
+ */
36
+ init(): void;
37
+ /**
38
+ * Pushes metrics to the pushgateway, if enabled
39
+ */
40
+ pushMetrics(): Promise<void>;
41
+ /**
42
+ * Shuts down the prometheus client cleanly
43
+ */
44
+ stop(): void;
45
+ /**
46
+ * Tells prom-client to collect default metrics
47
+ * Only called once on init
48
+ */
49
+ collectDefaultMetrics(): void;
50
+ /**
51
+ * Handles metrics requests to serve the /metrics endpoint
52
+ * @param req - The request object
53
+ * @param res - The response object
54
+ */
55
+ handleMetricsRequest(req: Request, res: Response): Promise<void>;
56
+ /**
57
+ * Returns the metrics from the registry as a string
58
+ */
59
+ getMetrics(): Promise<string>;
60
+ /**
61
+ * Returns the metrics from the registry as a JSON object
62
+ *
63
+ * Particularly useful for testing
64
+ */
65
+ getMetricsAsJSON(): Promise<object[]>;
66
+ getMetricsAsArray(): Promise<object[]>;
67
+ /**
68
+ * Returns the content type for the metrics
69
+ */
70
+ getContentType(): "text/plain; version=0.0.4; charset=utf-8";
71
+ /**
72
+ * Returns a single metric from the registry
73
+ * @param name - The name of the metric
74
+ * @returns The metric
75
+ */
76
+ getMetric(name: string): Metric | undefined;
77
+ /**
78
+ * Returns the metric object of a single metric, if it exists
79
+ * @param name - The name of the metric
80
+ * @returns The values of the metric
81
+ */
82
+ getMetricObject(name: string): Promise<MetricObjectWithValues<MetricValue<string>> | undefined>;
83
+ getMetricValues(name: string): Promise<MetricValue<string>[] | undefined>;
84
+ /**
85
+ *
86
+ */
87
+ /**
88
+ * Registers a counter metric
89
+ * @param name - The name of the metric
90
+ * @param help - The help text for the metric
91
+ * @param labelNames - The names of the labels for the metric
92
+ * @returns The counter metric
93
+ */
94
+ registerCounter({ name, help, labelNames }: {
95
+ name: string;
96
+ help: string;
97
+ labelNames?: string[];
98
+ }): client.Counter;
99
+ /**
100
+ * Registers a gauge metric
101
+ * @param name - The name of the metric
102
+ * @param help - The help text for the metric
103
+ * @param collect - The collect function to use for the gauge
104
+ * @returns The gauge metric
105
+ */
106
+ registerGauge({ name, help, collect }: {
107
+ name: string;
108
+ help: string;
109
+ collect?: () => void;
110
+ }): client.Gauge;
111
+ /**
112
+ * Registers a summary metric
113
+ * @param name - The name of the metric
114
+ * @param help - The help text for the metric
115
+ * @param percentiles - The percentiles to calculate for the summary
116
+ * @param collect - The collect function to use for the summary
117
+ * @returns The summary metric
118
+ */
119
+ registerSummary({ name, help, percentiles, maxAgeSeconds, ageBuckets, pruneAgedBuckets, collect }: {
120
+ name: string;
121
+ help: string;
122
+ percentiles?: number[];
123
+ maxAgeSeconds?: number;
124
+ ageBuckets?: number;
125
+ pruneAgedBuckets?: boolean;
126
+ collect?: () => void;
127
+ }): client.Summary;
128
+ /**
129
+ * Registers a histogram metric
130
+ * @param name - The name of the metric
131
+ * @param help - The help text for the metric
132
+ * @param buckets - The buckets to calculate for the histogram
133
+ * @param collect - The collect function to use for the histogram
134
+ * @returns The histogram metric
135
+ */
136
+ registerHistogram({ name, help, buckets }: {
137
+ name: string;
138
+ help: string;
139
+ buckets: number[];
140
+ collect?: () => void;
141
+ }): client.Histogram;
142
+ /**
143
+ * Instruments the knex connection pool and queries
144
+ * @param knexInstance - The knex instance
145
+ */
146
+ instrumentKnex(knexInstance: Knex): void;
147
+ }
148
+ export {};
@@ -0,0 +1,349 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PrometheusClient = void 0;
7
+ const http_1 = __importDefault(require("http"));
8
+ const prom_client_1 = __importDefault(require("prom-client"));
9
+ const logging_1 = __importDefault(require("@tryghost/logging"));
10
+ /**
11
+ * A client for exporting metrics to Prometheus, based on prom-client
12
+ */
13
+ class PrometheusClient {
14
+ /**
15
+ * Creates a new PrometheusClient instance
16
+ * @param prometheusConfig - The configuration for the PrometheusClient
17
+ */
18
+ constructor(prometheusConfig = {}, logger = logging_1.default) {
19
+ this.config = prometheusConfig;
20
+ this.client = prom_client_1.default;
21
+ this.prefix = 'ghost_';
22
+ this.logger = logger;
23
+ }
24
+ client;
25
+ gateway; // public for testing
26
+ queries = new Map();
27
+ acquires = new Map();
28
+ creates = new Map();
29
+ config;
30
+ prefix;
31
+ pushInterval;
32
+ pushRetries = 0;
33
+ logger;
34
+ /**
35
+ * Initializes the prometheus client, setting up the pushgateway if enabled
36
+ */
37
+ init() {
38
+ this.collectDefaultMetrics();
39
+ if (this.config.pushgateway?.enabled) {
40
+ const gatewayUrl = this.config.pushgateway.url || 'http://localhost:9091';
41
+ const interval = this.config.pushgateway.interval || 15000;
42
+ this.gateway = new prom_client_1.default.Pushgateway(gatewayUrl, {
43
+ timeout: 5000,
44
+ agent: new http_1.default.Agent({
45
+ keepAlive: true,
46
+ maxSockets: 1
47
+ })
48
+ });
49
+ this.pushMetrics();
50
+ this.pushInterval = setInterval(() => {
51
+ this.pushMetrics();
52
+ }, interval);
53
+ }
54
+ }
55
+ /**
56
+ * Pushes metrics to the pushgateway, if enabled
57
+ */
58
+ async pushMetrics() {
59
+ if (this.pushRetries >= 3) {
60
+ this.logger.error('Failed to push metrics to pushgateway 3 times in a row, giving up');
61
+ this.stop();
62
+ return;
63
+ }
64
+ if (this.config.pushgateway?.enabled && this.gateway) {
65
+ const jobName = this.config.pushgateway?.jobName || 'ghost';
66
+ try {
67
+ await this.gateway.pushAdd({ jobName });
68
+ this.logger.debug('Metrics pushed to pushgateway - jobName: ', jobName);
69
+ this.pushRetries = 0;
70
+ }
71
+ catch (err) {
72
+ let error;
73
+ if (typeof err === 'object' && err !== null && 'code' in err) {
74
+ error = 'Error pushing metrics to pushgateway: ' + err.code;
75
+ }
76
+ else {
77
+ error = 'Error pushing metrics to pushgateway: Unknown error';
78
+ }
79
+ this.logger.error(error);
80
+ this.pushRetries = this.pushRetries + 1;
81
+ }
82
+ }
83
+ }
84
+ /**
85
+ * Shuts down the prometheus client cleanly
86
+ */
87
+ stop() {
88
+ // Clear the push interval
89
+ if (this.pushInterval) {
90
+ clearInterval(this.pushInterval);
91
+ }
92
+ }
93
+ /**
94
+ * Tells prom-client to collect default metrics
95
+ * Only called once on init
96
+ */
97
+ collectDefaultMetrics() {
98
+ this.client.collectDefaultMetrics({ prefix: this.prefix });
99
+ }
100
+ /**
101
+ * Handles metrics requests to serve the /metrics endpoint
102
+ * @param req - The request object
103
+ * @param res - The response object
104
+ */
105
+ async handleMetricsRequest(req, res) {
106
+ try {
107
+ res.set('Content-Type', this.getContentType());
108
+ res.end(await this.getMetrics());
109
+ }
110
+ catch (err) {
111
+ if (err instanceof Error && err.message) {
112
+ res.status(500).end(err.message);
113
+ }
114
+ else {
115
+ res.status(500).end('Unknown error');
116
+ }
117
+ }
118
+ }
119
+ /**
120
+ * Returns the metrics from the registry as a string
121
+ */
122
+ async getMetrics() {
123
+ return this.client.register.metrics();
124
+ }
125
+ /**
126
+ * Returns the metrics from the registry as a JSON object
127
+ *
128
+ * Particularly useful for testing
129
+ */
130
+ async getMetricsAsJSON() {
131
+ return this.client.register.getMetricsAsJSON();
132
+ }
133
+ async getMetricsAsArray() {
134
+ return this.client.register.getMetricsAsArray();
135
+ }
136
+ /**
137
+ * Returns the content type for the metrics
138
+ */
139
+ getContentType() {
140
+ return this.client.register.contentType;
141
+ }
142
+ /**
143
+ * Returns a single metric from the registry
144
+ * @param name - The name of the metric
145
+ * @returns The metric
146
+ */
147
+ getMetric(name) {
148
+ if (!name.startsWith(this.prefix)) {
149
+ name = `${this.prefix}${name}`;
150
+ }
151
+ return this.client.register.getSingleMetric(name);
152
+ }
153
+ /**
154
+ * Returns the metric object of a single metric, if it exists
155
+ * @param name - The name of the metric
156
+ * @returns The values of the metric
157
+ */
158
+ async getMetricObject(name) {
159
+ const metric = this.getMetric(name);
160
+ if (!metric) {
161
+ return undefined;
162
+ }
163
+ return await metric.get();
164
+ }
165
+ async getMetricValues(name) {
166
+ const metricObject = await this.getMetricObject(name);
167
+ if (!metricObject) {
168
+ return undefined;
169
+ }
170
+ return metricObject.values;
171
+ }
172
+ /**
173
+ *
174
+ */
175
+ /**
176
+ * Registers a counter metric
177
+ * @param name - The name of the metric
178
+ * @param help - The help text for the metric
179
+ * @param labelNames - The names of the labels for the metric
180
+ * @returns The counter metric
181
+ */
182
+ registerCounter({ name, help, labelNames = [] }) {
183
+ return new this.client.Counter({
184
+ name: `${this.prefix}${name}`,
185
+ help,
186
+ labelNames
187
+ });
188
+ }
189
+ /**
190
+ * Registers a gauge metric
191
+ * @param name - The name of the metric
192
+ * @param help - The help text for the metric
193
+ * @param collect - The collect function to use for the gauge
194
+ * @returns The gauge metric
195
+ */
196
+ registerGauge({ name, help, collect }) {
197
+ return new this.client.Gauge({
198
+ name: `${this.prefix}${name}`,
199
+ help,
200
+ collect
201
+ });
202
+ }
203
+ /**
204
+ * Registers a summary metric
205
+ * @param name - The name of the metric
206
+ * @param help - The help text for the metric
207
+ * @param percentiles - The percentiles to calculate for the summary
208
+ * @param collect - The collect function to use for the summary
209
+ * @returns The summary metric
210
+ */
211
+ registerSummary({ name, help, percentiles, maxAgeSeconds, ageBuckets, pruneAgedBuckets, collect }) {
212
+ return new this.client.Summary({
213
+ name: `${this.prefix}${name}`,
214
+ help,
215
+ percentiles: percentiles || [0.5, 0.9, 0.99],
216
+ maxAgeSeconds,
217
+ ageBuckets,
218
+ pruneAgedBuckets,
219
+ collect
220
+ });
221
+ }
222
+ /**
223
+ * Registers a histogram metric
224
+ * @param name - The name of the metric
225
+ * @param help - The help text for the metric
226
+ * @param buckets - The buckets to calculate for the histogram
227
+ * @param collect - The collect function to use for the histogram
228
+ * @returns The histogram metric
229
+ */
230
+ registerHistogram({ name, help, buckets }) {
231
+ return new this.client.Histogram({
232
+ name: `${this.prefix}${name}`,
233
+ help,
234
+ buckets: buckets
235
+ });
236
+ }
237
+ // Utility functions for creating custom metrics
238
+ /**
239
+ * Instruments the knex connection pool and queries
240
+ * @param knexInstance - The knex instance
241
+ */
242
+ instrumentKnex(knexInstance) {
243
+ // Create some gauges for tracking the connection pool
244
+ this.registerGauge({
245
+ name: `db_connection_pool_max`,
246
+ help: 'The maximum number of connections allowed in the pool',
247
+ collect() {
248
+ this.set(knexInstance.client.pool.max);
249
+ }
250
+ });
251
+ this.registerGauge({
252
+ name: `db_connection_pool_min`,
253
+ help: 'The minimum number of connections allowed in the pool',
254
+ collect() {
255
+ this.set(knexInstance.client.pool.min);
256
+ }
257
+ });
258
+ this.registerGauge({
259
+ name: `db_connection_pool_active`,
260
+ help: 'The number of active connections to the database, which can be in use or idle',
261
+ collect() {
262
+ this.set(knexInstance.client.pool.numUsed() + knexInstance.client.pool.numFree());
263
+ }
264
+ });
265
+ this.registerGauge({
266
+ name: `db_connection_pool_used`,
267
+ help: 'The number of connections currently in use by the database',
268
+ collect() {
269
+ this.set(knexInstance.client.pool.numUsed());
270
+ }
271
+ });
272
+ this.registerGauge({
273
+ name: `db_connection_pool_idle`,
274
+ help: 'The number of active connections currently idle in pool',
275
+ collect() {
276
+ this.set(knexInstance.client.pool.numFree());
277
+ }
278
+ });
279
+ this.registerGauge({
280
+ name: `db_connection_pool_pending_acquires`,
281
+ help: 'The number of connections currently waiting to be acquired from the pool',
282
+ collect() {
283
+ this.set(knexInstance.client.pool.numPendingAcquires());
284
+ }
285
+ });
286
+ this.registerGauge({
287
+ name: `db_connection_pool_pending_creates`,
288
+ help: 'The number of connections currently waiting to be created',
289
+ collect() {
290
+ this.set(knexInstance.client.pool.numPendingCreates());
291
+ }
292
+ });
293
+ const queryDurationSummary = this.registerSummary({
294
+ name: `db_query_duration_seconds`,
295
+ help: 'Summary of the duration of knex database queries in seconds',
296
+ percentiles: [0.5, 0.9, 0.99],
297
+ maxAgeSeconds: 60,
298
+ ageBuckets: 6,
299
+ pruneAgedBuckets: false
300
+ });
301
+ const acquireDurationSummary = this.registerSummary({
302
+ name: `db_connection_acquire_duration_seconds`,
303
+ help: 'Summary of the duration of acquiring a connection from the pool in seconds',
304
+ percentiles: [0.5, 0.9, 0.99],
305
+ maxAgeSeconds: 60,
306
+ ageBuckets: 6,
307
+ pruneAgedBuckets: false
308
+ });
309
+ const createDurationSummary = this.registerSummary({
310
+ name: `db_connection_create_duration_seconds`,
311
+ help: 'Summary of the duration of creating a connection in seconds',
312
+ percentiles: [0.5, 0.9, 0.99],
313
+ maxAgeSeconds: 60,
314
+ ageBuckets: 6,
315
+ pruneAgedBuckets: false
316
+ });
317
+ knexInstance.on('query', (query) => {
318
+ // Add the query to the map
319
+ this.queries.set(query.__knexQueryUid, queryDurationSummary.startTimer());
320
+ });
321
+ knexInstance.on('query-response', (err, query) => {
322
+ this.queries.get(query.__knexQueryUid)?.();
323
+ this.queries.delete(query.__knexQueryUid);
324
+ });
325
+ knexInstance.client.pool.on('createRequest', (eventId) => {
326
+ this.creates.set(eventId, createDurationSummary.startTimer());
327
+ });
328
+ knexInstance.client.pool.on('createSuccess', (eventId) => {
329
+ this.creates.get(eventId)?.();
330
+ this.creates.delete(eventId);
331
+ });
332
+ knexInstance.client.pool.on('createFail', (eventId) => {
333
+ this.creates.get(eventId)?.();
334
+ this.creates.delete(eventId);
335
+ });
336
+ knexInstance.client.pool.on('acquireRequest', (eventId) => {
337
+ this.acquires.set(eventId, acquireDurationSummary.startTimer());
338
+ });
339
+ knexInstance.client.pool.on('acquireSuccess', (eventId) => {
340
+ this.acquires.get(eventId)?.();
341
+ this.acquires.delete(eventId);
342
+ });
343
+ knexInstance.client.pool.on('acquireFail', (eventId) => {
344
+ this.acquires.get(eventId)?.();
345
+ this.acquires.delete(eventId);
346
+ });
347
+ }
348
+ }
349
+ exports.PrometheusClient = PrometheusClient;
@@ -0,0 +1,2 @@
1
+ export * from './MetricsServer';
2
+ export * from './PrometheusClient';
package/build/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./MetricsServer"), exports);
18
+ __exportStar(require("./PrometheusClient"), exports);
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@tryghost/prometheus-metrics",
3
+ "version": "1.0.0",
4
+ "repository": "https://github.com/TryGhost/Ghost/tree/main/packages/prometheus-metrics",
5
+ "author": "Ghost Foundation",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "build/index.js",
10
+ "types": "build/index.d.ts",
11
+ "scripts": {
12
+ "dev": "tsc --watch --preserveWatchOutput --sourceMap",
13
+ "build": "yarn build:ts",
14
+ "build:ts": "tsc",
15
+ "prepare": "tsc",
16
+ "test:unit": "NODE_ENV=testing c8 --src src --all --check-coverage --90 --reporter text --reporter cobertura mocha -r ts-node/register './test/**/*.test.ts'",
17
+ "test": "yarn test:types && yarn test:unit",
18
+ "test:types": "tsc --noEmit",
19
+ "lint:code": "eslint src/ --ext .ts --cache",
20
+ "lint": "yarn lint:code && yarn lint:test",
21
+ "lint:test": "eslint -c test/.eslintrc.js test/ --ext .ts --cache"
22
+ },
23
+ "files": [
24
+ "build"
25
+ ],
26
+ "devDependencies": {
27
+ "@types/express": "4.17.21",
28
+ "@types/sinon": "17.0.4",
29
+ "@types/stoppable": "1.1.3",
30
+ "@types/supertest": "6.0.3",
31
+ "c8": "10.1.2",
32
+ "knex": "2.4.2",
33
+ "mocha": "10.8.2",
34
+ "nock": "13.5.6",
35
+ "sinon": "19.0.5",
36
+ "supertest": "7.0.0",
37
+ "ts-node": "10.9.2",
38
+ "typescript": "5.8.3"
39
+ },
40
+ "dependencies": {
41
+ "@tryghost/logging": "2.4.21",
42
+ "express": "4.21.2",
43
+ "prom-client": "15.1.3",
44
+ "stoppable": "1.1.0"
45
+ },
46
+ "gitHead": "422803e3f596e81f0321c328680e08d8221af5b4"
47
+ }