Permalink
Browse files

feat(cli): add `au config` command

The `au config` command gets or sets configuration for the Aurelia application.
`au config <key> <value> --get --set --clear --add --remove --no-save --no-backup`

Closes #629.
  • Loading branch information...
jwx committed May 12, 2017
1 parent e12d70d commit 5cd16f6afcfa7849bec75c676f3a211d8224136d
@@ -0,0 +1,47 @@
'use strict';
const UI = require('../../ui').UI;
const CLIOptions = require('../../cli-options').CLIOptions;
const Container = require('aurelia-dependency-injection').Container;
const os = require('os');
const Configuration = require('./configuration');
const ConfigurationUtilities = require('./util');
module.exports = class {
static inject() { return [Container, UI, CLIOptions]; }
constructor(container, ui, options) {
this.container = container;
this.ui = ui;
this.options = options;
}
execute(args) {
this.config = new Configuration(this.options);
this.util = new ConfigurationUtilities(this.options, args);
let key = this.util.getArg(0) || '';
let value = this.util.getValue(this.util.getArg(1));
let save = !CLIOptions.hasFlag('no-save');
let backup = !CLIOptions.hasFlag('no-backup');
let action = this.util.getAction(value);
this.displayInfo(`Performing configuration action '${action}' on '${key}'`, (value ? `with '${value}'` : ''));
this.displayInfo(this.config.execute(action, key, value));
if (action !== 'get') {
if (save) {
this.config.save(backup).then((name) => {
this.displayInfo('Configuration saved. ' + (backup ? `Backup file '${name}' created.` : 'No backup file was created.'));
});
}
else {
this.displayInfo(`Action was '${action}', but no save was performed!`);
}
}
}
displayInfo(message) {
return this.ui.log(message + os.EOL);
}
};
@@ -0,0 +1,53 @@
{
"name": "config",
"description": "Gets or sets configuration for the Aurelia application.",
"parameters": [
{
"name": "key",
"optional": true,
"description": "The key you want to get or set. Supports hierarchies and array indexes, for example build.targets[0] and arrayWithArray[2].[1]"
},
{
"name": "value",
"optional": true,
"description": "The value you want to set the key to. Supports json, for example \"{ \\\"myKey\\\": \\\"myValue\\\" }\""
}
],
"flags": [
{
"name": "get",
"description": "Gets the content of key, ignoring value parameter (the same as not specifying a value).",
"type": "boolean"
},
{
"name": "set",
"description": "Sets the content of key to value, replacing any existing content.",
"type": "boolean"
},
{
"name": "clear",
"description": "Deletes the key and all its content from the configuration.",
"type": "boolean"
},
{
"name": "add",
"description": "If value or existing content of the key is an array, adds value(s) to existing content. If value is an object, merges it into existing content of key.",
"type": "boolean"
},
{
"name": "remove",
"description": "If value or existing content of the key is an array, removes value(s) from existing content. If value or existing content of the key is an object, removes key(s) from existing content of key.",
"type": "boolean"
},
{
"name": "no-save",
"description": "Don't save the changes in the configuration file.",
"type": "boolean"
},
{
"name": "no-backup",
"description": "Don't create a backup configuration file before saving changes.",
"type": "boolean"
}
]
}
@@ -0,0 +1,165 @@
'use strict';
const os = require('os');
const copySync = require('../../file-system').copySync;
const readFileSync = require('../../file-system').readFileSync;
const writeFile = require('../../file-system').writeFile;
class Configuration {
constructor(options) {
this.options = options;
this.aureliaJsonPath = options.originalBaseDir + '/aurelia_project/aurelia.json';
this.project = JSON.parse(readFileSync(this.aureliaJsonPath));
}
configEntry(key, createKey) {
let entry = this.project;
let keys = key.split('.');
if (!keys[0]) {
return entry;
}
while (entry && keys.length) {
key = this.parsedKey(keys.shift());
if (entry[key.value] === undefined || entry[key.value] === null) {
if (!createKey) {
return entry[key.value];
}
let checkKey = this.parsedKey(keys.length ? keys[0] : createKey);
if (checkKey.index) {
entry[key.value] = [];
}
else if (checkKey.key) {
entry[key.value] = {};
}
}
entry = entry[key.value];
// TODO: Add support for finding objects based on input values?
// TODO: Add support for finding string in array?
}
return entry;
}
parsedKey(key) {
if (/\[(\d+)\]/.test(key)) {
return { index: true, key: false, value: +(RegExp.$1) };
}
else {
return { index: false, key: true, value: key };
}
}
normalizeKey(key) {
const re = /([^.])\[/;
while (re.exec(key)) {
key = key.replace(re, RegExp.$1 + '.[');
}
let keys = key.split('.');
for (let i = 0; i < keys.length; i++) {
if (/\[(\d+)\]/.test(keys[i])) {
// console.log(`keys[${i}] is index: ${keys[i]}`);
}
else if (/\[(.+)\]/.test(keys[i])) {
// console.log(`keys[${i}] is indexed name: ${keys[i]}`);
keys[i] = RegExp.$1;
}
else {
// console.log(`keys[${i}] is name: ${keys[i]}`);
}
}
return keys.join('.');
}
execute(action, key, value) {
let originalKey = key;
key = this.normalizeKey(key);
if (action === 'get') {
return `Configuration key '${key}' is:` + os.EOL + JSON.stringify(this.configEntry(key), null, 2);
}
let keys = key.split('.');
key = this.parsedKey(keys.pop());
let parent = keys.join('.');
if (action === 'set') {
let entry = this.configEntry(parent, key.value);
if (entry) {
entry[key.value] = value;
}
else {
console.log('Failed to set property', this.normalizeKey(originalKey), '!');
}
}
else if (action === 'clear') {
let entry = this.configEntry(parent);
if (entry && (key.value in entry)) {
delete entry[key.value];
}
else {
console.log('No property', this.normalizeKey(originalKey), 'to clear!');
}
}
else if (action === 'add') {
let entry = this.configEntry(parent, key.value);
if (Array.isArray(entry[key.value]) && !Array.isArray(value)) {
value = [value];
}
if (Array.isArray(value) && !Array.isArray(entry[key.value])) {
entry[key.value] = (entry ? [entry[key.value]] : []);
}
if (Array.isArray(value)) {
entry[key.value].push(...value);
}
else if (Object(value) === value) {
if (Object(entry[key.value]) !== entry[key.value]) {
entry[key.value] = {};
}
Object.assign(entry[key.value], value);
}
else {
entry[key.value] = value;
}
}
else if (action === 'remove') {
let entry = this.configEntry(parent);
if (Array.isArray(entry) && key.index) {
entry.splice(key.value, 1);
}
else if (Object(entry) === entry && key.key) {
delete entry[key.value];
}
else if (!entry) {
console.log('No property', this.normalizeKey(originalKey), 'to remove from!');
}
else {
console.log("Can't remove value from", entry[key.value], "!");
}
}
key = this.normalizeKey(originalKey);
return `Configuration key '${key}' is now:` + os.EOL + JSON.stringify(this.configEntry(key), null, 2);
}
save(backup = true) {
const unique = new Date().toISOString().replace(/[T\D]/g, '');
let arr = this.aureliaJsonPath.split(/[\\\/]/);
const name = arr.pop();
const path = arr.join('/');
const bak = `${name}.${unique}.bak`;
if (backup) {
copySync(this.aureliaJsonPath, [path, bak].join('/'));
}
return writeFile(this.aureliaJsonPath, JSON.stringify(this.project, null, 2), 'utf8')
.then(() => { return bak });
}
}
module.exports = Configuration;
@@ -0,0 +1,52 @@
'use strict';
class ConfigurationUtilities {
constructor(options, args) {
this.options = options;
this.args = args;
}
getArg(arg) {
let args = this.args;
if (args) {
for (let i = 0; i < args.length; i++) {
if (args[i].startsWith('--')) {
arg++;
}
if (i === arg) {
return args[i];
}
}
}
}
getValue(value) {
if (value) {
if (!value.startsWith('"') &&
!value.startsWith('[') &&
!value.startsWith('{')) {
value = `"${value}"`;
}
value = JSON.parse(value);
}
return value;
}
getAction(value) {
let actions = ['add', 'remove', 'set', 'clear', 'get'];
for (let action of actions) {
if (this.options.hasFlag(action)) {
return action;
}
}
if (!value) {
return 'get';
}
if (Array.isArray(value) || typeof value === 'object') {
return 'add';
}
return 'set';
}
}
module.exports = ConfigurationUtilities;
@@ -35,6 +35,7 @@ module.exports = class {
getLocalCommandText() {
let commands = [
require('../generate/command.json'),
require('../config/command.json'),
require('./command.json')
];
View
@@ -46,9 +46,10 @@
"gulp-conventional-changelog": "^1.1.3",
"gulp-eslint": "^3.0.1",
"jasmine": "^2.5.2",
"jasmine-spec-reporter": "^4.2.1",
"latest-version": "^3.1.0",
"mock-fs": "^4.2.0",
"minimatch": "^3.0.4",
"mock-fs": "^4.2.0",
"nodemon": "^1.11.0",
"require-dir": "^0.3.1",
"run-sequence": "^1.2.2",
Oops, something went wrong.

0 comments on commit 5cd16f6

Please sign in to comment.