UNPKG

4.33 kBJavaScriptView Raw
1const la = require('lazy-ass')
2const is = require('check-more-types')
3const { join } = require('path')
4const { existsSync } = require('fs')
5
6/**
7 * Returns parsed command line arguments.
8 * If start command is NPM script name defined in the package.json
9 * file in the current working directory, returns 'npm run start' command.
10 */
11const getArguments = cliArgs => {
12 la(is.strings(cliArgs), 'expected list of strings', cliArgs)
13
14 const service = {
15 start: 'start',
16 url: undefined
17 }
18 const services = [service]
19
20 let test = 'test'
21
22 if (cliArgs.length === 1 && isUrlOrPort(cliArgs[0])) {
23 // passed just single url or port number, for example
24 // "start": "http://localhost:8080"
25 service.url = normalizeUrl(cliArgs[0])
26 } else if (cliArgs.length === 2) {
27 if (isUrlOrPort(cliArgs[0])) {
28 // passed port and custom test command
29 // like ":8080 test-ci"
30 service.url = normalizeUrl(cliArgs[0])
31 test = cliArgs[1]
32 }
33 if (isUrlOrPort(cliArgs[1])) {
34 // passed start command and url/port
35 // like "start-server 8080"
36 service.start = cliArgs[0]
37 service.url = normalizeUrl(cliArgs[1])
38 }
39 } else if (cliArgs.length === 5) {
40 service.start = cliArgs[0]
41 service.url = normalizeUrl(cliArgs[1])
42
43 const secondService = {
44 start: cliArgs[2],
45 url: normalizeUrl(cliArgs[3])
46 }
47 services.push(secondService)
48
49 test = cliArgs[4]
50 } else {
51 la(
52 cliArgs.length === 3,
53 'expected <NPM script name that starts server> <url or port> <NPM script name that runs tests>\n',
54 'example: start-test start 8080 test\n',
55 'see https://github.com/bahmutov/start-server-and-test#use\n'
56 )
57 service.start = cliArgs[0]
58 service.url = normalizeUrl(cliArgs[1])
59 test = cliArgs[2]
60 }
61
62 services.forEach(service => {
63 service.start = normalizeCommand(service.start)
64 })
65
66 test = normalizeCommand(test)
67
68 return {
69 services,
70 test
71 }
72}
73
74function normalizeCommand (command) {
75 return UTILS.isPackageScriptName(command) ? `npm run ${command}` : command
76}
77
78/**
79 * Returns true if the given string is a name of a script in the package.json file
80 * in the current working directory
81 */
82const isPackageScriptName = command => {
83 la(is.unemptyString(command), 'expected command name string', command)
84
85 const packageFilename = join(process.cwd(), 'package.json')
86 if (!existsSync(packageFilename)) {
87 return false
88 }
89 const packageJson = require(packageFilename)
90 if (!packageJson.scripts) {
91 return false
92 }
93 return Boolean(packageJson.scripts[command])
94}
95
96const isWaitOnUrl = s => /^https?-(?:get|head|options)/.test(s)
97
98const isUrlOrPort = input => {
99 const str = is.string(input) ? input.split('|') : [input]
100
101 return str.every(s => {
102 if (is.url(s)) {
103 return s
104 }
105 // wait-on allows specifying HTTP verb to use instead of default HEAD
106 // and the format then is like "http-get://domain.com" to use GET
107 if (isWaitOnUrl(s)) {
108 return s
109 }
110
111 if (is.number(s)) {
112 return is.port(s)
113 }
114 if (!is.string(s)) {
115 return false
116 }
117 if (s[0] === ':') {
118 const withoutColon = s.substr(1)
119 return is.port(parseInt(withoutColon))
120 }
121 return is.port(parseInt(s))
122 })
123}
124
125const normalizeUrl = input => {
126 const str = is.string(input) ? input.split('|') : [input]
127
128 return str.map(s => {
129 if (is.url(s)) {
130 return s
131 }
132
133 if (is.number(s) && is.port(s)) {
134 return `http://localhost:${s}`
135 }
136
137 if (!is.string(s)) {
138 return s
139 }
140
141 if (is.port(parseInt(s))) {
142 return `http://localhost:${s}`
143 }
144
145 if (s[0] === ':') {
146 return `http://localhost${s}`
147 }
148 // for anything else, return original argument
149 return s
150 })
151}
152
153function printArguments ({ services, test }) {
154 services.forEach((service, k) => {
155 console.log('%d: starting server using command "%s"', k + 1, service.start)
156 console.log(
157 'and when url "%s" is responding with HTTP status code 200',
158 service.url
159 )
160 })
161
162 console.log('running tests using command "%s"', test)
163 console.log('')
164}
165
166// placing functions into a common object
167// makes them methods for easy stubbing
168const UTILS = {
169 getArguments,
170 isPackageScriptName,
171 isUrlOrPort,
172 normalizeUrl,
173 printArguments
174}
175
176module.exports = UTILS