particle-commands 0.2.12 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.circleci/config.yml +68 -0
  2. package/.eslintrc +1 -2
  3. package/.nvmrc +1 -0
  4. package/CHANGELOG.md +11 -0
  5. package/README.md +93 -1
  6. package/dist/cmd/library_add.js +1 -14
  7. package/dist/cmd/library_contribute.js +0 -10
  8. package/dist/cmd/library_install.js +11 -26
  9. package/dist/cmd/library_migrate.js +3 -4
  10. package/dist/cmd/library_properties.js +3 -4
  11. package/dist/cmd/library_publish.js +0 -8
  12. package/dist/cmd/project_init.js +0 -3
  13. package/dist/cmd/project_properties.js +9 -12
  14. package/dist/cmd/templates/project/README.md +2 -2
  15. package/dist/index.js +9 -28
  16. package/package.json +20 -20
  17. package/src/cmd/library_add.js +2 -8
  18. package/src/cmd/library_contribute.js +0 -3
  19. package/src/cmd/library_install.js +7 -16
  20. package/src/cmd/library_publish.js +0 -3
  21. package/src/cmd/project_init.js +1 -3
  22. package/src/cmd/templates/project/README.md +2 -2
  23. package/src/index.js +0 -8
  24. package/test/cmd/library_install.spec.js +1 -1
  25. package/test/cmd/library_list.spec.js +5 -9
  26. package/test/cmd/library_publish.spec.js +1 -1
  27. package/test/cmd/project_init.spec.js +1 -4
  28. package/test/cmd/projects_spec.js +4 -4
  29. package/.idea/jsLibraryMappings.xml +0 -6
  30. package/.idea/misc.xml +0 -13
  31. package/.idea/modules.xml +0 -8
  32. package/.idea/particle-commands.iml +0 -12
  33. package/.idea/vcs.xml +0 -6
  34. package/.idea/workspace.xml +0 -222
  35. package/.npmignore +0 -1
  36. package/.travis.yml +0 -2
  37. package/dist/lib/analytics.js +0 -229
  38. package/dist/lib/settings.js +0 -410
  39. package/dist/lib/utilities.js +0 -27
  40. package/src/lib/analytics.js +0 -182
  41. package/src/lib/settings.js +0 -359
  42. package/src/lib/utilities.js +0 -24
  43. package/test/lib/analytics.spec.js +0 -158
  44. package/test/lib/settings.spec.js +0 -27
  45. package/yarn.lock +0 -4690
@@ -1,182 +0,0 @@
1
-
2
- const Analytics = require('analytics-node');
3
- const promisify = require('es6-promisify');
4
- const _ = require('lodash');
5
-
6
- const pipeline = require('when/pipeline');
7
-
8
- class CommandContext {
9
-
10
- identifyUser(particleApiClient) {
11
- if (particleApiClient.ready()) {
12
- return particleApiClient.trackingIdentity();
13
- } else {
14
- return Promise.reject();
15
- }
16
- }
17
-
18
- /**
19
- * Determine if the given user descriptor has the requisite identification for a tracking request.
20
- * @param {object} user The user object to check.
21
- * @returns {boolean} true if the identity contains the requisite fields (id, email)
22
- */
23
- isIdentity(user) {
24
- return Boolean(user && user.id && user.email);
25
- }
26
-
27
- /**
28
- * Retrieves the tracking details for the current logged in user.
29
- * @param {function(newValue)} trackingIdentity a fetch-update function for the cached tracking identity for the
30
- * current profile.
31
- * @param {object} apiClient The API Client that provides the tracking identity via "
32
- * @param {function(opts)} clientFactory synchronously create a client
33
- * @return {Promise<object>} promise to retrieve the tracking identity
34
- */
35
- trackingUser(trackingIdentity, apiClient) {
36
- const ident = trackingIdentity();
37
- if (this.isIdentity(ident)) {
38
- return Promise.resolve(ident);
39
- } else {
40
- return this.identifyUser(apiClient)
41
- .then(user => {
42
- if (this.isIdentity(user)) {
43
- trackingIdentity(user);
44
- return user;
45
- } else {
46
- return null;
47
- }
48
- });
49
- }
50
- }
51
-
52
- buildContext({ tool, api, trackingIdentity, apiClient }) {
53
- // todo - allow the API key to be overridden in the environment so that CLI use during development/testing
54
- // is tracked against a distinct source
55
- return pipeline([
56
- () => this.trackingUser(trackingIdentity, apiClient),
57
- (user) => {
58
- return {
59
- user,
60
- tool,
61
- api
62
- };
63
- }
64
- ]);
65
- }
66
- }
67
-
68
- const test = {
69
- CommandContext
70
- };
71
-
72
- /**
73
- *
74
- * @param {object} tool The tool definition { name, version }
75
- * @param {object} api The tracking api details { key }
76
- * @param {function(newValue)} trackingIdentity function to retrieve or update the cached user identity
77
- * @param {object} apiClient The Particle api client that provides a `trackingIdentity` method.
78
- * @returns {Promise<object>} promise to build the command context object containing the tool, api and user attributes.
79
- */
80
- function buildContext({ tool, api, trackingIdentity, apiClient }) {
81
- return new CommandContext().buildContext({ tool, api, trackingIdentity, apiClient });
82
- }
83
-
84
- /**
85
- * Provides command oriented tracking for analytics. This establishes a convention for how
86
- * the calling tool specifies the user, any user traits, the tool name, event date time.
87
- */
88
-
89
- const analyticsCache = {};
90
-
91
- function analyticsFor(key) {
92
- let analytics = analyticsCache[key];
93
- if (!analytics) {
94
- analytics = new Analytics(key, { flushAfter: 10 });
95
- analyticsCache[key] = analytics;
96
- }
97
- return analytics;
98
- }
99
-
100
- function buildProperties(context, properties) {
101
- const tool = _.mapKeys((context.tool || {}), (value, key) => 'tool'+key);
102
- return Object.assign(properties, tool);
103
- }
104
-
105
- function checkApiKey(context) {
106
- if (!context || !context.api || !context.api.key) {
107
- throw new Error('context.api.key not provided'); // todo - distinguish programming/caller errors from runtime errors?
108
- }
109
- return context.api.key;
110
- }
111
-
112
- function checkUserId(context) {
113
- const userId = context && context.user && context.user.id;
114
- const track = context && context.user && context.user.track;
115
- if (!userId && track!==false) {
116
- throw new Error('context.user.id not provided'); // todo - distinguish programming/caller errors from runtime errors?
117
- }
118
- return userId;
119
- }
120
-
121
- function makeAnalytics(context) {
122
- const key = checkApiKey(context);
123
- return analyticsFor(key);
124
- }
125
-
126
- /**
127
- *
128
- * @param {Command} command The command being executed. May be null for tracking events outside of a command
129
- * execution context.
130
- * @param {Object} context The command invocation context. The analytics property should contain
131
- * {tool, user, api}. The api property should include `key` as a minimum, to specify the Segment write key.
132
- * THe tool object must contain at least a `name` property to identify the tool being used. User traits must include
133
- * at least `id` and `name` properties, which are opaque and natural keys used to identify the current user.
134
- * If userTraits is falsey the event is logged anonymously.
135
- * @param {CommandSite} site The command site. This is presently unused but may be later used for context-specific
136
- * functionality.
137
- * @param {String} event The event name to track.
138
- * @param {Object} properties Additional data to log with the event.
139
- * @return {Promise} promise to track
140
- */
141
- function track({ command, context, site, event, properties }) {
142
- const userId = checkUserId(context);
143
- if (!userId) {
144
- return Promise.resolve();
145
- }
146
- const analytics = makeAnalytics(context);
147
- const allProperties = buildProperties(context, properties);
148
-
149
- const track = promisify(analytics.track, { thisArg: analytics });
150
- return track({
151
- userId,
152
- event,
153
- allProperties
154
- });
155
- }
156
-
157
- function identify({ command, context, site, traits }) {
158
- const analytics = makeAnalytics(context);
159
- const userId = checkUserId(context);
160
- const identify = promisify(analytics.identify, { thisArg: analytics });
161
- return identify({
162
- userId,
163
- traits
164
- });
165
- }
166
-
167
- /**
168
- * Ensure that any events have been sent. Returns a promise.
169
- * @return {Promise} to eventually flush
170
- */
171
- function flush() {
172
- return Promise.resolve();
173
- }
174
-
175
- export {
176
- identify,
177
- track,
178
- flush,
179
- buildProperties,
180
- buildContext,
181
- test
182
- };
@@ -1,359 +0,0 @@
1
- const path = require('path');
2
- const _ = require('underscore');
3
- const utilities = require('./utilities');
4
- const fs = require('fs');
5
-
6
- class Settings {
7
-
8
- constructor({ separateSettings=true, settings=[] }={}) {
9
- this.settings = separateSettings ? {} : this; // current configuration settings
10
- this.profile = null; // current profile name
11
- this.profile_json = {}; // parsed contents of profile.json
12
- this.overrides = {}; // current overrides from the profile config file
13
- settings.unshift(this.settings); // add target as first argument
14
- _.extend.apply(_, settings);
15
- }
16
-
17
- get(name) {
18
- return (name===undefined) ? this.settings : this.settings[name];
19
- }
20
-
21
- set(key, value) {
22
- return this.override(null, key, value);
23
- }
24
-
25
- /**
26
- * Retrieve a value from the environment
27
- * @param {string} varName The name of the environment variable to retrieve
28
- * @param {string} defaultValue The value to return when the environment variable is not defined
29
- * @return {string} the value of the environment, or defaultValue if not defined
30
- */
31
- envValue(varName, defaultValue) {
32
- const value = process.env[varName];
33
- return (typeof value === 'undefined') ? defaultValue : value;
34
- }
35
-
36
- /**
37
- * Fetches the value of an environment varaible interpreted as a boolean
38
- * @param {string} varName The environment variable name
39
- * @param {string} defaultValue The default boolean value to return when the environment
40
- * variable is not defined ir is not recognized as a boolean
41
- * @returns {boolean} The value of the environment variable when defined. defaultValue when not defined, or not
42
- * one of true, TRUE, 1, false, FALSE, 0
43
- */
44
- envValueBoolean(varName, defaultValue) {
45
- const value = this.envValue(varName);
46
- if (value === 'true' || value === 'TRUE' || value === '1') {
47
- return true;
48
- } else if (value === 'false' || value === 'FALSE' || value === '0') {
49
- return false;
50
- } else {
51
- return defaultValue;
52
- }
53
- }
54
-
55
- /**
56
- * Determine the user home path from the environment
57
- * @returns {string} The home path of the current user, or
58
- * the directory containing this file if it could not be determined.
59
- */
60
- findHomePath(fs=require('fs')) {
61
- const envVars = [
62
- 'home',
63
- 'HOME',
64
- 'HOMEPATH',
65
- 'USERPROFILE'
66
- ];
67
-
68
- for (let i=0;i<envVars.length;i++) {
69
- const dir = this.envValue(envVars[i]);
70
- if (dir && fs.existsSync(dir)) {
71
- return dir;
72
- }
73
- }
74
- return __dirname;
75
- }
76
-
77
- /**
78
- * Ensures that the particle folder in the home directory exists.
79
- * @returns {string} The location of the particle folder.
80
- */
81
- ensureFolder() {
82
- const particleDir = path.join(this.findHomePath(), '.particle');
83
- if (!fs.existsSync(particleDir)) {
84
- fs.mkdirSync(particleDir);
85
- }
86
- return particleDir;
87
- }
88
-
89
- /**
90
- * Determine the location of a given profile configuratino file
91
- * @param {string} profile The name of the profile
92
- * @returns {string} the path of the overrides file
93
- */
94
- findOverridesFile(profile) {
95
- profile = this.defaultProfile(profile);
96
- const particleDir = this.ensureFolder();
97
- return path.join(particleDir, profile + '.config.json');
98
- }
99
-
100
- defaultProfile(profile) {
101
- return profile || this.profile || 'particle';
102
- }
103
-
104
- loadOverrides(profile) {
105
- profile = this.defaultProfile(profile);
106
-
107
- const filename = this.findOverridesFile(profile);
108
- try {
109
- if (fs.existsSync(filename)) {
110
- this.overrides = JSON.parse(fs.readFileSync(filename));
111
- // need to do an in-situ extend since external clients may have already obtained the settings object
112
- // this.settings = extend(this.settings, this.overrides);
113
- _.extend(this.settings, this.overrides);
114
- }
115
- } catch (ex) {
116
- console.error('There was an error reading ' + filename + ': ', ex);
117
- }
118
- return this;
119
- }
120
-
121
- /**
122
- * ?? Reads the default profile
123
- */
124
- whichProfile() {
125
- this.profile = 'particle';
126
- this.readProfileData();
127
- }
128
-
129
- /**
130
- * in another file in our user dir, we store a profile name that switches between setting override files
131
- * @param {string} profileName The name of the profile to switch to
132
- */
133
- switchProfile(profileName) {
134
- this.profile_json.name = profileName;
135
- this.saveProfileData();
136
- }
137
-
138
- /**
139
- * Reads "profile.json" and saves its content to profile_json
140
- * @return {undefined} nothing but keep the linter happy
141
- */
142
- readProfileData(fs=require('fs')) {
143
- const particleDir = this.ensureFolder();
144
- const proFile = path.join(particleDir, 'profile.json'); //proFile, get it?
145
- if (fs.existsSync(proFile)) {
146
- try {
147
- const data = JSON.parse(fs.readFileSync(proFile));
148
- this.profile = (data) ? data.name : 'particle';
149
- this.profile_json = data;
150
- } catch (err) {
151
- throw new Error('Error parsing file '+proFile+': '+err);
152
- }
153
- } else {
154
- this.profile = 'particle';
155
- this.profile_json = {};
156
- }
157
- }
158
-
159
- /**
160
- * Saves profile.json
161
- */
162
- saveProfileData(fs=require('fs')) {
163
- const particleDir = this.ensureFolder();
164
- const proFile = path.join(particleDir, 'profile.json'); //proFile, get it?
165
- fs.writeFileSync(proFile, JSON.stringify(this.profile_json, null, 2), { mode: '600' });
166
- }
167
-
168
-
169
- // this is here instead of utilities to prevent a require-loop
170
- // when files that utilties requires need settings
171
- matchKey(needle, obj, caseInsensitive) {
172
- needle = (caseInsensitive) ? needle.toLowerCase() : needle;
173
- for (let key in obj) {
174
- const keyCopy = (caseInsensitive) ? key.toLowerCase() : key;
175
- if (keyCopy === needle) {
176
- return key; //return the original
177
- }
178
- }
179
- return null;
180
- }
181
-
182
-
183
- override(profile, key, value) {
184
- if (!this.overrides) {
185
- this.overrides = {};
186
- }
187
-
188
- if (this.settings[key]===undefined) {
189
- // find any key that matches our key, regardless of case
190
- const realKey = this.matchKey(key, this.settings, true);
191
- if (realKey) {
192
- //console.log("Using the setting \"" + realKey + "\" instead ");
193
- key = realKey;
194
- }
195
- }
196
-
197
- if (value===undefined) {
198
- delete this.settings[key];
199
- delete this.overrides[key];
200
- } else {
201
- //store the new value (redundant)
202
- this.settings[key] = value;
203
-
204
- //store that in overrides
205
- this.overrides[key] = value;
206
- }
207
- //make sure our overrides are in sync
208
- _.extend(this.settings, this.overrides);
209
-
210
- const filename = this.findOverridesFile(profile);
211
- try {
212
- fs.writeFileSync(filename, JSON.stringify(this.overrides, null, 2), { mode: '600' });
213
- } catch (ex) {
214
- console.error('There was an error writing ' + filename + ': ', ex);
215
- }
216
- }
217
-
218
- transitionSparkProfiles(fs=require('fs'), notifyTranslate) {
219
- const sparkDir = path.join(this.findHomePath(), '.spark');
220
- const particleDir = path.join(this.findHomePath(), '.particle');
221
- if (fs.existsSync(sparkDir) && !fs.existsSync(particleDir)) {
222
- fs.mkdirSync(particleDir);
223
-
224
- if (notifyTranslate) {
225
- notifyTranslate(sparkDir, particleDir);
226
- }
227
-
228
- const files = fs.readdirSync(sparkDir);
229
- files.forEach((filename) => {
230
- const data = fs.readFileSync(path.join(sparkDir, filename));
231
- let jsonData;
232
- try {
233
- jsonData = JSON.parse(data);
234
- } catch (ex) {
235
- // invalid JSON, don't transition
236
- return;
237
- }
238
-
239
- if (filename === 'profile.json') {
240
- if (jsonData.name === 'spark') {
241
- jsonData.name = 'particle';
242
- }
243
- }
244
-
245
- if (filename === 'spark.config.json') {
246
- filename = 'particle.config.json';
247
- }
248
-
249
- if (jsonData.apiUrl && jsonData.apiUrl.indexOf('.spark.io') > 0) {
250
- jsonData.apiUrl = jsonData.apiUrl.replace('.spark.io', '.particle.io');
251
- }
252
-
253
- const jsonString = JSON.stringify(jsonData, null, 2);
254
- fs.writeFileSync(path.join(particleDir, filename), jsonString, { mode: '600' });
255
- });
256
- }
257
- }
258
-
259
- listConfigs(particleDir = this.ensureFolder()) {
260
- const files = utilities.globList(null, [
261
- path.join(particleDir, '*.config.json')
262
- ]);
263
-
264
- return files.map(item => {
265
- const filename = path.basename(item);
266
- //strip the extension
267
- const name = filename.replace('.config.json', '');
268
- return name;
269
- });
270
- }
271
-
272
- /**
273
- * Retrieves a function that can retrieve or set a value.
274
- * @param {string} key The key of the setting to fetch/update
275
- * @returns {function(newValue)} A function that fetches the current value, or updates if an argument
276
- * is defined.
277
- */
278
- fetchUpdate(key) {
279
- function fetchUpdateKey(key, value) {
280
- if (value!==undefined) {
281
- this.set(key, value);
282
- return value;
283
- } else {
284
- return this.get(key);
285
- }
286
- }
287
- return fetchUpdateKey.bind(this, key);
288
- }
289
- }
290
-
291
- const defaultSettings = {
292
- apiUrl: 'https://api.particle.io',
293
- buildUrl: 'https://build.particle.io',
294
- access_token: null,
295
-
296
- notSourceExtensions: [
297
- '.ds_store',
298
- '.jpg',
299
- '.gif',
300
- '.png',
301
- '.include',
302
- '.ignore',
303
- '.ds_store',
304
- '.git',
305
- '.bin'
306
- ],
307
-
308
- dirIncludeFilename: 'particle.include',
309
- dirExcludeFilename: 'particle.ignore',
310
-
311
- knownApps: {
312
- 'deep_update_2014_06': true,
313
- 'cc3000': true,
314
- 'cc3000_1_14': true,
315
- 'tinker': true,
316
- 'voodoo': true
317
- },
318
- knownPlatforms: {
319
- 0: 'Core',
320
- 6: 'Photon',
321
- 8: 'P1',
322
- 10: 'Electron',
323
- 88: 'Duo',
324
- 103: 'Bluz'
325
- },
326
- updates: {
327
- '2b04:d006': {
328
- systemFirmwareOne: 'system-part1-0.6.1-photon.bin',
329
- systemFirmwareTwo: 'system-part2-0.6.1-photon.bin'
330
- },
331
- '2b04:d008': {
332
- systemFirmwareOne: 'system-part1-0.6.1-p1.bin',
333
- systemFirmwareTwo: 'system-part2-0.6.1-p1.bin'
334
- },
335
- '2b04:d00a': {
336
- // The bin files MUST be in this order to be flashed to the correct memory locations
337
- systemFirmwareOne: 'system-part2-0.6.1-electron.bin',
338
- systemFirmwareTwo: 'system-part3-0.6.1-electron.bin',
339
- systemFirmwareThree: 'system-part1-0.6.1-electron.bin'
340
- }
341
- }
342
- };
343
-
344
- function buildSettings(separateSettings, initialSettings, skipLoadProfile) {
345
- const settings = new Settings({ separateSettings, settings:[defaultSettings, initialSettings] });
346
- if (!skipLoadProfile) {
347
- settings.transitionSparkProfiles();
348
- settings.whichProfile();
349
- settings.loadOverrides();
350
- }
351
- return settings;
352
- }
353
-
354
-
355
- export {
356
- buildSettings,
357
- defaultSettings,
358
- Settings
359
- };
@@ -1,24 +0,0 @@
1
- const glob = require('glob');
2
- const path = require('path');
3
-
4
- function globList(basepath, arr) {
5
- let line, found, files = [];
6
- for (let i=0;i<arr.length;i++) {
7
- line = arr[i];
8
- if (basepath) {
9
- line = path.join(basepath, line);
10
- }
11
- found = glob.sync(line, { nodir: true, follow: true });
12
-
13
- if (found && (found.length > 0)) {
14
- files = files.concat(found);
15
- }
16
- }
17
- return files;
18
- }
19
-
20
- export {
21
- globList
22
- };
23
-
24
-