/**
* @module flitter-flap/FlapDirective
*/
const Directive = require('flitter-cli/Directive')
const path = require('path')
const flapper = require('../FlapHelper')(false)
const migrate = require('node-migration')
/**
* ./flitter directive for interacting with flitter-flap
*
* @extends module:flitter-cli/Directive~Directive
*/
class FlapDirective extends Directive {
/**
* Defines the services required by this directive.
* @returns {Array<string>}
*/
static get services() {
return [...super.services, 'utility', 'flap']
}
/**
* Get the name of the command for this directive. This is what is used by ./flitter.
*
* @static
* @returns {string} "flap"
*/
static name(){
return "flap"
}
/**
* Get the options provided by this directive. False to disable parsing.
* @returns {boolean}
*/
static options() {
return false
}
/**
* Get the usage text for this directive.
*
* @static
* @returns {string}
*/
static help(){
return "Flitter Flap -- updates Flitter in-place (experimental)"
}
/**
* Get the ASCII-text flap logo.
*
* @returns {string}
*/
logo(){
return `
\\ \\ \\
- flap - -
/ / /
`
}
/**
* Get the usage information string for flap.
*
* @returns {string}
*/
usage(){
// it may look weird, but the ascii art is actually not broken
return `
__ __ _ __ __
\\ \\ / _| | \\ \\ \\ \\
\\ \\ | |_| | __ _ _ __ \\ \\ \\ \\
> > | _| |/ _\` | '_ \\ > > > >
/ / | | | | (_| | |_) | / / / /
/_/ |_| |_|\\__,_| .__/ /_/ /_/
| |
|_|
Welcome to Flitter Flap! Flap is a tool designed to make it
easy to upgrade Flitter in-place. It uses a migration-based
system to modify the parts of Flitter that aren't a part of
yarn packages.
USAGE: ./flitter flap <directive> [OPTIONS]
help : display usage info
do : run all pending migrations
do <name> : run pending migrations from unit with name <name>
undo : undo applied migrations from all units
undo <name> : undo applied migrations from unit with name <name>
stamp : get the current UNIX timestamp (useful for creating migrations)
OPTIONS:
--dry : don't change any files, just list changes to be made
`
}
/**
* Apply migrations for the target unit in the specified direction. This uses the the migration tracking file
* specified in {@link module:flitter-flap/FlapUnit~FlapUnit#file}. If dry mode is specified, then a temporary copy of this file will be created and
* deleted, and the migrations will be provided a set of FlapHelper tools with dry mode enabled.
*
* @param {module:libflitter/app/FlitterApp~FlitterApp} app - the Flitter app
* @param {string} target - name of the unit from which migrations should be applied. This unit's migration directory is looked up from the list created in {@link module:flitter-flap/FlapUnit~FlapUnit#go}.
* @param {boolean} dry - If true, then the FlapHelper functions provided to the migrations will be run in dry mode. This specifies that files should not be modified.
* @param {"up" | "down"} direction - Direction in which migrations should be applied.
* @returns {Promise<void>}
*/
async do_migration(app, target, dry, direction="up"){
// resolve the folder containing the migration files
const folder = path.resolve(this.flap.loaded_migrations[target])
this.info("\n\nWill migrate target: "+target)
// create the setup folder with the toolchain
const setup_contents = `const FH = require('flitter-flap/FlapHelper')(${dry})
module.exports = (ctx) => { ctx.flap = FH }`
await flapper.write_file(path.resolve(folder, 'setup.js'), setup_contents)
// the file tracking applied migrations
const file_root = path.resolve(this.flap.dir, target)
let file = file_root+'.json'
if ( !await flapper.exists(file) ){
await flapper.write_file(file, '[]')
}
// if we're in dry-run mode, create a copy of the tracking file
if ( dry ){
await flapper.touch(file)
file = file_root+".dry.json"
await flapper.copy(file_root+'.json', file)
}
// do migration
await migrate.run(direction, { dir: folder, file })
// if we're in dry run mode, delete the copy of the tracking file
if ( dry ){
await flapper.delete(file)
}
// delete the setup file from the migrations folder
await flapper.delete(path.resolve(folder, 'setup.js'))
}
/**
* Handle an invocation of this command. Interprets the CLI arguments and handles them accordingly.
* @param {module:libflitter/app/FlitterApp~FlitterApp} app - the Flitter app
* @param {Object} argv - command line arguments passed in from ./flitter
* @returns {Promise<void>}
*/
async handle(app, argv){
const dry = argv.includes("--dry")
argv = argv.filter((val, ind, arr) => { return ( val !== "--dry" ) })
if ( dry ) console.log("Running in dry-run mode. Flap helpers won't modify files.")
switch (argv[0]){
case "do":
console.log(this.logo())
if ( argv[1] ){
if ( argv[1] in this.flap.loaded_migrations ){
await this.do_migration(app, argv[1], dry)
}
else {
console.log("ERROR: Could not find unit to migrate with name: "+argv[1])
}
}
else {
this.info("Will migrate all.", 2)
for ( let target in this.flap.loaded_migrations ){
await this.do_migration(app, target, dry)
}
}
break
case "undo":
console.log(this.logo())
if ( argv[1] ){
if ( argv[1] in this.flap.loaded_migrations ){
await this.do_migration(app, argv[1], dry, 'down')
}
else {
console.log("ERROR: Could not find unit to migrate with name: "+argv[1])
}
}
else {
for ( let target in this.flap.loaded_migrations ){
await this.do_migration(app, target, dry, 'down')
}
}
break
case "stamp":
console.log("The current UNIX time is: "+Math.floor(new Date() / 1000))
break
case "help":
console.log(this.usage())
break
default:
console.log("Invalid operation.")
console.log(this.usage())
}
}
}
module.exports = exports = FlapDirective