1 | const la = require('lazy-ass')
|
2 | const is = require('check-more-types')
|
3 | const { join } = require('path')
|
4 | const { existsSync } = require('fs')
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | const crossArguments = cliArgs => {
|
12 | let concatModeChar = false
|
13 | const indicationChars = ["'", '"', '`']
|
14 | const combinedArgs = []
|
15 | for (let i = 0; i < cliArgs.length; i++) {
|
16 | let arg = cliArgs[i]
|
17 | if (
|
18 | !concatModeChar &&
|
19 | indicationChars.some(char => cliArgs[i].startsWith(char))
|
20 | ) {
|
21 | arg = arg.slice(1)
|
22 | }
|
23 | if (concatModeChar && cliArgs[i].endsWith(concatModeChar)) {
|
24 | arg = arg.slice(0, -1)
|
25 | }
|
26 |
|
27 | if (concatModeChar && combinedArgs.length) {
|
28 | combinedArgs[combinedArgs.length - 1] += ' ' + arg
|
29 | } else {
|
30 | combinedArgs.push(arg)
|
31 | }
|
32 |
|
33 | if (
|
34 | !concatModeChar &&
|
35 | indicationChars.some(char => cliArgs[i].startsWith(char))
|
36 | ) {
|
37 | concatModeChar = cliArgs[i][0]
|
38 | }
|
39 | if (concatModeChar && cliArgs[i].endsWith(concatModeChar)) {
|
40 | concatModeChar = false
|
41 | }
|
42 | }
|
43 | return combinedArgs
|
44 | }
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | const getArguments = cliArgs => {
|
52 | la(is.strings(cliArgs), 'expected list of strings', cliArgs)
|
53 |
|
54 | const service = {
|
55 | start: 'start',
|
56 | url: undefined
|
57 | }
|
58 | const services = [service]
|
59 |
|
60 | let test = 'test'
|
61 |
|
62 | if (cliArgs.length === 1 && isUrlOrPort(cliArgs[0])) {
|
63 |
|
64 |
|
65 | service.url = normalizeUrl(cliArgs[0])
|
66 | } else if (cliArgs.length === 2) {
|
67 | if (isUrlOrPort(cliArgs[0])) {
|
68 |
|
69 |
|
70 | service.url = normalizeUrl(cliArgs[0])
|
71 | test = cliArgs[1]
|
72 | }
|
73 | if (isUrlOrPort(cliArgs[1])) {
|
74 |
|
75 |
|
76 | service.start = cliArgs[0]
|
77 | service.url = normalizeUrl(cliArgs[1])
|
78 | }
|
79 | } else if (cliArgs.length === 5) {
|
80 | service.start = cliArgs[0]
|
81 | service.url = normalizeUrl(cliArgs[1])
|
82 |
|
83 | const secondService = {
|
84 | start: cliArgs[2],
|
85 | url: normalizeUrl(cliArgs[3])
|
86 | }
|
87 | services.push(secondService)
|
88 |
|
89 | test = cliArgs[4]
|
90 | } else {
|
91 | la(
|
92 | cliArgs.length === 3,
|
93 | 'expected <NPM script name that starts server> <url or port> <NPM script name that runs tests>\n',
|
94 | 'example: start-test start 8080 test\n',
|
95 | 'see https://github.com/bahmutov/start-server-and-test#use\n'
|
96 | )
|
97 | service.start = cliArgs[0]
|
98 | service.url = normalizeUrl(cliArgs[1])
|
99 | test = cliArgs[2]
|
100 | }
|
101 |
|
102 | services.forEach(service => {
|
103 | service.start = normalizeCommand(service.start)
|
104 | })
|
105 |
|
106 | test = normalizeCommand(test)
|
107 |
|
108 | return {
|
109 | services,
|
110 | test
|
111 | }
|
112 | }
|
113 |
|
114 | function normalizeCommand (command) {
|
115 | return UTILS.isPackageScriptName(command) ? `npm run ${command}` : command
|
116 | }
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 | const isPackageScriptName = command => {
|
123 | la(is.unemptyString(command), 'expected command name string', command)
|
124 |
|
125 | const packageFilename = join(process.cwd(), 'package.json')
|
126 | if (!existsSync(packageFilename)) {
|
127 | return false
|
128 | }
|
129 | const packageJson = require(packageFilename)
|
130 | if (!packageJson.scripts) {
|
131 | return false
|
132 | }
|
133 | return Boolean(packageJson.scripts[command])
|
134 | }
|
135 |
|
136 | const isWaitOnUrl = s => /^https?-(?:get|head|options)/.test(s)
|
137 |
|
138 | const isUrlOrPort = input => {
|
139 | const str = is.string(input) ? input.split('|') : [input]
|
140 |
|
141 | return str.every(s => {
|
142 | if (is.url(s)) {
|
143 | return s
|
144 | }
|
145 |
|
146 |
|
147 | if (isWaitOnUrl(s)) {
|
148 | return s
|
149 | }
|
150 |
|
151 | if (is.number(s)) {
|
152 | return is.port(s)
|
153 | }
|
154 | if (!is.string(s)) {
|
155 | return false
|
156 | }
|
157 | if (s[0] === ':') {
|
158 | const withoutColon = s.substr(1)
|
159 | return is.port(parseInt(withoutColon))
|
160 | }
|
161 | return is.port(parseInt(s))
|
162 | })
|
163 | }
|
164 |
|
165 | const normalizeUrl = input => {
|
166 | const str = is.string(input) ? input.split('|') : [input]
|
167 |
|
168 | return str.map(s => {
|
169 | if (is.url(s)) {
|
170 | return s
|
171 | }
|
172 |
|
173 | if (is.number(s) && is.port(s)) {
|
174 | return `http://localhost:${s}`
|
175 | }
|
176 |
|
177 | if (!is.string(s)) {
|
178 | return s
|
179 | }
|
180 |
|
181 | if (is.port(parseInt(s))) {
|
182 | return `http://localhost:${s}`
|
183 | }
|
184 |
|
185 | if (s[0] === ':') {
|
186 | return `http://localhost${s}`
|
187 | }
|
188 |
|
189 | return s
|
190 | })
|
191 | }
|
192 |
|
193 | function printArguments ({ services, test }) {
|
194 | services.forEach((service, k) => {
|
195 | console.log('%d: starting server using command "%s"', k + 1, service.start)
|
196 | console.log(
|
197 | 'and when url "%s" is responding with HTTP status code 200',
|
198 | service.url
|
199 | )
|
200 | })
|
201 |
|
202 | console.log('running tests using command "%s"', test)
|
203 | console.log('')
|
204 | }
|
205 |
|
206 |
|
207 |
|
208 | const UTILS = {
|
209 | crossArguments,
|
210 | getArguments,
|
211 | isPackageScriptName,
|
212 | isUrlOrPort,
|
213 | normalizeUrl,
|
214 | printArguments
|
215 | }
|
216 |
|
217 | module.exports = UTILS
|