-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Feature/blueprint clone command #133
base: master
Are you sure you want to change the base?
Changes from all commits
1e378ad
d880b21
636bb94
e8ef3d1
3b814a2
9069c7c
ce2be51
995135e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import getHandler from '../handler'; | ||
import handlers from './clone/handlers'; | ||
|
||
getHandler().onRun('clone', handlers.handleRun); | ||
|
||
const usage = `Usage: | ||
$0 clone <blueprint> <name>`; | ||
|
||
module.exports = { | ||
command: 'clone <blueprint> <name>', | ||
aliases: [], | ||
describe: 'Clone a blueprint with a different name', | ||
builder: yargs => { | ||
yargs | ||
.usage(usage) | ||
.option('dry-run', { | ||
alias: 'd', | ||
describe: "List files but don't generate them", | ||
type: 'boolean' | ||
}) | ||
.group(['dry-run', 'help'], 'Clone Options:'); | ||
|
||
return yargs; | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import getEnvironment from '../../environment'; | ||
import { logYargs } from '../../yargs'; | ||
import Clone from '../../../sub-commands/clone'; | ||
|
||
const handlers = { | ||
handleRun | ||
}; | ||
|
||
export default handlers; | ||
|
||
function handleRun(argv, yargs, rawArgs = process.argv.slice(3)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
const blueprintName = argv.blueprint; | ||
const environment = getEnvironment(); | ||
|
||
if (blueprintExists(environment.settings.blueprints, blueprintName)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pedant suggestion... if (blueprintInEnvironment(blueprintName, environment) {
...
} |
||
const subCommand = new Clone(environment); | ||
subCommand.run(argv.blueprint, argv); | ||
} else { | ||
logYargs(yargs, `Unknown blueprint '${blueprintName}'`); | ||
} | ||
} | ||
|
||
function blueprintExists(blueprints, blueprintName) { | ||
return blueprints.lookup(blueprintName); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import path from 'path'; | ||
import { copySync } from 'fs-extra'; | ||
import walkSync from 'walk-sync'; | ||
|
||
import { fileExists } from '../util/fs'; | ||
|
||
const FILES_BLACKLIST = ['.ds_store', '.git', '.gitkeep']; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We definitely need a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be worth it 🤔 If we set the defaults it's always good to let the user add more customization. But in what other circumstances will we need to blacklist files besides |
||
|
||
export default class BlueprintCloner { | ||
constructor(blueprint, options = {}) { | ||
this.blueprint = blueprint; | ||
this.options = options; | ||
this.ui = options.ui; | ||
} | ||
|
||
clone() { | ||
const blueprint = this.blueprint; | ||
this.ui.writeInfo('cloning blueprint...'); | ||
|
||
const cloneToDirectory = this.cloneToDirectory(); | ||
this.ui.writeInfo('cloning into: ' + cloneToDirectory); | ||
|
||
const blueprintFiles = this.blueprintFiles(); | ||
this.cloneFiles(blueprint.path, cloneToDirectory, blueprintFiles); | ||
} | ||
|
||
cloneFiles(sourceDirectory, cloneToDirectory, files) { | ||
files.forEach(file => { | ||
let sourcePath = path.resolve(sourceDirectory, file); | ||
let destinationPath = path.resolve(cloneToDirectory, file); | ||
this.cloneFile(sourcePath, destinationPath); | ||
}); | ||
} | ||
|
||
cloneFile(sourcePath, destinationPath) { | ||
const ui = this.ui; | ||
const dryRun = this.options.dryRun; | ||
|
||
ui.writeDebug(`Attempting to clone file: ${destinationPath}`); | ||
if (fileExists(destinationPath)) { | ||
ui.writeError( | ||
`Not writing file. File already exists at: ${destinationPath}` | ||
); | ||
} else { | ||
if (!dryRun) { | ||
copySync(sourcePath, destinationPath); | ||
ui.writeCreate(destinationPath); | ||
} else { | ||
ui.writeWouldCreate(destinationPath); | ||
} | ||
} | ||
} | ||
|
||
cloneToDirectory() { | ||
const settings = this.options.settings; | ||
|
||
// settings.blueprints.searchPaths[0] will be settings.cloneTo | ||
return path.resolve( | ||
settings.blueprints.searchPaths[0], | ||
this.newBlueprintName() | ||
); | ||
} | ||
|
||
newBlueprintName() { | ||
const options = this.options; | ||
|
||
if (options.entity) { | ||
return options.entity.name; | ||
} | ||
} | ||
|
||
blueprintFiles() { | ||
const blueprint = this.blueprint; | ||
let blueprintFiles = walkSync(blueprint.path, { | ||
directories: false, | ||
ignore: FILES_BLACKLIST | ||
}); | ||
blueprintFiles = this.filterBlacklistedFiles(blueprintFiles); | ||
return blueprintFiles; | ||
} | ||
|
||
filterBlacklistedFiles(files) { | ||
return files.filter(file => !this.isBlacklistedFile(file)); | ||
} | ||
|
||
isBlacklistedFile(file) { | ||
const fileName = path.basename(file).toLowerCase(); | ||
return FILES_BLACKLIST.includes(fileName); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import SubCommand from '../models/sub-command'; | ||
import Blueprint from '../models/blueprint'; | ||
import CloneBlueprint from '../tasks/clone-blueprint'; | ||
import chalk from 'chalk'; | ||
|
||
// Primary purpose is to take cli args and pass them through | ||
// to the proper task that will do the generation. | ||
// | ||
// Logic for displaying all blueprints and what their options | ||
// are will live in here. For now it's pretty baren. | ||
class Clone extends SubCommand { | ||
constructor(options) { | ||
super(options); | ||
this.cloneTask = new CloneBlueprint(this.environment); | ||
} | ||
|
||
run(blueprintName, cliArgs) { | ||
if (cliArgs.debug) { | ||
this.ui.setWriteLevel('DEBUG'); | ||
} | ||
|
||
this.cloneTask.run(blueprintName, cliArgs); | ||
} | ||
} | ||
|
||
export default Clone; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import Task from '../models/task'; | ||
import BlueprintCloner from '../models/blueprint-cloner'; | ||
|
||
export default class extends Task { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lets add an explicit class name, if only to make our IDE's happier. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI... none of the other Tasks currently specify a class name. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well, that's true. AirBNB recommend the existing way too: TIL |
||
constructor(environment) { | ||
super(environment); | ||
} | ||
|
||
run(blueprintName, cliArgs) { | ||
const blueprint = this.lookupBlueprint(blueprintName); | ||
|
||
const entity = { | ||
name: cliArgs.name | ||
}; | ||
|
||
const blueprintOptions = { | ||
originalBlueprintName: blueprintName, | ||
ui: this.ui, | ||
settings: this.settings, | ||
dryRun: cliArgs.dryRun, | ||
entity | ||
}; | ||
|
||
const blueprintCloner = new BlueprintCloner(blueprint, blueprintOptions); | ||
blueprintCloner.clone(); | ||
} | ||
|
||
lookupBlueprint(name) { | ||
return this.settings.blueprints.lookup(name); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 different quoting styles used in this line and last/next. If you install and run prettier, that will help with your formatting.This is the only descepency that eslint finds is in switch statements. Prettier wants cases indented, and eslint wants them flush with the switch statement.And so I've been editing it back after iyarn run pretty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@anithri
Prettier would use the two different quoting styles, because the describe string has an embedded single quote. Eslint should already allow that (and complain if double quotes are used in any other circumstance). The rule for quoting was modified by a recent PR
"quotes": [2, "single", {"avoidEscape": true}],
I prefer the prettier formatting for switch, and not having any manual step to undo it! This can also be accommodated by eslint:
"indent": [2, 2, {"SwitchCase": 1}],
Suggest we make that change in
.eslintrc