Skip to content

Commit df7730c

Browse files
committed
Refactor cli
1 parent 12602e9 commit df7730c

File tree

8 files changed

+136
-85
lines changed

8 files changed

+136
-85
lines changed

src/cli.ts

+52-69
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
import process from 'node:process'
44
import chalk from 'chalk'
5-
import minimist from 'minimist'
6-
import {createHost, parseHost} from './host.js'
5+
import minimist, {ParsedArgs} from 'minimist'
6+
import {Context, createHost, parseHost} from './host.js'
77
import {runTask} from './task.js'
88
import './recipe/common.js'
9-
import {exec, humanPath} from './utils.js'
10-
import {startSpinner, stopSpinner} from './spinner.js'
11-
import {ask, confirm, skipPrompts} from './prompt.js'
9+
import {exec} from './utils.js'
10+
import {disableSpinner, startSpinner, stopSpinner} from './spinner.js'
11+
import {ask, skipPrompts} from './prompt.js'
1212
import fs from 'node:fs'
1313
import {handleError, StopError} from './error.js'
14-
import {detectFramework, setupFramework} from './framework.js'
1514
import {login} from './api.js'
1615
import {loadUserConfig} from './env.js'
1716

@@ -41,6 +40,7 @@ await async function main() {
4140
},
4241
})
4342
skipPrompts(argv.yes)
43+
normiliazeArgs(argv)
4444

4545
if (argv.version) {
4646
console.log(JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version)
@@ -59,21 +59,7 @@ await async function main() {
5959
await loadUserConfig()
6060
await login()
6161

62-
let task, remoteUser, hostname, become
63-
if (argv._.length == 2) {
64-
task = argv._[0];
65-
({remoteUser, hostname, become} = parseHost(argv._[1]))
66-
} else if (argv._.length == 1) {
67-
({remoteUser, hostname, become} = parseHost(argv._[0]))
68-
} else if (argv._.length == 0) {
69-
({remoteUser, hostname, become} = parseHost(await ask('Enter hostname: ')))
70-
}
71-
if (!task) {
72-
task = 'provision-and-deploy'
73-
}
74-
if (argv.scripts && !Array.isArray(argv.scripts)) {
75-
argv.scripts = [argv.scripts]
76-
}
62+
const {task, remoteUser, hostname, become} = await parseTaskAndHost(argv)
7763

7864
const context = createHost({
7965
remoteUser,
@@ -83,60 +69,57 @@ await async function main() {
8369
})
8470

8571
// Check SSH connection
86-
const shellName = await context.$.with({nothrow: true})`echo $0`
87-
if (shellName.toString() != 'bash') {
88-
throw new StopError(
89-
`Webpod cannot connect to ${bold(`${remoteUser}@${hostname}`)}`,
90-
`Please, verify that you can connect to the host using ${bold('ssh')} command.\n\n` +
91-
` ssh ${remoteUser}@${hostname} ${context.config.port ? `-p ${context.config.port}` : ''}\n`
92-
)
93-
}
72+
await checkSSHConnection(context)
9473

95-
do {
96-
const framework = detectFramework()
97-
await setupFramework(framework, context)
98-
99-
await context.host.domain
100-
await context.host.uploadDir
101-
await context.host.publicDir
102-
103-
console.log(`Webpod now will configure your server with:`)
104-
console.log(` ${bold('✔')} Updates`)
105-
console.log(` ${bold('✔')} Firewall`)
106-
console.log(` ${bold('✔')} Webserver`)
107-
console.log(` ${bold('✔')} SSL/TLS certificates`)
108-
console.log(` ${bold('✔')} Node.js`)
109-
console.log(` ${bold('✔')} Supervisor`)
110-
console.log(`and deploy your app:`)
111-
if (framework == 'next') {
112-
console.log(`${cyan('Next.js')} detected`)
113-
} else {
114-
console.log(` ${bold('✔')} Uploading ${cyan(humanPath(await context.host.uploadDir))} to ${cyan(await context.host.remoteUser + '@' + await context.host.hostname)}`)
115-
if (await context.host.static) {
116-
console.log(` ${bold('✔')} Serving ${cyan(humanPath(await context.host.uploadDir, await context.host.publicDir))} at ${cyan('https://' + await context.host.domain)}`)
117-
}
118-
for (const script of await context.host.scripts) {
119-
console.log(` ${bold('✔')} Running ${cyan(humanPath(await context.host.uploadDir, script))}`)
120-
}
121-
}
122-
123-
if (await confirm(`Correct?`)) {
124-
break
125-
} else {
126-
delete context.config.domain
127-
delete context.config.uploadDir
128-
delete context.config.publicDir
129-
delete context.config.scripts
130-
}
131-
} while (true)
132-
133-
if (!context.config.verbose) startSpinner()
74+
if (context.config.verbose) {
75+
disableSpinner()
76+
}
77+
startSpinner()
13478
try {
13579
await runTask(task, context)
13680
} finally {
13781
stopSpinner()
13882
}
13983

14084
console.log(`${green('Done!')} ${cyan('https://' + await context.host.domain)} 🎉`)
141-
14285
}().catch(handleError)
86+
87+
function normiliazeArgs(argv: ParsedArgs) {
88+
if (argv.scripts && !Array.isArray(argv.scripts)) {
89+
argv.scripts = [argv.scripts]
90+
}
91+
}
92+
93+
type TaskAndHost = {
94+
task: string
95+
remoteUser: string
96+
hostname: string
97+
become?: string
98+
}
99+
100+
async function parseTaskAndHost(argv: ParsedArgs): Promise<TaskAndHost> {
101+
let task = 'default'
102+
let remoteUser: string
103+
let hostname: string
104+
let become: string | undefined
105+
if (argv._.length == 2) {
106+
task = argv._[0];
107+
({remoteUser, hostname, become} = parseHost(argv._[1]))
108+
} else if (argv._.length == 1) {
109+
({remoteUser, hostname, become} = parseHost(argv._[0]))
110+
} else {
111+
({remoteUser, hostname, become} = parseHost(await ask('Enter hostname: ')))
112+
}
113+
return {task, remoteUser, hostname, become}
114+
}
115+
116+
async function checkSSHConnection(context: Context) {
117+
const shellName = await context.$.with({nothrow: true})`echo $0`
118+
if (shellName.toString() != 'bash') {
119+
throw new StopError(
120+
`Webpod cannot connect to ${bold(`${context.config.remoteUser}@${context.config.hostname}`)}`,
121+
`Please, verify that you can connect to the host using ${bold('ssh')} command.\n\n` +
122+
` ssh ${context.config.remoteUser}@${context.config.hostname} ${context.config.port ? `-p ${context.config.port}` : ''}\n`,
123+
)
124+
}
125+
}

src/framework.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import {Context} from './host.js'
1+
import {Config, Context} from './host.js'
22
import fs from 'node:fs'
33

4-
export async function setupFramework(framework: string | undefined, context: Context) {
4+
export async function setupFramework(framework: string | undefined, config: Partial<Config>) {
55
if (!framework) return
66
if (framework == 'next') {
7-
context.config.uploadDir = '.'
8-
context.config.publicDir = '.'
9-
context.config.static = false
10-
context.config.scripts = ['node_modules/.bin/next start']
7+
config.uploadDir = '.'
8+
config.publicDir = '.'
9+
config.static = false
10+
config.scripts = ['node_modules/.bin/next start']
1111
} else if (framework == 'angular') {
12-
context.config.fallback = '/index.html'
12+
config.fallback = '/index.html'
1313
}
1414
}
1515

src/host.ts

-3
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,6 @@ export function parseHost(remoteUserAndHostname: string) {
113113
if (!remoteUser) {
114114
remoteUser = 'root'
115115
}
116-
if (remoteUser != 'root') {
117-
become = 'root'
118-
}
119116
return {remoteUser, hostname, become}
120117
}
121118

src/prompt.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import inquirer from 'inquirer'
2+
import {continueSpinner, stopSpinner} from './spinner.js'
23

34
let skip = false
45

@@ -8,34 +9,40 @@ export function skipPrompts(yes: boolean) {
89

910
export async function ask(message: string, defaultValue?: string): Promise<string> {
1011
if (skip) return defaultValue ?? ''
12+
stopSpinner()
1113
const answers = await inquirer.prompt({
1214
name: 'out',
1315
type: 'input',
1416
message,
1517
default: defaultValue,
1618
})
19+
continueSpinner()
1720
return answers.out
1821
}
1922

2023
export async function confirm(message: string, defaultValue = true): Promise<boolean> {
2124
if (skip) return defaultValue
25+
stopSpinner()
2226
const answers = await inquirer.prompt({
2327
name: 'out',
2428
type: 'confirm',
2529
message,
2630
default: defaultValue,
2731
})
32+
continueSpinner()
2833
return answers.out
2934
}
3035

3136
export async function choose(message: string, choices: string[]): Promise<string> {
3237
if (skip) return ''
38+
stopSpinner()
3339
const answers = await inquirer.prompt({
3440
name: 'out',
3541
type: 'list',
3642
message,
3743
choices,
3844
default: choices[0],
3945
})
46+
continueSpinner()
4047
return answers.out
4148
}

src/recipe/common.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import {defaults} from '../host.js'
22
import path from 'node:path'
33
import fs from 'node:fs'
4-
import {ask, choose} from '../prompt.js'
5-
import {humanPath} from '../utils.js'
6-
import './deploy/index.js'
7-
import './provision/index.js'
4+
import {ask} from '../prompt.js'
85
import {task} from '../task.js'
96
import chalk from 'chalk'
7+
import './deploy/index.js'
8+
import './provision/index.js'
9+
import './prepare.js'
1010

11-
task('provision-and-deploy', [
11+
task('default', [
12+
'prepare',
1213
'provision',
1314
'deploy',
1415
])

src/recipe/prepare.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import chalk from 'chalk'
2+
import {task} from '../task.js'
3+
import {detectFramework, setupFramework} from '../framework.js'
4+
import {humanPath} from '../utils.js'
5+
import {confirm} from '../prompt.js'
6+
7+
const {cyan, grey, green, bold} = chalk
8+
9+
task('prepare', async ({host, config}) => {
10+
do {
11+
const framework = detectFramework()
12+
await setupFramework(framework, config)
13+
14+
await host.domain
15+
await host.uploadDir
16+
await host.publicDir
17+
18+
console.log(`Webpod now will configure your server with:`)
19+
console.log(` ${bold('✔')} Updates`)
20+
console.log(` ${bold('✔')} Firewall`)
21+
console.log(` ${bold('✔')} Webserver`)
22+
console.log(` ${bold('✔')} SSL/TLS certificates`)
23+
console.log(` ${bold('✔')} Node.js`)
24+
console.log(` ${bold('✔')} Supervisor`)
25+
console.log(`and deploy your app:`)
26+
if (framework == 'next') {
27+
console.log(`${cyan('Next.js')} detected`)
28+
} else {
29+
console.log(` ${bold('✔')} Uploading ${cyan(humanPath(await host.uploadDir))} to ${cyan(await host.remoteUser + '@' + await host.hostname)}`)
30+
if (await host.static) {
31+
console.log(` ${bold('✔')} Serving ${cyan(humanPath(await host.uploadDir, await host.publicDir))} at ${cyan('https://' + await host.domain)}`)
32+
}
33+
for (const script of await host.scripts) {
34+
console.log(` ${bold('✔')} Running ${cyan(humanPath(await host.uploadDir, script))}`)
35+
}
36+
}
37+
38+
if (await confirm(`Correct?`)) {
39+
break
40+
} else {
41+
delete config.domain
42+
delete config.uploadDir
43+
delete config.publicDir
44+
delete config.scripts
45+
}
46+
} while (true)
47+
})

src/spinner.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,28 @@ import chalk from 'chalk'
66
let spinner: NodeJS.Timer | undefined
77
let startedAt: Date
88
let status = ''
9+
let disable = false
910

1011
export function progressMessage(message: string) {
1112
status = remoteNonAscii(getLastNonEmptyLine(message))
13+
if (/investigate|error/i.test(status)) {
14+
status = 'compiling...'
15+
}
16+
}
17+
18+
export function disableSpinner() {
19+
disable = true
1220
}
1321

1422
export function startSpinner() {
15-
let s = 0
23+
if (disable) return
1624
startedAt = new Date()
25+
continueSpinner()
26+
}
27+
28+
export function continueSpinner() {
29+
if (disable) return
30+
let s = 0
1731
const spin = () => {
1832
const time = secondsToHumanReadableFormat((new Date().getTime() - startedAt.getTime()) / 1000)
1933
const display = ` ${'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'[s++ % 10]} ${time}${currentlyRunningTask} `
@@ -24,6 +38,7 @@ export function startSpinner() {
2438
}
2539

2640
export function stopSpinner() {
41+
if (disable) return
2742
clearInterval(spinner)
2843
process.stderr.write(' '.repeat(process.stderr.columns - 1) + '\r')
2944
}

tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"strict": true,
1212
"declaration": true,
1313
"outDir": "dist",
14+
"sourceMap": true
1415
},
1516
"include": [
1617
"src/**/*",

0 commit comments

Comments
 (0)