@algolia/cli 4.0.7 → 5.10.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.
- package/README.md +42 -0
- package/bin/run.js +26 -0
- package/package.json +26 -78
- package/commands/AddRules.js +0 -72
- package/commands/AddSynonyms.js +0 -89
- package/commands/Base.js +0 -97
- package/commands/DeleteIndicesPattern.js +0 -104
- package/commands/Export.js +0 -117
- package/commands/ExportRules.js +0 -61
- package/commands/ExportSynonyms.js +0 -61
- package/commands/GetSettings.js +0 -38
- package/commands/Import.js +0 -379
- package/commands/Interactive.js +0 -60
- package/commands/Search.js +0 -68
- package/commands/SetSettings.js +0 -72
- package/commands/TransferIndex.js +0 -126
- package/commands/TransferIndexConfig.js +0 -108
- package/commands/TransformLines.js +0 -136
- package/commands.js +0 -16
- package/index.js +0 -373
- package/readme.md +0 -656
package/commands/ExportRules.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const algolia = require('algoliasearch');
|
|
4
|
-
const Base = require('./Base.js');
|
|
5
|
-
|
|
6
|
-
class ExportRulesScript extends Base {
|
|
7
|
-
constructor() {
|
|
8
|
-
super();
|
|
9
|
-
// Bind class methods
|
|
10
|
-
this.getOutputPath = this.getOutputPath.bind(this);
|
|
11
|
-
this.start = this.start.bind(this);
|
|
12
|
-
// Define validation constants
|
|
13
|
-
this.message =
|
|
14
|
-
'\nExample: $ algolia exportrules -a algoliaappid -k algoliaapikey -n algoliaindexname -o outputpath\n\n';
|
|
15
|
-
this.params = ['algoliaappid', 'algoliaapikey', 'algoliaindexname'];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
getOutputPath(outputpath, indexName) {
|
|
19
|
-
const defaultFilename = `${indexName}-rules.json`;
|
|
20
|
-
const defaultFilepath = path.resolve(process.cwd(), defaultFilename);
|
|
21
|
-
// Process output filepath
|
|
22
|
-
const filepath =
|
|
23
|
-
outputpath !== null ? this.normalizePath(outputpath) : defaultFilepath;
|
|
24
|
-
// Validate filepath targets valid directory
|
|
25
|
-
const dir = path.dirname(filepath);
|
|
26
|
-
if (!fs.lstatSync(dir).isDirectory()) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
`Output path must target valid directory. Eg. ${defaultFilepath}`
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
return filepath;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async start(program) {
|
|
35
|
-
try {
|
|
36
|
-
// Validate command; if invalid display help text and exit
|
|
37
|
-
this.validate(program, this.message, this.params);
|
|
38
|
-
|
|
39
|
-
// Config params
|
|
40
|
-
const appId = program.algoliaappid;
|
|
41
|
-
const apiKey = program.algoliaapikey;
|
|
42
|
-
const indexName = program.algoliaindexname;
|
|
43
|
-
const outputpath = program.outputpath || null;
|
|
44
|
-
|
|
45
|
-
const filepath = this.getOutputPath(outputpath, indexName);
|
|
46
|
-
|
|
47
|
-
// Instantiate Algolia index
|
|
48
|
-
const client = algolia(appId, apiKey);
|
|
49
|
-
const index = client.initIndex(indexName);
|
|
50
|
-
// Get index settings
|
|
51
|
-
const rules = await index.exportRules();
|
|
52
|
-
fs.writeFileSync(filepath, JSON.stringify(rules));
|
|
53
|
-
return console.log(`Done writing ${filepath}`);
|
|
54
|
-
} catch (e) {
|
|
55
|
-
throw e;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const exportRulesScript = new ExportRulesScript();
|
|
61
|
-
module.exports = exportRulesScript;
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const algolia = require('algoliasearch');
|
|
4
|
-
const Base = require('./Base.js');
|
|
5
|
-
|
|
6
|
-
class ExportSynonymsScript extends Base {
|
|
7
|
-
constructor() {
|
|
8
|
-
super();
|
|
9
|
-
// Bind class methods
|
|
10
|
-
this.getOutputPath = this.getOutputPath.bind(this);
|
|
11
|
-
this.start = this.start.bind(this);
|
|
12
|
-
// Define validation constants
|
|
13
|
-
this.message =
|
|
14
|
-
'\nExample: $ algolia exportsynonyms -a algoliaappid -k algoliaapikey -n algoliaindexname -o outputpath\n\n';
|
|
15
|
-
this.params = ['algoliaappid', 'algoliaapikey', 'algoliaindexname'];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
getOutputPath(outputpath, indexName) {
|
|
19
|
-
const defaultFilename = `${indexName}-synonyms.json`;
|
|
20
|
-
const defaultFilepath = path.resolve(process.cwd(), defaultFilename);
|
|
21
|
-
// Process output filepath
|
|
22
|
-
const filepath =
|
|
23
|
-
outputpath !== null ? this.normalizePath(outputpath) : defaultFilepath;
|
|
24
|
-
// Validate filepath targets valid directory
|
|
25
|
-
const dir = path.dirname(filepath);
|
|
26
|
-
if (!fs.lstatSync(dir).isDirectory()) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
`Output path must target valid directory. Eg. ${defaultFilepath}`
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
return filepath;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async start(program) {
|
|
35
|
-
try {
|
|
36
|
-
// Validate command; if invalid display help text and exit
|
|
37
|
-
this.validate(program, this.message, this.params);
|
|
38
|
-
|
|
39
|
-
// Config params
|
|
40
|
-
const appId = program.algoliaappid;
|
|
41
|
-
const apiKey = program.algoliaapikey;
|
|
42
|
-
const indexName = program.algoliaindexname;
|
|
43
|
-
const outputpath = program.outputpath || null;
|
|
44
|
-
|
|
45
|
-
const filepath = this.getOutputPath(outputpath, indexName);
|
|
46
|
-
|
|
47
|
-
// Instantiate Algolia index
|
|
48
|
-
const client = algolia(appId, apiKey);
|
|
49
|
-
const index = client.initIndex(indexName);
|
|
50
|
-
// Get index settings
|
|
51
|
-
const synonyms = await index.exportSynonyms();
|
|
52
|
-
fs.writeFileSync(filepath, JSON.stringify(synonyms));
|
|
53
|
-
return console.log(`Done writing ${filepath}`);
|
|
54
|
-
} catch (e) {
|
|
55
|
-
throw e;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const exportSynonymsScript = new ExportSynonymsScript();
|
|
61
|
-
module.exports = exportSynonymsScript;
|
package/commands/GetSettings.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
const algolia = require('algoliasearch');
|
|
2
|
-
const Base = require('./Base.js');
|
|
3
|
-
|
|
4
|
-
class GetSettingsScript extends Base {
|
|
5
|
-
constructor() {
|
|
6
|
-
super();
|
|
7
|
-
// Bind class methods
|
|
8
|
-
this.start = this.start.bind(this);
|
|
9
|
-
// Define validation constants
|
|
10
|
-
this.message =
|
|
11
|
-
'\nExample: $ algolia getsettings -a algoliaappid -k algoliaapikey -n algoliaindexname\n\n';
|
|
12
|
-
this.params = ['algoliaappid', 'algoliaapikey', 'algoliaindexname'];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async start(program) {
|
|
16
|
-
try {
|
|
17
|
-
// Validate command; if invalid display help text and exit
|
|
18
|
-
this.validate(program, this.message, this.params);
|
|
19
|
-
|
|
20
|
-
// Config params
|
|
21
|
-
const appId = program.algoliaappid;
|
|
22
|
-
const apiKey = program.algoliaapikey;
|
|
23
|
-
const indexName = program.algoliaindexname;
|
|
24
|
-
|
|
25
|
-
// Instantiate Algolia index
|
|
26
|
-
const client = algolia(appId, apiKey);
|
|
27
|
-
const index = client.initIndex(indexName);
|
|
28
|
-
// Get index settings
|
|
29
|
-
const settings = await index.getSettings();
|
|
30
|
-
return console.log(JSON.stringify(settings));
|
|
31
|
-
} catch (e) {
|
|
32
|
-
throw e;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const getSettingsScript = new GetSettingsScript();
|
|
38
|
-
module.exports = getSettingsScript;
|
package/commands/Import.js
DELETED
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const JSONStream = require('JSONStream');
|
|
3
|
-
const through = require('through');
|
|
4
|
-
const transform = require('stream-transform');
|
|
5
|
-
const Batch = require('batch-stream');
|
|
6
|
-
const async = require('async');
|
|
7
|
-
const csv = require('csvtojson');
|
|
8
|
-
const regexParser = require('regex-parser');
|
|
9
|
-
const chalk = require('chalk');
|
|
10
|
-
const algolia = require('algoliasearch');
|
|
11
|
-
const Base = require('./Base.js');
|
|
12
|
-
|
|
13
|
-
class ImportScript extends Base {
|
|
14
|
-
constructor() {
|
|
15
|
-
super();
|
|
16
|
-
// Bind class methods
|
|
17
|
-
this.defaultTransformations = this.defaultTransformations.bind(this);
|
|
18
|
-
this.suggestions = this.suggestions.bind(this);
|
|
19
|
-
this.checkMemoryUsage = this.checkMemoryUsage.bind(this);
|
|
20
|
-
this.handleHighMemoryUsage = this.handleHighMemoryUsage.bind(this);
|
|
21
|
-
this.handleExtremeMemoryUsage = this.handleExtremeMemoryUsage.bind(this);
|
|
22
|
-
this.setIndex = this.setIndex.bind(this);
|
|
23
|
-
this.setTransformations = this.setTransformations.bind(this);
|
|
24
|
-
this.setCsvOptions = this.setCsvOptions.bind(this);
|
|
25
|
-
this.conditionallyParseCsv = this.conditionallyParseCsv.bind(this);
|
|
26
|
-
this.setBatchSize = this.setBatchSize.bind(this);
|
|
27
|
-
this.estimateBatchSize = this.estimateBatchSize.bind(this);
|
|
28
|
-
this.updateBatchSize = this.updateBatchSize.bind(this);
|
|
29
|
-
this.importToAlgolia = this.importToAlgolia.bind(this);
|
|
30
|
-
this.retryImport = this.retryImport.bind(this);
|
|
31
|
-
this.indexFiles = this.indexFiles.bind(this);
|
|
32
|
-
this.start = this.start.bind(this);
|
|
33
|
-
// Define validation constants
|
|
34
|
-
this.message =
|
|
35
|
-
'\nExample: $ algolia import -s sourcefilepath -a algoliaappid -k algoliaapikey -n algoliaindexname -b batchsize -t transformationfilepath -m maxconcurrency -p csvtojsonparams\n\n';
|
|
36
|
-
this.params = [
|
|
37
|
-
'sourcefilepath',
|
|
38
|
-
'algoliaappid',
|
|
39
|
-
'algoliaapikey',
|
|
40
|
-
'algoliaindexname',
|
|
41
|
-
];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
defaultTransformations(data, cb) {
|
|
45
|
-
cb(null, data);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
suggestions() {
|
|
49
|
-
let output = `\nConsider reducing <batchSize> (currently ${
|
|
50
|
-
this.batchSize
|
|
51
|
-
}).`;
|
|
52
|
-
if (this.maxConcurrency > 1)
|
|
53
|
-
output += `\nConsider reducing <maxConcurrency> (currently ${
|
|
54
|
-
this.maxConcurrency
|
|
55
|
-
}).`;
|
|
56
|
-
return output;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
checkMemoryUsage() {
|
|
60
|
-
// Exit early if high memory usage warning issued too recently
|
|
61
|
-
if (this.highMemoryUsage) return false;
|
|
62
|
-
// Get memory usage
|
|
63
|
-
const { usedMb, percentUsed } = this.getMemoryUsage();
|
|
64
|
-
// Handle if heap usage exceeds n% of estimated allocation for node process
|
|
65
|
-
if (percentUsed >= 70) this.handleHighMemoryUsage(percentUsed);
|
|
66
|
-
if (percentUsed >= 90) this.handleExtremeMemoryUsage(usedMb, percentUsed);
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
handleHighMemoryUsage(percentUsed) {
|
|
71
|
-
const newBatchSize = Math.floor(this.batchSize / 2);
|
|
72
|
-
this.updateBatchSize(newBatchSize);
|
|
73
|
-
this.writeProgress(
|
|
74
|
-
`High memory usage (${percentUsed}%). Reducing batchSize to ${newBatchSize}`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
handleExtremeMemoryUsage(usedMb, percentUsed) {
|
|
79
|
-
// Issue warning
|
|
80
|
-
const name = `Warning: High memory usage`;
|
|
81
|
-
const message = `Memory usage at ${usedMb} MB (${percentUsed}% of heap allocation for this process).`;
|
|
82
|
-
// Set class instance flag to debounce future warnings
|
|
83
|
-
this.highMemoryUsage = true;
|
|
84
|
-
// Output warning
|
|
85
|
-
console.log(
|
|
86
|
-
chalk.white.bgRed(`\n${name}`),
|
|
87
|
-
chalk.red(`\n${message}`),
|
|
88
|
-
chalk.red(`${this.suggestions()}`)
|
|
89
|
-
);
|
|
90
|
-
// Reset flag in 30 seconds
|
|
91
|
-
setTimeout(() => {
|
|
92
|
-
this.highMemoryUsage = false;
|
|
93
|
-
}, 30000);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
setIndex(options) {
|
|
97
|
-
// Set Algolia index
|
|
98
|
-
this.client = algolia(options.appId, options.apiKey);
|
|
99
|
-
this.index = this.client.initIndex(options.indexName);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
setTransformations(options) {
|
|
103
|
-
try {
|
|
104
|
-
// Set JSON record transformations
|
|
105
|
-
const transformations = options.transformations
|
|
106
|
-
? require(this.normalizePath(options.transformations))
|
|
107
|
-
: null;
|
|
108
|
-
// Validate transformations function input param
|
|
109
|
-
const valid = transformations && typeof transformations === 'function';
|
|
110
|
-
// Assign our transformations function using provided custom transformations file if exists
|
|
111
|
-
this.formatRecord = valid ? transformations : this.defaultTransformations;
|
|
112
|
-
} catch (e) {
|
|
113
|
-
throw e;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
setCsvOptions(options) {
|
|
118
|
-
try {
|
|
119
|
-
this.csvOptions = options.csvToJsonParams
|
|
120
|
-
? JSON.parse(options.csvToJsonParams)
|
|
121
|
-
: null;
|
|
122
|
-
if (!this.csvOptions) return;
|
|
123
|
-
const csvToJsonRegexPropertyList = ['includeColumns', 'ignoreColumns'];
|
|
124
|
-
csvToJsonRegexPropertyList.forEach(prop => {
|
|
125
|
-
if (this.csvOptions.hasOwnProperty(prop)) {
|
|
126
|
-
this.csvOptions[prop] = regexParser(this.csvOptions[prop]);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
} catch (e) {
|
|
130
|
-
throw e;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
conditionallyParseCsv(isCsv) {
|
|
135
|
-
// Return the appropriate writestream for piping depending on filetype
|
|
136
|
-
return isCsv
|
|
137
|
-
? csv(this.csvOptions) // Convert from CSV to JSON
|
|
138
|
-
: through(); // Do nothing
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async setBatchSize(options) {
|
|
142
|
-
try {
|
|
143
|
-
// If user provided batchSize, use and exit early
|
|
144
|
-
// Otherwise calculate and set optimal batch size
|
|
145
|
-
if (options.objectsPerBatch !== null) {
|
|
146
|
-
this.batchSize = options.objectsPerBatch;
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
// Test files to estimate optimal batch size
|
|
150
|
-
const estimatedBatchSize = await this.estimateBatchSize();
|
|
151
|
-
// Test network upload speed
|
|
152
|
-
const uploadSpeedMb = await this.getNetworkSpeed();
|
|
153
|
-
// Calculate optimal batch size
|
|
154
|
-
this.writeProgress('Calculating optimal batch size...');
|
|
155
|
-
let batchSize;
|
|
156
|
-
// Reconcile batch size with network speed
|
|
157
|
-
if (uploadSpeedMb >= this.desiredBatchSizeMb)
|
|
158
|
-
batchSize = Math.floor(estimatedBatchSize);
|
|
159
|
-
else
|
|
160
|
-
batchSize = Math.floor(
|
|
161
|
-
(uploadSpeedMb / this.desiredBatchSizeMb) * estimatedBatchSize
|
|
162
|
-
);
|
|
163
|
-
// Ensure minimum batch size is enforced
|
|
164
|
-
batchSize = Math.max(this.minBatchSize, batchSize);
|
|
165
|
-
console.log(chalk.blue(`\nOptimal batch size: ${batchSize}`));
|
|
166
|
-
// Set batch size
|
|
167
|
-
this.batchSize = batchSize;
|
|
168
|
-
} catch (e) {
|
|
169
|
-
throw e;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
estimateBatchSize() {
|
|
174
|
-
// Read file, estimate average record size, estimate batch size
|
|
175
|
-
// Return estimated batch size divided by maxConcurrency
|
|
176
|
-
return new Promise((resolve, reject) => {
|
|
177
|
-
try {
|
|
178
|
-
const filename = this.filenames[0];
|
|
179
|
-
const file = `${this.directory}/${filename}`;
|
|
180
|
-
const isCsv = filename.split('.').pop() === 'csv';
|
|
181
|
-
const fileStream = fs.createReadStream(file, {
|
|
182
|
-
autoclose: true,
|
|
183
|
-
flags: 'r',
|
|
184
|
-
});
|
|
185
|
-
this.writeProgress(`Estimating data size...`);
|
|
186
|
-
const jsonStreamOption = isCsv ? null : '*';
|
|
187
|
-
fileStream
|
|
188
|
-
.pipe(this.conditionallyParseCsv(isCsv))
|
|
189
|
-
.pipe(JSONStream.parse(jsonStreamOption))
|
|
190
|
-
.pipe(transform(this.formatRecord))
|
|
191
|
-
.pipe(new Batch({ size: 10000 }))
|
|
192
|
-
.pipe(
|
|
193
|
-
through(data => {
|
|
194
|
-
const count = data.length;
|
|
195
|
-
const string = JSON.stringify(data);
|
|
196
|
-
const batchSizeMb = this.getStringSizeMb(string);
|
|
197
|
-
const avgRecordSizeMb = batchSizeMb / count;
|
|
198
|
-
const avgRecordSizeKb = Math.ceil(avgRecordSizeMb * 1000);
|
|
199
|
-
const roughBatchSize = this.desiredBatchSizeMb / avgRecordSizeMb;
|
|
200
|
-
const estimatedBatchSize = Math.floor(
|
|
201
|
-
roughBatchSize / this.maxConcurrency
|
|
202
|
-
);
|
|
203
|
-
console.log(
|
|
204
|
-
chalk.blue(`\nAverage record size: ${avgRecordSizeKb} Kb`)
|
|
205
|
-
);
|
|
206
|
-
fileStream.destroy();
|
|
207
|
-
resolve(estimatedBatchSize);
|
|
208
|
-
})
|
|
209
|
-
);
|
|
210
|
-
} catch (e) {
|
|
211
|
-
reject(e);
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
updateBatchSize(newSize) {
|
|
217
|
-
this.batchSize = newSize;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
getBatchStream() {
|
|
221
|
-
return new Batch({ size: this.batchSize });
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async importToAlgolia(data) {
|
|
225
|
-
// Method to index batches of records in Algolia
|
|
226
|
-
try {
|
|
227
|
-
await this.index.addObjects(data);
|
|
228
|
-
this.importCount += data.length;
|
|
229
|
-
this.writeProgress(`Records indexed: ${this.importCount}`);
|
|
230
|
-
} catch (e) {
|
|
231
|
-
let message = e.message;
|
|
232
|
-
let addendum = e.stack;
|
|
233
|
-
if (e.name === 'AlgoliaSearchRequestTimeoutError') {
|
|
234
|
-
message = `You may be attempting to import batches too large for the network connection.`;
|
|
235
|
-
addendum = this.suggestions();
|
|
236
|
-
this.retryImport(data);
|
|
237
|
-
}
|
|
238
|
-
console.log(
|
|
239
|
-
chalk.white.bgRed(`\nImport error: ${e.name}`),
|
|
240
|
-
chalk.red(`\n${message}`),
|
|
241
|
-
chalk.red(addendum)
|
|
242
|
-
);
|
|
243
|
-
throw e;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
retryImport(data) {
|
|
248
|
-
// Algolia import retry strategy
|
|
249
|
-
try {
|
|
250
|
-
this.retryCount++;
|
|
251
|
-
console.log(`\n(${this.retryCount}) Retrying batch...`);
|
|
252
|
-
const importedBatchCount = Math.floor(this.importCount / this.batchSize);
|
|
253
|
-
const retryLimit =
|
|
254
|
-
this.retryCount > 15 && this.retryCount > importedBatchCount / 2;
|
|
255
|
-
if (retryLimit) {
|
|
256
|
-
console.log(
|
|
257
|
-
chalk.white.bgRed(`\nError: Failure to index data`),
|
|
258
|
-
chalk.red(`\nRetry limit reached.`),
|
|
259
|
-
chalk.red(this.suggestions())
|
|
260
|
-
);
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
// Split data in half
|
|
264
|
-
const middle = Math.floor(data.length / 2);
|
|
265
|
-
const firstHalf = data.splice(0, middle);
|
|
266
|
-
// Reduce batchsize
|
|
267
|
-
if (this.batchSize > middle) this.updateBatchSize(middle);
|
|
268
|
-
// Push each half of data into import queue
|
|
269
|
-
this.queue.push([firstHalf]);
|
|
270
|
-
this.queue.push([data]);
|
|
271
|
-
} catch (e) {
|
|
272
|
-
console.error('Retry error:', e);
|
|
273
|
-
throw e;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
indexFiles(filenames) {
|
|
278
|
-
// Recursive method that iterates through an array of filenames, opens a read stream for each file
|
|
279
|
-
// then pipes the read stream through a series of transformations (parse CSV/JSON objects, transform
|
|
280
|
-
// them, batch them, index them in Algolia) while imposing a queue so that only so many
|
|
281
|
-
// indexing threads will be run in parallel
|
|
282
|
-
if (filenames.length <= 0) {
|
|
283
|
-
console.log('\nDone reading files');
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
// Start new file read stream
|
|
287
|
-
// Note: filenames is a reference to the mutable class instance variable this.filenames
|
|
288
|
-
const filename = filenames.pop();
|
|
289
|
-
const file = `${this.directory}/${filename}`;
|
|
290
|
-
const isCsv = filename.split('.').pop() === 'csv';
|
|
291
|
-
const fileStream = fs.createReadStream(file, {
|
|
292
|
-
autoclose: true,
|
|
293
|
-
flags: 'r',
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
fileStream.on('data', () => {
|
|
297
|
-
if (this.queue.length() >= this.maxConcurrency) {
|
|
298
|
-
// If async upload queue is full, pause reading from file stream
|
|
299
|
-
fileStream.pause();
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
fileStream.on('end', () => {
|
|
304
|
-
// File complete, process next file
|
|
305
|
-
this.indexFiles(filenames);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// Once the async upload queue is drained, resume reading from file stream
|
|
309
|
-
this.queue.drain = () => {
|
|
310
|
-
fileStream.resume();
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
// Handle parsing, transforming, batching, and indexing JSON and CSV files
|
|
314
|
-
console.log(`\nImporting [${filename}]`);
|
|
315
|
-
const jsonStreamOption = isCsv ? null : '*';
|
|
316
|
-
fileStream
|
|
317
|
-
.pipe(this.conditionallyParseCsv(isCsv, filename))
|
|
318
|
-
.pipe(JSONStream.parse(jsonStreamOption))
|
|
319
|
-
.pipe(transform(this.formatRecord))
|
|
320
|
-
.pipe(this.getBatchStream())
|
|
321
|
-
.pipe(
|
|
322
|
-
through(data => {
|
|
323
|
-
this.checkMemoryUsage();
|
|
324
|
-
this.queue.push([data]);
|
|
325
|
-
})
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
async start(program) {
|
|
330
|
-
// Script reads JSON or CSV file, or directory of such files, optionally applies
|
|
331
|
-
// transformations, then batches and indexes the data in Algolia.
|
|
332
|
-
|
|
333
|
-
// Validate command; if invalid display help text and exit
|
|
334
|
-
this.validate(program, this.message, this.params);
|
|
335
|
-
|
|
336
|
-
// Config params
|
|
337
|
-
const options = {
|
|
338
|
-
sourceFilepath: program.sourcefilepath,
|
|
339
|
-
appId: program.algoliaappid,
|
|
340
|
-
apiKey: program.algoliaapikey,
|
|
341
|
-
indexName: program.algoliaindexname,
|
|
342
|
-
objectsPerBatch: program.batchsize || null,
|
|
343
|
-
transformations: program.transformationfilepath || null,
|
|
344
|
-
maxConcurrency: program.maxconcurrency || 2,
|
|
345
|
-
csvToJsonParams: program.params || null,
|
|
346
|
-
};
|
|
347
|
-
// Configure Algolia (this.client, this.index)
|
|
348
|
-
this.setIndex(options);
|
|
349
|
-
// Configure source paths (this.directory, this.filenames)
|
|
350
|
-
this.setSource(options);
|
|
351
|
-
// Configure transformations (this.formatRecord)
|
|
352
|
-
this.setTransformations(options);
|
|
353
|
-
// Configure optional csvtojson params (this.csvOptions)
|
|
354
|
-
this.setCsvOptions(options);
|
|
355
|
-
// Configure data upload parameters
|
|
356
|
-
this.maxConcurrency = options.maxConcurrency;
|
|
357
|
-
// Theoretically desirable batch size in MB
|
|
358
|
-
this.desiredBatchSizeMb = 10;
|
|
359
|
-
// Minimum batch size
|
|
360
|
-
this.minBatchSize = 100;
|
|
361
|
-
// Configure number of records to index per batch (this.batchSize, this.batch)
|
|
362
|
-
await this.setBatchSize(options);
|
|
363
|
-
// Assign dangerous memory usage flag
|
|
364
|
-
this.highMemoryUsage = false;
|
|
365
|
-
// Assign import count
|
|
366
|
-
this.importCount = 0;
|
|
367
|
-
// Assign retry count
|
|
368
|
-
this.retryCount = 0;
|
|
369
|
-
// Assign async queue
|
|
370
|
-
this.queue = async.queue(this.importToAlgolia, this.maxConcurrency);
|
|
371
|
-
|
|
372
|
-
// Execute import
|
|
373
|
-
console.log(chalk.bgGreen.white('Starting import...'));
|
|
374
|
-
return this.indexFiles(this.filenames);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const importScript = new ImportScript();
|
|
379
|
-
module.exports = importScript;
|
package/commands/Interactive.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
const inquirer = require('inquirer');
|
|
2
|
-
|
|
3
|
-
class Interactive {
|
|
4
|
-
parseCommandNames(commandList, ownName) {
|
|
5
|
-
const names = commandList.map(command => command._name);
|
|
6
|
-
// Remove current command name and default command
|
|
7
|
-
const commandNames = names.filter(name => name !== ownName && name !== '*');
|
|
8
|
-
return commandNames;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
getCommandQuestion(commandNames) {
|
|
12
|
-
return {
|
|
13
|
-
type: 'list',
|
|
14
|
-
name: 'commandChoice',
|
|
15
|
-
message: 'Select the command to run',
|
|
16
|
-
choices: commandNames,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
getArgumentQuestions(validArguments) {
|
|
21
|
-
return validArguments.map(argument => ({
|
|
22
|
-
type: argument.description.includes('key') ? 'password' : 'input',
|
|
23
|
-
name: argument.long.substring(2),
|
|
24
|
-
message: `${argument.long} | ${argument.description}`,
|
|
25
|
-
}));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async start(program) {
|
|
29
|
-
try {
|
|
30
|
-
const commands = require('../commands.js');
|
|
31
|
-
const ownName = program._name;
|
|
32
|
-
const commandList = program.parent.commands;
|
|
33
|
-
// Get list of valid commands
|
|
34
|
-
const commandNames = this.parseCommandNames(commandList, ownName);
|
|
35
|
-
const commandQuestion = this.getCommandQuestion(commandNames);
|
|
36
|
-
// Prompt user to select a command
|
|
37
|
-
const commandResponse = await inquirer.prompt(commandQuestion);
|
|
38
|
-
// Prepare subsequent questions
|
|
39
|
-
const selectedCommand = commandList.find(
|
|
40
|
-
command => command._name === commandResponse.commandChoice
|
|
41
|
-
);
|
|
42
|
-
const validArguments = selectedCommand.options;
|
|
43
|
-
const argumentQuestions = this.getArgumentQuestions(validArguments);
|
|
44
|
-
// Prompt user to input command arguments
|
|
45
|
-
const argumentsResponse = await inquirer.prompt(argumentQuestions);
|
|
46
|
-
// Pass arguments to program
|
|
47
|
-
const argumentsList = Object.keys(argumentsResponse);
|
|
48
|
-
argumentsList.forEach(arg => {
|
|
49
|
-
if (argumentsResponse[arg] !== '')
|
|
50
|
-
program[arg] = argumentsResponse[arg]; // eslint-disable-line no-param-reassign
|
|
51
|
-
});
|
|
52
|
-
// Execute selected command
|
|
53
|
-
commands[selectedCommand._name].start(program);
|
|
54
|
-
} catch (e) {
|
|
55
|
-
throw e;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
module.exports = new Interactive();
|
package/commands/Search.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const algolia = require('algoliasearch');
|
|
4
|
-
const Base = require('./Base.js');
|
|
5
|
-
|
|
6
|
-
class SearchScript extends Base {
|
|
7
|
-
constructor() {
|
|
8
|
-
super();
|
|
9
|
-
// Bind class methods
|
|
10
|
-
this.start = this.start.bind(this);
|
|
11
|
-
this.parseSearchOptions = this.parseSearchOptions.bind(this);
|
|
12
|
-
this.writeOutput = this.writeOutput.bind(this);
|
|
13
|
-
// Define validation constants
|
|
14
|
-
this.message =
|
|
15
|
-
'\nExample: $ algolia search -a algoliaappid -k algoliaapikey -n algoliaindexname -q query -p searchparams -o outputpath\n\n';
|
|
16
|
-
this.params = ['algoliaappid', 'algoliaapikey', 'algoliaindexname'];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
parseSearchOptions(params) {
|
|
20
|
-
return params === null ? {} : JSON.parse(params);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async writeOutput(outputFilepath, content) {
|
|
24
|
-
const defaultFilepath = path.resolve(process.cwd(), 'search-results.json');
|
|
25
|
-
const filepath = this.normalizePath(outputFilepath);
|
|
26
|
-
const dir = path.dirname(filepath);
|
|
27
|
-
if (!fs.lstatSync(dir).isDirectory()) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
`Output path must target valid directory. Eg. ${defaultFilepath}`
|
|
30
|
-
);
|
|
31
|
-
} else {
|
|
32
|
-
await fs.writeFileSync(filepath, content);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async start(program) {
|
|
37
|
-
try {
|
|
38
|
-
// Validate command; if invalid display help text and exit
|
|
39
|
-
this.validate(program, this.message, this.params);
|
|
40
|
-
|
|
41
|
-
// Config params
|
|
42
|
-
const appId = program.algoliaappid;
|
|
43
|
-
const apiKey = program.algoliaapikey;
|
|
44
|
-
const indexName = program.algoliaindexname;
|
|
45
|
-
const query = program.query || '';
|
|
46
|
-
const params = program.params || null;
|
|
47
|
-
const outputPath = program.outputpath || null;
|
|
48
|
-
|
|
49
|
-
// Get options
|
|
50
|
-
const options = this.parseSearchOptions(params);
|
|
51
|
-
|
|
52
|
-
// Instantiate Algolia index
|
|
53
|
-
const client = algolia(appId, apiKey);
|
|
54
|
-
const index = client.initIndex(indexName);
|
|
55
|
-
// Get index settings
|
|
56
|
-
const result = await index.search(query, options);
|
|
57
|
-
const output = JSON.stringify(result);
|
|
58
|
-
return outputPath === null
|
|
59
|
-
? console.log(output)
|
|
60
|
-
: await this.writeOutput(outputPath, output);
|
|
61
|
-
} catch (e) {
|
|
62
|
-
throw e;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const searchScript = new SearchScript();
|
|
68
|
-
module.exports = searchScript;
|