mirror of
https://github.com/mtan93/homebridge.git
synced 2026-04-05 06:13:13 +01:00
Provider config and cli-based setup
This commit is contained in:
@@ -20,4 +20,4 @@ var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
|
|||||||
global.homebridge = require(lib + '/homebridge');
|
global.homebridge = require(lib + '/homebridge');
|
||||||
|
|
||||||
// Run the HomeBridge CLI
|
// Run the HomeBridge CLI
|
||||||
homebridge.cli();
|
require(lib + '/cli')();
|
||||||
|
|||||||
@@ -1,3 +1,24 @@
|
|||||||
|
import request from 'request';
|
||||||
|
|
||||||
// Demonstrate that we were loaded
|
// Demonstrate that we were loaded
|
||||||
console.log("Lockitron provider 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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,5 +8,8 @@
|
|||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"homebridge-provider"
|
"homebridge-provider"
|
||||||
]
|
],
|
||||||
}
|
"dependencies": {
|
||||||
|
"request": "^2.58.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
118
lib/cli.js
Normal file
118
lib/cli.js
Normal 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
40
lib/config.js
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import cli from './homebridge/cli';
|
import { User } from './user';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Main HomeBridge Module with global exports.
|
// Main HomeBridge Module with global exports.
|
||||||
@@ -8,7 +8,5 @@ import cli from './homebridge/cli';
|
|||||||
// HomeBridge version
|
// HomeBridge version
|
||||||
export const HOMEBRIDGE_VERSION = JSON.parse(fs.readFileSync('package.json')).version;
|
export const HOMEBRIDGE_VERSION = JSON.parse(fs.readFileSync('package.json')).version;
|
||||||
|
|
||||||
// HomeBridge CLI
|
|
||||||
export { cli }
|
|
||||||
|
|
||||||
// HomeBridge API
|
// HomeBridge API
|
||||||
|
export let config = User.config;
|
||||||
@@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ import path from 'path';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import { User } from './user';
|
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.
|
// This class represents a HomeBridge Provider that may or may not be installed.
|
||||||
export class Provider {
|
export class Provider {
|
||||||
@@ -15,11 +15,11 @@ export class Provider {
|
|||||||
return path.join(User.providersPath, this.name);
|
return path.join(User.providersPath, this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
load():object {
|
load(options:object = {}):object {
|
||||||
|
|
||||||
// does this provider exist at all?
|
// does this provider exist at all?
|
||||||
if (!fs.existsSync(this.path)) {
|
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
|
// check for a package.json
|
||||||
@@ -62,7 +62,7 @@ export class Provider {
|
|||||||
let providerConfig = loadedProvider.config;
|
let providerConfig = loadedProvider.config;
|
||||||
|
|
||||||
// verify that all required values are present
|
// verify that all required values are present
|
||||||
if (providerConfig) {
|
if (providerConfig && !options.skipConfigCheck) {
|
||||||
for (let key:string in providerConfig) {
|
for (let key:string in providerConfig) {
|
||||||
|
|
||||||
let configParams:object = providerConfig[key];
|
let configParams:object = providerConfig[key];
|
||||||
31
lib/user.js
Normal file
31
lib/user.js
Normal 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
9
lib/util.js
Normal 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(); })
|
||||||
|
}
|
||||||
@@ -29,6 +29,8 @@
|
|||||||
"babel": "^5.6.14",
|
"babel": "^5.6.14",
|
||||||
"commander": "^2.8.1",
|
"commander": "^2.8.1",
|
||||||
"hap-nodejs": "git+https://github.com/khaost/HAP-NodeJS#2a1bc8d99a2009317ab5da93faebea34c89f197c",
|
"hap-nodejs": "git+https://github.com/khaost/HAP-NodeJS#2a1bc8d99a2009317ab5da93faebea34c89f197c",
|
||||||
|
"npmlog": "^1.2.1",
|
||||||
|
"prompt": "^0.2.14",
|
||||||
"semver": "^4.3.6"
|
"semver": "^4.3.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user