diff --git a/src/app.ts b/src/app.ts index e2f4def..f966e95 100644 --- a/src/app.ts +++ b/src/app.ts @@ -17,6 +17,14 @@ export default class App { this.containers = containers.sort((a: Container, b: Container) => a.number - b.number) } + get context(): string { + return this.primary.context + } + + get composeFile(): string { + return this.primary.composeFile + } + get primary(): Container { return this.containers[0] } @@ -86,36 +94,29 @@ export default class App { } async down() { - return Promise.all(this.containers.map(container => container.down())) + return docker.compose(this.context, 'stop', this.composeFile) } async up() { - return Promise.all(this.containers.map(container => container.up())) + return docker.compose(this.context, 'start', this.composeFile, { exit: true }) } async remove() { - return Promise.all(this.containers.map(container => container.remove())) + return docker.compose(this.context, 'rm --stop --force --volumes', this.composeFile) } async exec(command: string) { return this.primary.exec(command) } - findContainer(options: FindContainerOptions): Container | null { - return this.containers.find(container => container.service == options.service) - } - - async shell(options: FindContainerOptions) { + findContainer(options: FindContainerOptions): Container { if (options.service) { - const container = this.findContainer(options) - if (!container) exit(1, `👿 No container found for a service named '${options.service}'. Valid services are: ${this.containers.map(container => container.service).join(', ')}`) - return container.shell() + const container = this.containers.find(container => container.service == options.service) + if (!container) + exit(1, `👿 No container found for a service named '${options.service}'. Valid services are: ${this.containers.map(container => container.service).join(', ')}`) + return container } - return this.primary.shell() - } - - async logs(options: { follow?: boolean, tail?: number } = {}) { - return this.primary.logs(options) + return this.primary } async rebuild(config: StaxConfig, options: SetupOptions = {}) { diff --git a/src/cli.ts b/src/cli.ts index f686d52..dacf32a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,6 +11,7 @@ const DEFAULT_CONTEXT_NAME = 'stax' const editor = process.env.STAX_EDITOR || 'code' const stax = new Stax(DEFAULT_CONTEXT_NAME) const program = new Command() + program.name('stax') program.command('alias') @@ -20,10 +21,11 @@ program.command('alias') .action((name, alias) => stax.find(name).addAlias(alias)) program.command('config') - .argument('', 'Name of application') + .option('-s, --service ', 'Name of service to act on') .description('Show config variables for the container.') - .action(name => { - const container = stax.find(name).primary + .action((name, options) => { + const container = stax.findContainer(name, options) const attributes = { ...container.config, labels: container.labels } console.log(attributes) }) @@ -33,14 +35,19 @@ program.command('copy') .argument('', 'Name of application') .argument('', 'Path to a local file or directory') .argument('', 'Path to a destination file or directory in the container') + .option('-s, --service ', 'Name of service to act on') .option('-n, --dont-overwrite', 'Don\'t overwrite if file already exists') .description('Copy a file to the container') - .action(async (name, source, destination, options) => stax.find(name).primary.copy(source, destination, options)) + .action(async (name, source, destination, options) => stax.findContainer(name, options).copy(source, destination, options)) program.command('down') .argument('', 'Name of application') + .option('-s, --service ', 'Name of service to act on') .description('Stop an application') - .action(async name => { await stax.find(name).down() }) + .action(async (name, options) => { + const target = options.service ? stax.findContainer(name, options) : stax.find(name) + target.down() + }) program.command('edit') .argument('', 'Name of application') @@ -62,9 +69,18 @@ program.command('edit') program.command('exec') .argument('', 'Name of application') - .argument('', 'Command to execute') + .argument('', 'Command to execute. Use "--" before your command if it has more than one word.') + .option('-s, --service ', 'Name of service to act on') .description('Execute a command in a running application') - .action(async (name, command) => { await stax.find(name).exec(command) }) + .action(async (name, command, options) => await stax.findContainer(name, options).exec(command)) + +program.command('get') + .argument('', 'Name of application') + .argument('', 'File to copy from the container') + .argument('', 'Local destination to copy the file to') + .option('-s, --service ', 'Name of service to act on') + .description('Copy a from the container') + .action(async (name, source, destination, options) => stax.findContainer(name, options).get(source, destination)) program.command('inspect') .argument('', 'Name of application') @@ -85,20 +101,23 @@ program.command('inspect') run(`docker inspect ${app.primary.containerName}`) }) -program.command('list') - .alias('ps').alias('ls') +program.command('ls') + .alias('ps').alias('list') .description('List applications') .action(() => stax.list()) program.command('logs') .argument('', 'Name of application') + .option('-s, --service ', 'Name of service to act on') .option('-f, --follow', 'Follow log output') .option('-t, --tail ', 'Number of lines to show from the end of the logs') + .option('--since ', 'A time or duration string accepted by \'docker container logs\'') .description('Tail logs for an application') .action(async (name, options) => { const follow = options.follow || false const tail = options.tail ? parseInt(options.tail) : undefined - await stax.find(name).logs({ follow, tail }) + const since = options.since + await stax.findContainer(name, options).logs({ follow, tail, since }) }) program.command('rebuild') @@ -118,13 +137,6 @@ program.command('restart') .description('Restart an application') .action(async name => { await stax.find(name).restart() }) -program.command('get') - .argument('', 'Name of application') - .argument('', 'File to copy from the container') - .argument('', 'Local destination to copy the file to') - .description('Copy a from the container') - .action(async (name, source, destination) => stax.find(name).primary.get(source, destination)) - program.command('setup') .argument('', 'Path to a local directory or git repo of application') .option('-s, --staxfile ', 'Staxfile to use for setup') @@ -135,20 +147,26 @@ program.command('setup') program.command('shell') .alias('sh') .argument('', 'Name of application') - .option('-s, --service ', 'Service to shell into') + .option('-s, --service ', 'Name of service to act on') .description('Shell into application\' primary container') - .action(async (name, options) => stax.find(name).shell(options)) + .action(async (name, options) => stax.findContainer(name, options).shell()) program.command('up') .argument('', 'Name of application') + .option('-s, --service ', 'Name of service to act on') .description('Start an application') - .action(async name => { await stax.find(name).up() }) + .action(async (name, options) => { + const target = options.service ? stax.findContainer(name, options) : stax.find(name) + target.up() + }) let [ args, overrides ] = parseAndRemoveWildcardOptions(process.argv, '--stax.') +const commandSeparator = args.indexOf('--') -if (args[2] == 'exec' && process.argv.length > 5) { - args = process.argv.slice(0, 4) - args = args.concat(process.argv.slice(4, 9999).join(' ')) +if (commandSeparator >= 0) { + const command = args.slice(commandSeparator+1).join(' ') + args = args.slice(0, commandSeparator) + args.push(command) } tmp.setGracefulCleanup() diff --git a/src/container.ts b/src/container.ts index 49cd13c..eb896b5 100644 --- a/src/container.ts +++ b/src/container.ts @@ -1,4 +1,4 @@ -import { existsSync } from 'fs' +import { existsSync, readFileSync } from 'fs' import { csvKeyValuePairs, exit } from '~/utils' import { FindOptions, SetupOptions, StaxConfig } from '~/types' import { run } from '~/shell' @@ -112,20 +112,20 @@ export default class Container { if (!c) { if (options.warn) - console.warn(`🤷 Container '${name}@${context}' not found`) + console.warn(`🤷 Container '${context}/${containerName}' not found`) else if (options.mustExist) - return exit(1, `👿 '${name}@${context}' is not a valid container name`) + return exit(1, `👿 '${context}/${containerName}' is not a valid container name`) } return c } async down() { - return docker.compose(this.context, 'stop', this.composeFile) + return docker.compose(this.context, `stop ${this.name}`, this.composeFile) } async up() { - return docker.compose(this.context, 'start', this.composeFile, { exit: true }) + return docker.compose(this.context, `start ${this.name}`, this.composeFile, { exit: true }) } async remove() { @@ -163,10 +163,11 @@ export default class Container { }) } - async logs(options: { follow?: boolean, tail?: number } = {}) { + async logs(options: { follow?: boolean, tail?: number, since?: string } = {}) { let command = `logs ${this.name}` if (options.follow) command += ' --follow' if (options.tail) command += ` --tail=${options.tail}` + if (options.since) command += ` --since=${options.since}` return docker.compose(this.context, command, this.composeFile) } diff --git a/src/stax.ts b/src/stax.ts index e6c1ca7..2a19a6a 100644 --- a/src/stax.ts +++ b/src/stax.ts @@ -1,6 +1,7 @@ import { exit } from '~/utils' -import { StaxConfig } from '~/types' +import { FindContainerOptions, StaxConfig } from '~/types' import App from '~/app' +import Container from '~/container' import settings from '~/settings' import list from '~/app_list' @@ -29,6 +30,10 @@ export default class Stax { return app } + findContainer(appName: string, options: FindContainerOptions): Container { + return this.find(appName).findContainer(options) + } + apps(): App[] { return App.all(this.context) }