Provider config and cli-based setup

This commit is contained in:
Nick Farina
2015-07-15 22:36:45 -07:00
parent 6afff4f3e4
commit 5611e38beb
13 changed files with 233 additions and 137 deletions

View File

@@ -20,4 +20,4 @@ var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
global.homebridge = require(lib + '/homebridge');
// Run the HomeBridge CLI
homebridge.cli();
require(lib + '/cli')();

View File

@@ -1,3 +1,24 @@
import request from 'request';
// Demonstrate that we were loaded
console.log("Lockitron provider loaded!");
module.exports = {
config: {
accessToken: {
type: 'string',
description: "You can find your personal Access Token at: https://api.lockitron.com",
required: true
}
},
validateConfig: function(callback) {
// validate the accessToken
let accessToken = homebridge.config.get('homebridge-lockitron.accessToken');
// prove that we got a value
console.log(`Access Token: ${accessToken}`);
}
}

View File

@@ -8,5 +8,8 @@
},
"keywords": [
"homebridge-provider"
]
}
],
"dependencies": {
"request": "^2.58.0"
}
}

118
lib/cli.js Normal file
View File

@@ -0,0 +1,118 @@
import program from 'commander';
import log from 'npmlog';
import prompt from 'prompt';
import { HOMEBRIDGE_VERSION } from './homebridge';
import { User } from './user';
import { Server } from './server';
import { Provider } from './provider';
import { camelCaseToRegularForm } from './util';
export default function() {
// Global options (none currently) and version printout
program
.version(HOMEBRIDGE_VERSION);
// Run the HomeBridge server
program
.command('server')
.description('Run the HomeBridge server.')
.action(runServer);
program
.command('providers')
.description('List installed providers.')
.action(listInstalledProviders);
program
.command('setup [provider]')
.description('Sets up a new HomeBridge provider or re-configures an existing one.')
.action(setupProvider);
// Parse options and execute HomeBridge
program.parse(process.argv);
// Display help by default if no commands or options given
if (!process.argv.slice(2).length) {
program.help();
}
}
function runServer(options) {
// get all installed providers
let providers:Array<Provider> = Provider.installed();
// load and validate providers - check for valid package.json, etc.
try {
this.providerModules = providers.map((provider) => provider.load());
}
catch (err) {
console.log(err.message);
process.exit(1);
}
}
function listInstalledProviders(options) {
Provider.installed().forEach((provider) => console.log(provider.name));
}
function setupProvider(providerName, options) {
// if you didn't specify a provider, print help
if (!providerName) {
log.error("You must specify the name of the provider to setup. Type 'homebridge providers' to list the providers currently installed.");
program.help();
}
try {
let provider = new Provider(providerName);
let providerModule:object = provider.load({skipConfigCheck: true});
if (providerModule.config) {
prompt.message = "";
prompt.delimiter = "";
prompt.start();
prompt.get(buildPromptSchema(providerName, providerModule.config), (err, result) => {
// apply configuration values entered by the user
for (let key:string in result) {
let value:object = result[key];
User.config.set(`${providerName}.${key}`, value);
}
providerModule.validateConfig();
});
}
else {
providerModule.validateConfig();
}
}
catch (err) {
log.error(`Setup failed: ${err.message}`);
}
}
// builds a "schema" obejct for the prompt lib based on the provider's config spec
function buildPromptSchema(providerName: string, providerConfig: object): object {
let properties = {};
for (let key:string in providerConfig) {
let spec:object = providerConfig[key];
// do we have a value for this config key currently?
let currentValue = User.config.get(`${providerName}.${key}`);
// copy over config spec with some modifications
properties[key] = {
description: `\n${spec.description}\n${camelCaseToRegularForm(key).white}:`,
type: spec.type,
required: spec.required,
default: currentValue
}
}
return { properties };
}

40
lib/config.js Normal file
View File

@@ -0,0 +1,40 @@
import fs from 'fs';
export class Config {
constructor(path:string, data:object = {}) {
this.path = path;
this.data = data;
}
get(key:string) {
this.validateKey(key);
let [providerName, keyName] = key.split(".");
return this.data[providerName] && this.data[providerName][keyName];
}
set(key:string, value:object) {
this.validateKey(key);
let [providerName, keyName] = key.split(".");
this.data[providerName] = this.data[providerName] || {};
this.data[providerName][keyName] = value;
this.save();
}
validateKey(key:string) {
if (key.split(".").length != 2)
throw new Error(`The config key '${key}' is invalid. Configuration keys must be in the form [my-provider].[myKey]`);
}
static load(configPath: string): Config {
// load up the previous config if found
if (fs.existsSync(configPath))
return new Config(configPath, JSON.parse(fs.readFileSync(configPath)));
else
return new Config(configPath); // empty initial config
}
save() {
fs.writeFileSync(this.path, JSON.stringify(this.data, null, 2));
}
}

View File

@@ -1,5 +1,5 @@
import fs from 'fs';
import cli from './homebridge/cli';
import { User } from './user';
//
// Main HomeBridge Module with global exports.
@@ -8,7 +8,5 @@ import cli from './homebridge/cli';
// HomeBridge version
export const HOMEBRIDGE_VERSION = JSON.parse(fs.readFileSync('package.json')).version;
// HomeBridge CLI
export { cli }
// HomeBridge API
export let config = User.config;

View File

@@ -1,70 +0,0 @@
import program from 'commander';
import { HOMEBRIDGE_VERSION } from '../homebridge';
import { Server } from './server';
import { Provider } from './provider';
export default function() {
// Global options (none currently) and version printout
program
.version(HOMEBRIDGE_VERSION);
// Run the HomeBridge server
program
.command('server')
.description('Run the HomeBridge server.')
.action(runServer);
program
.command('providers')
.description('List installed providers.')
.action(listInstalledProviders);
program
.command('setup [provider]')
.description('Sets up a new HomeBridge provider or re-configures an existing one.')
.action(setupProvider);
// Parse options and execute HomeBridge
program.parse(process.argv);
// Display help by default if no commands or options given
if (!process.argv.slice(2).length) {
program.help();
}
}
function runServer(options) {
// get all installed providers
let providers:Array<Provider> = Provider.installed();
// load and validate providers - check for valid package.json, etc.
try {
this.providerModules = providers.map((provider) => provider.load());
}
catch (err) {
console.log(err.message);
process.exit(1);
}
}
function listInstalledProviders(options) {
Provider.installed().forEach((provider) => console.log(provider.name));
}
function setupProvider(providerName, options) {
// if you didn't specify a provider, print help
if (!providerName) {
console.log("You must specify the name of the provider to setup. Type 'homebridge providers' to list the providers currently installed.");
program.help();
}
try {
let provider = new Provider(providerName);
}
catch (err) {
}
}

View File

@@ -1,56 +0,0 @@
import path from 'path';
import fs from 'fs';
//
// Manages user settings and storage locations.
//
// global cached config
let config:Config;
export class User {
static get config():Config {
return config || (config = new Config());
}
static get storagePath():string {
let home = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
return path.join(home, ".homebridge");
}
static get configPath():string {
return path.join(User.storagePath, "config.json");
}
static get providersPath():string {
return path.join(User.storagePath, "providers");
}
}
export class Config {
constructor(data:object = {}) {
this.data = data;
}
get(key:string) {
return this.data[key];
}
set(key:string, value:object) {
this.data[key] = value;
}
static load():Config {
// load up the previous config if found
if (fs.existsSync(User.configPath))
return new Config(JSON.parse(fs.readFileSync(User.configPath)));
else
return new Config(); // empty initial config
}
save() {
fs.writeFileSync(User.configPath, JSON.stringify(this.data));
}
}

View File

@@ -2,7 +2,7 @@ import path from 'path';
import fs from 'fs';
import semver from 'semver';
import { User } from './user';
import { HOMEBRIDGE_VERSION } from '../homebridge';
import { HOMEBRIDGE_VERSION } from './homebridge';
// This class represents a HomeBridge Provider that may or may not be installed.
export class Provider {
@@ -15,11 +15,11 @@ export class Provider {
return path.join(User.providersPath, this.name);
}
load():object {
load(options:object = {}):object {
// does this provider exist at all?
if (!fs.existsSync(this.path)) {
throw new Error(`Provider ${this.name} was not found. Make sure a directory matching the name '${this.name}' exists in your ~/.homebridge/providers folder.`)
throw new Error(`Provider ${this.name} was not found. Make sure the directory '~/.homebridge/providers/${this.name}' exists.`)
}
// check for a package.json
@@ -62,7 +62,7 @@ export class Provider {
let providerConfig = loadedProvider.config;
// verify that all required values are present
if (providerConfig) {
if (providerConfig && !options.skipConfigCheck) {
for (let key:string in providerConfig) {
let configParams:object = providerConfig[key];

31
lib/user.js Normal file
View File

@@ -0,0 +1,31 @@
import path from 'path';
import fs from 'fs';
import { Config } from './config';
//
// Manages user settings and storage locations.
//
// global cached config
let config:Config;
export class User {
static get config():Config {
return config || (config = Config.load(User.configPath));
}
static get storagePath():string {
let home = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
return path.join(home, ".homebridge");
}
static get configPath():string {
return path.join(User.storagePath, "config.json");
}
static get providersPath():string {
return path.join(User.storagePath, "providers");
}
}

9
lib/util.js Normal file
View File

@@ -0,0 +1,9 @@
// Converts "accessToken" to "Access Token"
export function camelCaseToRegularForm(camelCase: string): string {
return camelCase
// insert a space before all caps
.replace(/([A-Z])/g, ' $1')
// uppercase the first character
.replace(/^./, function(str){ return str.toUpperCase(); })
}

View File

@@ -29,6 +29,8 @@
"babel": "^5.6.14",
"commander": "^2.8.1",
"hap-nodejs": "git+https://github.com/khaost/HAP-NodeJS#2a1bc8d99a2009317ab5da93faebea34c89f197c",
"npmlog": "^1.2.1",
"prompt": "^0.2.14",
"semver": "^4.3.6"
}
}