UNPKG

@pact-foundation/pact-cli

Version:
249 lines 10.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AbstractService = void 0; const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const events_1 = __importDefault(require("events")); const promise_timeout_1 = require("promise-timeout"); const mkdirp_1 = require("mkdirp"); const check_types_1 = __importDefault(require("check-types")); const needle_1 = __importDefault(require("needle")); const spawn_1 = __importDefault(require("./spawn")); const logger_1 = __importStar(require("./logger")); const { setTimeout } = global; const RETRY_AMOUNT = 60; const getTimeout = (options) => options.timeout || 30000; const getRetryTickTime = (options) => Math.round(getTimeout(options) / RETRY_AMOUNT); class AbstractService extends events_1.default.EventEmitter { static get Events() { return { START_EVENT: 'start', STOP_EVENT: 'stop', DELETE_EVENT: 'delete', }; } constructor(command, options, argMapping, cliVerb) { super(); if (options.logLevel) { (0, logger_1.setLogLevel)(options.logLevel); if (options.logLevel === 'fatal') { options.logLevel = 'error'; } else if (options.logLevel === 'trace') { options.logLevel = 'debug'; } options.logLevel = options.logLevel.toUpperCase(); } options.ssl = options.ssl || false; options.cors = options.cors || false; options.host = options.host || 'localhost'; if (options.port) { check_types_1.default.assert.number(options.port); check_types_1.default.assert.integer(options.port); check_types_1.default.assert.positive(options.port); if (!check_types_1.default.inRange(options.port, 0, 65535)) { throw new Error(`Port number ${options.port} is not in the range 0-65535`); } if (check_types_1.default.not.inRange(options.port, 1024, 49151)) { logger_1.default.warn('Like a Boss, you used a port outside of the recommended range (1024 to 49151); I too like to live dangerously.'); } } check_types_1.default.assert.boolean(options.ssl); if ((options.sslcert && !options.sslkey) || (!options.sslcert && options.sslkey)) { throw new Error('Custom ssl certificate and key must be specified together.'); } if (options.sslcert) { try { fs_1.default.statSync(path_1.default.normalize(options.sslcert)).isFile(); } catch (e) { throw new Error(`Custom ssl certificate not found at path: ${options.sslcert}`); } } if (options.sslkey) { try { fs_1.default.statSync(path_1.default.normalize(options.sslkey)).isFile(); } catch (e) { throw new Error(`Custom ssl key not found at path: ${options.sslkey}`); } } if (options.sslcert && options.sslkey) { options.ssl = true; } check_types_1.default.assert.boolean(options.cors); if (options.log) { const fileObj = path_1.default.parse(path_1.default.normalize(options.log)); try { fs_1.default.statSync(fileObj.dir).isDirectory(); } catch (e) { (0, mkdirp_1.sync)(fileObj.dir); } } if (options.host) { check_types_1.default.assert.string(options.host); } this.options = options; this.__running = false; this.__cliVerb = cliVerb; this.__serviceCommand = command; this.__argMapping = argMapping; } start() { if (this.__instance && this.__instance.connected) { logger_1.default.warn(`You already have a process running with PID: ${this.__instance.pid}`); return Promise.resolve(this); } this.__instance = this.spawnBinary(); this.__instance.once('close', () => this.stop()); if (!this.options.port) { const catchPort = (data) => { const match = data.match(/port=([0-9]+)/); if (match && match[1]) { this.options.port = parseInt(match[1], 10); if (this?.__instance?.stdout) { this.__instance.stdout.removeListener('data', catchPort); } logger_1.default.info(`Pact running on port ${this.options.port}`); } }; if (this?.__instance?.stdout) { this.__instance.stdout.on('data', catchPort); } } if (this?.__instance?.stderr) { this.__instance.stderr.on('data', (data) => logger_1.default.error(`Pact Binary Error: ${data}`)); } return (0, promise_timeout_1.timeout)(this.__waitForServiceUp(), getTimeout(this.options)) .then(() => { this.__running = true; this.emit(AbstractService.Events.START_EVENT, this); return this; }) .catch((err) => { if (err instanceof promise_timeout_1.TimeoutError) { throw new Error(`Timeout while waiting to start Pact with PID: ${this.__instance ? this.__instance.pid : 'No Instance'}`); } throw err; }); } stop() { const pid = this.__instance ? this.__instance.pid : -1; return (0, promise_timeout_1.timeout)(Promise.resolve(this.__instance) .then(spawn_1.default.killBinary) .then(() => this.__waitForServiceDown()), getTimeout(this.options)) .catch((err) => { if (err instanceof promise_timeout_1.TimeoutError) { throw new Error(`Timeout while waiting to stop Pact with PID '${pid}'`); } throw err; }) .then(() => { this.__running = false; this.emit(AbstractService.Events.STOP_EVENT, this); return this; }); } delete() { return this.stop().then(() => { this.emit(AbstractService.Events.DELETE_EVENT, this); return this; }); } spawnBinary() { return spawn_1.default.spawnBinary(this.__serviceCommand, this.__cliVerb ? [this.__cliVerb, this.options] : [this.options], this.__argMapping); } __waitForServiceUp() { let amount = 0; const waitPromise = new Promise((resolve, reject) => { const retry = () => { if (amount >= RETRY_AMOUNT) { reject(new Error(`Pact startup failed; tried calling service ${RETRY_AMOUNT} times with no result.`)); } setTimeout(check.bind(this), getRetryTickTime(this.options)); }; const check = () => { amount += 1; if (this.options.port) { this.__call(this.options).then(() => resolve(), retry.bind(this)); } else { retry(); } }; check(); }); return waitPromise; } __waitForServiceDown() { let amount = 0; const checkPromise = new Promise((resolve, reject) => { const check = () => { amount += 1; if (this.options.port) { this.__call(this.options).then(() => { if (amount >= RETRY_AMOUNT) { reject(new Error(`Pact stop failed; tried calling service ${RETRY_AMOUNT} times with no result.`)); return; } setTimeout(check, getRetryTickTime(this.options)); }, () => resolve()); } else { resolve(); } }; check(); }); return checkPromise; } __call(options) { const config = { method: 'GET', headers: { 'X-Pact-Mock-Service': 'true', 'Content-Type': 'application/json', }, }; if (options.ssl) { process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; config.rejectUnauthorized = false; config.agent = false; } return (0, needle_1.default)('get', `http${options.ssl ? 's' : ''}://${options.host}:${options.port}`, config).then((res) => { if (res.statusCode !== 200) { throw new Error(`HTTP Error: '${JSON.stringify(res)}'`); } }); } } exports.AbstractService = AbstractService; //# sourceMappingURL=service.js.map