-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathindex.js
More file actions
321 lines (296 loc) Β· 15.5 KB
/
index.js
File metadata and controls
321 lines (296 loc) Β· 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
const axios = require('axios');
const pkg = require('./package.json');
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* π name (String) *
* ==== *
* *
* The name of the plugin. Typically, this value is the *
* same as the `name` field from `package.json`. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
module.exports.name = pkg.name;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* π options (Object) *
* ======= *
* *
* The options expected by the plugin, as an object. Each *
* key represents an option. The values are objects with *
* one or more of the following keys: *
* *
* - `default` (Any): The value to be used for this option *
* in case one hasn't been supplied. *
* - `env` (String): The name of an environment variable *
* to read the value from. *
* - `private` (Boolean): Whether this option represents *
* sensitive information and therefore should be stored *
* in a `.env` file, rather than the main configuration *
* file. *
* - `runtimeParameter` (String): The name of a runtime *
* parameter (e.g. CLI parameter) to read the value from. *
* When present, the value of the parameter overrides any *
* value defined in the configuration file. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
module.exports.options = {
mySecret: {
// π The value will be read from `process.env.MY_SECRET`.
env: 'MY_SECRET',
// π When running the interactive setup process, this
// option will be stored in an `.env` file instead of the
// main configuration file.
private: true
},
watch: {
// π By default, the value of this option will be `false`.
default: false,
// π The value for this option will be read from the `watch`
// runtime parameter, which means that if the user starts
// Sourcebit with `sourcebit fetch --watch`, then the value
// of this option will be set to `true`, regardless of any
// other value defined in the configuration file.
runtimeParameter: 'watch'
},
titleCase: {
default: false
}
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* π bootstrap (Function) *
* ========= *
* *
* A function to be executed once when the plugin starts. *
* It receives an object with the following properties: *
* *
* - `debug` (Function): A method for printing data that *
* might be useful to see when debugging the plugin. *
* Data sent to this method will be hidden from the user *
* unless the application is in debug mode. *
* - `getPluginContext` (Function): A function for getting *
* the plugin's context object. *
* - `log` (Function): A method for logging a message. It *
* adds a prefix with the name of the plugin that created *
* it, and respects the verbosity settings specified by *
* the user. *
* - `options` (Object): The plugin options object, as they *
* come from the main configuration file, `.env` files *
* and runtime parameters. *
* - `refresh` (Function): A function to be called whenever *
* there are changes in the data managed by the plugin, *
* forcing the entire plugin chain to be re-executed. *
* - `setPluginContext` (Function): A function for setting *
* the plugin's context object *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
module.exports.bootstrap = async ({ debug, getPluginContext, log, options, refresh, setPluginContext }) => {
// π Get the plugin's context object. This is useful for the
// plugin to share any data between its various methods during
// its runtime lifecycle.
// Additionally, it leverages Sourcebit's caching layer, which
// means that whatever a plugin stores in its context will be
// persisted to disk and loaded automatically on the next run.
const context = getPluginContext();
// π If there are entries in the cache, there's nothing that
// needs to be done right now.
if (context && context.entries) {
log(`Loaded ${context.entries.length} entries from cache`);
} else {
const { data: entries } = await axios.get('https://jsonplaceholder.typicode.com/posts');
log(`Loaded ${entries.length} entries`);
debug('Initial entries: %O', entries);
// π Adding the newly-generated entries to the plugin's
// context object.
setPluginContext({
entries
});
}
// π If the `watch` option is enabled, we set up a polling routine
// that checks for changes in the data source. In a real-world plugin,
// you'd be doing things like making regular calls to an API to check
// whenever something changes.
if (options.watch) {
setInterval(() => {
const { entries } = getPluginContext();
const entryIndex = Math.floor(Math.random() * entries.length);
entries[entryIndex].body = entries[entryIndex].body + ' (updated)';
log(`Updated entry #${entryIndex}`);
debug('Updated entries: %O', entries);
// π We take the new entries array and update the plugin context.
setPluginContext({ entries });
// π After updating the context, we must communicate the change and
// the need for all plugins to re-run in order to act on the new data.
refresh();
}, 3000);
}
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* π transform (Function) *
* ========= *
* *
* A function to be executed once when the plugin starts *
* and whenever one of the plugins triggers an update *
* (i.e. by calling `refresh()` inside `bootstrap()`). *
* Its purpose is to receive and transform an object that *
* contains data buckets, which are arrays of entries. *
* Therefore, the return value of this method must be a *
* new data object. *
* Please note that in the first execution, `transform` *
* always runs after `bootstrap()`. *
* It receives an object with the following properties: *
* *
* - `data` (Object): The input data object, containing *
* data buckets. *
* - `debug` (Function): A method for printing data that *
* might be useful to see when debugging the plugin. *
* Data sent to this method will be hidden from the user *
* unless the application is in debug mode. *
* - `getPluginContext` (Function): A function for getting *
* the plugin's context object. *
* - `log` (Function): An alias for `console.log` that adds *
* to the message information about the plugin it comes *
* from. *
* - `options` (Object): The plugin options object, as they *
* come from the main configuration file, `.env` files *
* and runtime parameters. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
module.exports.transform = ({ data, debug, getPluginContext, log, options }) => {
// π Let's retrieve from the plugin's context object the
// entries that we've created in the bootstrap method.
const { entries } = getPluginContext();
// Source plugins are encouraged to add information about their
// models to the `models` data bucket.
const model = {
source: pkg.name,
modelName: 'sample-data',
modelLabel: 'Mock data',
projectId: '12345',
projectEnvironment: 'master',
fieldNames: ['userId', 'id', 'title', 'body']
};
// π The main purpose of this method is to normalize the
// entries, so that they conform to a standardized format
// used by all source plugins.
const normalizedEntries = entries.map(entry => {
const title = options.titleCase
? entry.title
.split(' ')
.map(word => word[0].toUpperCase() + word.substring(1))
.join(' ')
: entry.title;
return {
...entry,
title,
__metadata: model
};
});
// π The method must return the updated data object, which
// in our case means appending our entries to the `objects`
// property.
return {
...data,
models: data.models.concat(model),
objects: data.objects.concat(normalizedEntries)
};
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* π getSetup (Function) *
* ======== *
* *
* A function to be executed as part of the interactive *
* setup process for this plugin. *
* It receives an object with the following properties: *
* *
* - `chalk` (Function): An instance of the `chalk` npm *
* module (https://www.npmjs.com/package/chalk), used in *
* the command-line interface for styling text. *
* - `context` (Object): The global context object, shared *
* by all plugins. *
* - `currentOptions` (Object): The options for this plugin *
* present in an existing configuration file, if found. *
* - `data` (Object): The data object populated by all *
* previous plugins. *
* data buckets. *
* - `debug` (Function): A method for printing data that *
* might be useful to see when debugging the plugin. *
* Data sent to this method will be hidden from the user *
* unless the application is in debug mode. *
* - `getSetupContext` (Function): A function for getting *
* the context object that is shared between all the *
* plugins during the setup process. *
* - `inquirer` (Function): An instance of the `inquirer` *
* npm module (https://www.npmjs.com/package/inquirer), *
* used in the command-line interface to prompt questions *
* to the user. *
* - `ora` (Function): An instance of the `ora` npm module *
* (https://www.npmjs.com/package/ora), used in the *
* command-line interface to display information and *
* error messages, as well as loading states. *
* - `setSetupContext` (Function): A function for setting *
* the context object that is shared between all the *
* plugins during the setup process. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
module.exports.getSetup = ({ chalk, context, currentOptions, data, debug, getSetupContext, inquirer, ora, setSetupContext }) => {
const questions = [
{
type: 'confirm',
name: 'titleCase',
message: 'Do you want to convert the title field to title-case?',
default: currentOptions.pointsForJane || false
}
];
// π For simple setup processes, this method can simply return
// an array of questions in the format expected by `inquirer`.
// Alternatively, it can run its own setup instance, display
// messages, make external calls, etc. For this, it should return
// a function which, when executed, must return a Promise with
// an answers object.
return async () => {
const spinner = ora('Crunching some numbers...').start();
// β³ await runSomeAsyncTask();
spinner.succeed();
const answers = await inquirer.prompt(questions);
return answers;
};
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* π getOptionsFromSetup (Function) *
* =================== *
* *
* A function to be executed after the interactive has *
* finished. *
* It receives an object with the following properties: *
* *
* - `answers` (Object): The answers generated during the *
* interactive setup process. *
* data buckets. *
* - `debug` (Function): A method for printing data that *
* might be useful to see when debugging the plugin. *
* Data sent to this method will be hidden from the user *
* unless the application is in debug mode. *
* - `getSetupContext` (Function): A function for getting *
* the context object that is shared between all the *
* plugins during the setup process. *
* - `setSetupContext` (Function): A function for setting *
* the context object that is shared between all the *
* plugins during the setup process. *
* *
* The return value of this function must be the object *
* that is to be set as the `options` block of the plugin *
* configuration in `sourcebit.js`. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
module.exports.getOptionsFromSetup = ({ answers, debug, getSetupContext, setSetupContext }) => {
// π This is a good place to make some transformation to the
// values generated in the setup process before they're added
// to the configuration file.
return {
titleCase: answers.titleCase
};
};