Skip to content

Commit b9c43e9

Browse files
committed
feat: Add configuration manager, builder, watcher, and utility functions for RESTHeart CLI
1 parent 42dc146 commit b9c43e9

10 files changed

+983
-621
lines changed

lib/builder.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import shell from 'shelljs'
4+
import { commandExists, createSpinner, logger } from './utils.js'
5+
6+
/**
7+
* Handles building and deploying RESTHeart plugins
8+
*/
9+
export class Builder {
10+
/**
11+
* Create a new Builder
12+
* @param {ConfigManager} configManager The configuration manager
13+
*/
14+
constructor(configManager) {
15+
this.configManager = configManager
16+
}
17+
18+
/**
19+
* Build RESTHeart and plugins
20+
* @param {string} mvnParams Maven parameters - default is 'package'
21+
* @param {boolean} skipTests Skip tests - default is false
22+
*/
23+
build(mvnParams = 'package', skipTests = false) {
24+
const { repoDir } = this.configManager.getAll()
25+
26+
logger.warning('\nBuilding RESTHeart... ⏱️')
27+
28+
shell.rm('-rf', path.join(repoDir, 'target'))
29+
const currentDir = shell.pwd()
30+
31+
shell.cd(repoDir)
32+
let mvnCommand = `./mvnw -f pom.xml ${mvnParams} -DskipTests=${skipTests}`
33+
34+
try {
35+
if (!fs.existsSync(path.join(repoDir, 'mvnw'))) {
36+
logger.warning('mvnw not found, using mvn instead')
37+
commandExists('mvn')
38+
mvnCommand = `mvn -f pom.xml ${mvnParams}`
39+
} else {
40+
commandExists('./mvnw')
41+
}
42+
43+
if (shell.exec(mvnCommand).code !== 0) {
44+
shell.cd(currentDir)
45+
throw new Error('Failed to build RESTHeart')
46+
}
47+
48+
shell.cd(currentDir)
49+
} catch (error) {
50+
shell.cd(currentDir)
51+
logger.error(`Building failed: ${error.message}`)
52+
shell.exit(1)
53+
}
54+
}
55+
56+
/**
57+
* Deploy RESTHeart and plugins
58+
*/
59+
deploy() {
60+
const { repoDir, rhDir, debugMode } = this.configManager.getAll()
61+
62+
const spinner = createSpinner('Deploying plugins...')
63+
64+
try {
65+
// Copy plugin jars to the plugins directory
66+
shell.cp(path.join(repoDir, 'target', '*.jar'), path.join(rhDir, 'plugins'))
67+
shell.cp(
68+
path.join(repoDir, 'target', 'lib', '*.jar'),
69+
path.join(rhDir, 'plugins')
70+
)
71+
72+
spinner.succeed('Plugins deployed')
73+
74+
// Log deployed plugins in debug mode
75+
if (debugMode) {
76+
let count = 1
77+
console.log('\n')
78+
shell.ls(path.join(rhDir, 'plugins')).forEach((file) => {
79+
logger.debug(`${count++}\t${file}`, debugMode)
80+
})
81+
console.log('\n')
82+
}
83+
} catch (error) {
84+
spinner.fail('Failed to deploy plugins')
85+
logger.error(`Deployment failed: ${error.message}`)
86+
shell.exit(1)
87+
}
88+
}
89+
}

lib/cli.js

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import chalk from 'chalk'
2+
import yargs from 'yargs'
3+
import { hideBin } from 'yargs/helpers'
4+
import { RESTHeartManager, msg } from './restheart.js'
5+
6+
/**
7+
* Initialize the CLI application
8+
*/
9+
export function initCLI() {
10+
const rh = new RESTHeartManager()
11+
12+
// Intercept CTRL-C and kill RESTHeart before exiting
13+
process.on('SIGINT', async () => {
14+
console.log('\n')
15+
if (await rh.isRunning()) await rh.kill()
16+
process.exit()
17+
})
18+
19+
// Register cleanup function to run on exit
20+
process.on('exit', () => {
21+
msg(chalk.green('\nDone.\n'))
22+
})
23+
24+
// Print welcome message
25+
console.log('\n')
26+
msg(chalk.green(' ============================'))
27+
msg(' Welcome to RESTHeart CLI')
28+
msg(chalk.green(' ============================\n'))
29+
30+
// Command line arguments setup with command and options handling
31+
yargs(hideBin(process.argv))
32+
.strict()
33+
.parserConfiguration({
34+
'populate--': true,
35+
})
36+
.usage('Usage: $0 [command] [options]')
37+
.command(
38+
['install [restheart-version]', 'i'],
39+
'Install RESTHeart',
40+
(yargs) => {
41+
yargs
42+
.positional('restheart-version', {
43+
describe: 'RESTHeart version to install',
44+
type: 'string',
45+
default: 'latest',
46+
})
47+
.option('force', {
48+
alias: 'f',
49+
type: 'boolean',
50+
description: 'Force reinstalling RESTHeart',
51+
})
52+
},
53+
(argv) => runCommand('install', argv, rh)
54+
)
55+
.command(
56+
['build', 'b'],
57+
'Build and deploy the plugin, restarting RESTHeart (default)',
58+
{},
59+
(argv) => runCommand('build', argv, rh)
60+
)
61+
.command(
62+
['run [restheart-options..]', 'r'],
63+
'Start or restart RESTHeart',
64+
(yargs) => {
65+
yargs
66+
.option('build', {
67+
alias: 'b',
68+
type: 'boolean',
69+
description: 'Build and deploy the plugin before running RESTHeart',
70+
})
71+
.option('port', {
72+
alias: 'p',
73+
type: 'number',
74+
description: 'HTTP port',
75+
})
76+
.positional('restheart-options', {
77+
describe: 'Options to pass to RESTHeart',
78+
type: 'string',
79+
default: '',
80+
})
81+
.example(
82+
'rh run -- -o etc/localhost.yml',
83+
'Start or restart RESTHeart with custom options'
84+
)
85+
},
86+
(argv) => {
87+
runCommand('run', argv, rh)
88+
}
89+
)
90+
.command(
91+
['kill', 'k'],
92+
'Kill RESTHeart',
93+
(yargs) => {
94+
yargs.option('port', {
95+
alias: 'p',
96+
type: 'number',
97+
description: 'HTTP port',
98+
})
99+
},
100+
(argv) => runCommand('kill', argv, rh)
101+
)
102+
.command(
103+
['watch', 'w'],
104+
'Watch sources and build and deploy plugins on changes, restarting RESTHeart',
105+
(yargs) => {
106+
yargs
107+
.option('build', {
108+
alias: 'b',
109+
type: 'boolean',
110+
description: 'Build and deploy the plugin before running RESTHeart',
111+
})
112+
.option('port', {
113+
alias: 'p',
114+
type: 'number',
115+
description: 'HTTP port',
116+
})
117+
.example(
118+
'rh watch -- -o etc/localhost.yml',
119+
'Watch sources and build and deploy plugins on changes, restarting RESTHeart with custom options'
120+
)
121+
},
122+
(argv) => {
123+
runCommand('watch', argv, rh)
124+
}
125+
)
126+
.command(
127+
['status', 's'],
128+
'Show the status of RESTHeart',
129+
(yargs) => {
130+
yargs.option('port', {
131+
alias: 'p',
132+
type: 'number',
133+
description: 'HTTP port',
134+
})
135+
},
136+
(argv) => runCommand('status', argv, rh)
137+
)
138+
.option('debug', {
139+
alias: 'd',
140+
type: 'boolean',
141+
description: 'Run in debug mode',
142+
})
143+
.help('h')
144+
.alias('h', 'help')
145+
.demandCommand(1, 'You need at least one command before moving on')
146+
.parse()
147+
}
148+
149+
/**
150+
* Run a command
151+
* @param {string} command Command to run
152+
* @param {Object} argv Command arguments
153+
* @param {RESTHeartManager} rh RESTHeart manager instance
154+
*/
155+
async function runCommand(command, argv, rh) {
156+
const restheartOptions = (argv['--'] && argv['--'].join(' ')) || ''
157+
158+
if (argv.port) {
159+
rh.setHttpPort(argv.port)
160+
}
161+
if (argv.debug) {
162+
msg(
163+
chalk.cyan('Running command: ') +
164+
command +
165+
chalk.cyan(' with options:\n') +
166+
JSON.stringify(argv, null, 2)
167+
)
168+
rh.setDebugMode(argv.debug)
169+
rh.printConfiguration()
170+
}
171+
172+
switch (command) {
173+
case 'install':
174+
rh.install(argv.restheartVersion, argv.force)
175+
break
176+
case 'build':
177+
rh.build('clean package')
178+
rh.deploy()
179+
break
180+
case 'run':
181+
if (!rh.onlyPrintConfig(restheartOptions)) {
182+
await rh.checkAndKill()
183+
if (argv.build) {
184+
rh.build('clean package', true)
185+
rh.deploy()
186+
}
187+
}
188+
await rh.run(restheartOptions)
189+
break
190+
case 'kill':
191+
await rh.checkAndKill()
192+
break
193+
case 'watch':
194+
await rh.checkAndKill()
195+
if (argv.build) {
196+
rh.build('clean package', true)
197+
rh.deploy()
198+
}
199+
await rh.run(restheartOptions)
200+
rh.watchFiles(restheartOptions)
201+
break
202+
case 'status':
203+
rh.status()
204+
break
205+
default:
206+
yargs.showHelp()
207+
break
208+
}
209+
}

lib/config.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import path from 'path'
2+
import { ensureDir } from './utils.js'
3+
4+
/**
5+
* Configuration manager for RESTHeart CLI
6+
*/
7+
export class ConfigManager {
8+
/**
9+
* Create a new ConfigManager
10+
* @param {Object} options Configuration options
11+
*/
12+
constructor(options = {}) {
13+
const repoDir = process.cwd()
14+
15+
this.config = {
16+
repoDir: repoDir,
17+
cacheDir: options.cacheDir || path.join(repoDir, '.cache'),
18+
rhDir: options.rhDir || path.join(repoDir, '.cache', 'restheart'),
19+
httpPort: options.httpPort || 8080,
20+
debugMode: options.debugMode || false
21+
}
22+
23+
// Ensure cache directory exists
24+
ensureDir(this.config.cacheDir)
25+
ensureDir(this.config.rhDir)
26+
}
27+
28+
/**
29+
* Get a configuration value
30+
* @param {string} key The configuration key
31+
* @returns {any} The configuration value
32+
*/
33+
get(key) {
34+
return this.config[key]
35+
}
36+
37+
/**
38+
* Get all configuration values
39+
* @returns {Object} All configuration values
40+
*/
41+
getAll() {
42+
return { ...this.config }
43+
}
44+
45+
/**
46+
* Set a configuration value
47+
* @param {string} key The configuration key
48+
* @param {any} value The configuration value
49+
*/
50+
set(key, value) {
51+
this.config[key] = value
52+
}
53+
54+
/**
55+
* Set multiple configuration values
56+
* @param {Object} config Configuration object
57+
*/
58+
setMultiple(config) {
59+
this.config = { ...this.config, ...config }
60+
}
61+
}

0 commit comments

Comments
 (0)