UNPKG

6.68 kBJavaScriptView Raw
1import R from "ramda";
2import Promise from "bluebird";
3import uuid from "uuid";
4import shell from "shelljs";
5import app from "./app";
6import gateway from "./gateway";
7import log from "./log";
8import start from "./main-start";
9import manifest from "./manifest";
10import { promises } from "./util";
11import pubSub from "./pub-sub";
12import {
13 DEFAULT_APP_PORT,
14 DEFAULT_TARGET_FOLDER,
15} from "./const";
16
17
18
19// Ensure PM2 is installed globally.
20if (shell.exec("pm2 -v", { silent: true }).code !== 0) {
21 log.warn("WARNING: The PM2 (Process Manager) must be install globally, run: `npm install -g pm2`.");
22}
23
24
25
26/**
27 * Initializes a new app syncer.
28 * @param settings:
29 * - userAgent: https://developer.github.com/v3/#user-agent-required
30 * - token: The Github authorization token to use for calls to
31 * restricted resources.
32 * see: https://github.com/settings/tokens
33 * - targetFolder: The path where apps are downloaded to.
34 * - manifest: The <repo>/<path>:<branch> of the manifest YAML file.
35 *
36 * --- Manifest Overrides ---
37 * The following fields, if present, will override their corresponding values
38 * within the manifest:
39 *
40 * - rabbitMQ: The URL to the RabbitMQ/AMQP server.
41 *
42 */
43export default (settings = {}) => {
44 const userAgent = settings.userAgent || "app-syncer";
45 const token = settings.token;
46 let rabbitMQ = settings.rabbitMQ;
47 let publishEvent;
48
49 const api = {
50 uid: uuid.v4().toString(),
51 apps: [],
52 userAgent,
53 targetFolder: settings.targetFolder || DEFAULT_TARGET_FOLDER,
54
55 /**
56 * Adds a new application to run.
57 * @param {string} id: The unique name of the app (ID).
58 * @param {string} repo: The Github 'username/repo'.
59 * Optionally you can specify a sub-path within the repos
60 * like this:
61 * 'username/repo/my/sub/path'
62 * @param {string} route: Route details for directing requests to the app.
63 * @param {Object} options:
64 * - branch: The branch to query.
65 * Default: "master".
66 */
67 add(id, repo, route, options = {}) {
68 // Setup initial conditions.
69 if (R.find(item => item.id === id, this.apps)) {
70 throw new Error(`An app with the ID '${ id }' has already been registered.`);
71 }
72 if (R.find(item => item.route.toString() === route, this.apps)) {
73 throw new Error(`An app with the route '${ route }' has already been registered.`);
74 }
75
76 // Create the App object.
77 const port = DEFAULT_APP_PORT + (this.apps.length);
78 const item = app({
79 userAgent,
80 token,
81 targetFolder: this.targetFolder,
82 id,
83 repo,
84 route,
85 port,
86 branch: options.branch,
87 publishEvent
88 });
89 this.apps.push(item);
90
91 // Finish up.
92 return this;
93 },
94
95
96 /**
97 * Stops and removes the specified app.
98 * @param id: The unique identifier of the app.
99 * @return {Promise}
100 */
101 remove(id) {
102 return new Promise((resolve, reject) => {
103 Promise.coroutine(function*() {
104 const removeApp = R.find(item => item.id === id, this.apps);
105 if (!removeApp) {
106 reject(new Error(`An app with the id '#{ id }' does not exist.`));
107 } else {
108 log.info(`Removing app '${ id }'`);
109
110 // Stop the app if it's running.
111 yield removeApp.stop();
112
113 // Remove the app from the list.
114 const index = R.findIndex(item => item.id === id, this.apps);
115 this.apps.splice(index, 1);
116
117 // Finish up.
118 resolve({});
119 }
120 }).call(this);
121 });
122 },
123
124
125 /**
126 * Downloads all registered apps.
127 * @param options:
128 * - install: Flag indicating if `npm install` should be run on the directory.
129 * Default: true.
130 * @return {Promise}
131 */
132 download(options = {}) {
133 return new Promise((resolve, reject) => {
134 promises(this.apps.map(item => item.download(options)))
135 .then(result => resolve({ apps: result.results }))
136 .catch(err => reject(err));
137 });
138 },
139
140
141
142 /**
143 * Performs an update on all registered apps.
144 * @param options
145 * - start: Flag indicating if the app should be started after an update.
146 */
147 update(options = {}) {
148 return new Promise((resolve, reject) => {
149 const updatingApps = this.apps.map(item => item.downloading
150 ? null // Don't update an app that is currently downloading.
151 : item.update(options));
152 promises(updatingApps)
153 .then(result => resolve({ apps: result.results }))
154 .catch(err => reject(err));
155 });
156 },
157
158
159
160 /**
161 * Starts the gateway and apps.
162 * @param options
163 * - port: The port to start the gateway on.
164 * @return {Promise}
165 */
166 start(options = {}) {
167 return start(
168 this.apps,
169 (args) => this.update(args),
170 settings.apiRoute,
171 this.manifest,
172 options
173 );
174 },
175
176
177
178 /**
179 * Stops the gateway and all running apps.
180 * @return {Promise}
181 */
182 stop() {
183 log.info("Stopping...");
184 return new Promise((resolve) => {
185 gateway.stop();
186 this.apps.forEach(item => item.stop());
187 log.info("");
188 log.info("Gateway and apps stopped.");
189 resolve({});
190 });
191 }
192 };
193
194 return new Promise((resolve, reject) => {
195 Promise.coroutine(function*() {
196
197 // Download the manifest, if one was set.
198 if (settings.manifest) {
199 api.manifest = manifest(userAgent, token, settings.manifest, api);
200 log.info(`Reading manifest from: ${ api.manifest.repo.fullPath }`);
201 yield api.manifest.get().catch(err => reject(err));
202
203 // Read out global settings.
204 const current = api.manifest.current;
205 if (current) {
206 // Target folder.
207 if (current.targetFolder) {
208 api.targetFolder = current.targetFolder;
209 }
210
211 // Start the RabbitMQ pub-sub module if a URL was specified.
212 if (!rabbitMQ && current.rabbitMQ) {
213 rabbitMQ = current.rabbitMQ;
214 }
215 }
216
217 if (rabbitMQ) {
218 publishEvent = pubSub(api.uid, api.apps, rabbitMQ).publish;
219 }
220 }
221
222 // Finish up.
223 resolve(api);
224
225 }).call(this);
226 });
227};