closer-cli 2.47.0 → 2.48.1
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/CHANGELOG.md +16 -0
- package/cmds/importData.js +249 -39
- package/cmds/importMedia.js +39 -45
- package/helpers.js +67 -798
- package/index.js +4 -4
- package/package.json +5 -8
- package/cmds/importData_cmds/multiple.js +0 -341
- package/cmds/importData_cmds/single.js +0 -281
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [2.48.1](https://code.hfarm.dev/closer/closer/compare/v2.48.0...v2.48.1) (2023-02-27)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package closer-cli
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [2.48.0](https://code.hfarm.dev/closer/closer/compare/v2.47.0...v2.48.0) (2023-02-17)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package closer-cli
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
# [2.47.0](https://code.hfarm.dev/closer/closer/compare/v2.46.1...v2.47.0) (2023-02-15)
|
|
7
23
|
|
|
8
24
|
**Note:** Version bump only for package closer-cli
|
package/cmds/importData.js
CHANGED
|
@@ -1,61 +1,271 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
/* eslint-disable no-restricted-syntax */
|
|
3
|
+
/* eslint-disable no-console */
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
import Ora from 'ora'
|
|
6
|
+
import fs from 'fs'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
import prompts from 'prompts'
|
|
9
|
+
import axios from 'axios'
|
|
10
|
+
import FormData from 'form-data'
|
|
11
|
+
import qs from 'qs'
|
|
12
|
+
import { deleteAsync } from 'del'
|
|
4
13
|
|
|
5
|
-
|
|
6
|
-
|
|
14
|
+
import {
|
|
15
|
+
getHumanTimestamp,
|
|
16
|
+
CsvType,
|
|
17
|
+
getGoogleSheetCsvUrl,
|
|
18
|
+
getCsvFilesFromDir,
|
|
19
|
+
download,
|
|
20
|
+
getImportDirName
|
|
21
|
+
} from '../helpers.js'
|
|
22
|
+
|
|
23
|
+
export const command = 'import:data'
|
|
24
|
+
export const desc = 'Import data to Closer'
|
|
7
25
|
|
|
8
26
|
export function builder(yargs) {
|
|
9
27
|
yargs
|
|
10
|
-
|
|
11
|
-
.option('
|
|
12
|
-
alias: '
|
|
13
|
-
describe: '
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
.usage(`Usage: ${chalk.cyan('$0 import data')} [options]`)
|
|
29
|
+
.option('name', {
|
|
30
|
+
alias: 'n',
|
|
31
|
+
describe: 'Define import process name',
|
|
32
|
+
type: 'string',
|
|
33
|
+
default: getHumanTimestamp(new Date())
|
|
16
34
|
})
|
|
17
35
|
.option('mode', {
|
|
18
36
|
alias: 'm',
|
|
19
|
-
describe: 'Import mode, if "full"
|
|
37
|
+
describe: 'Import mode, if "full" old db records will be deleted (soft) based on Partitions',
|
|
20
38
|
choices: ['full', 'incremental']
|
|
21
39
|
})
|
|
40
|
+
.option('csvType', {
|
|
41
|
+
alias: 'c',
|
|
42
|
+
describe: 'CSV type to consider during import',
|
|
43
|
+
choices: CsvType,
|
|
44
|
+
array: true
|
|
45
|
+
})
|
|
46
|
+
.option('csvDelimiter', {
|
|
47
|
+
alias: 's',
|
|
48
|
+
describe: 'CSV Delimiter',
|
|
49
|
+
type: 'string',
|
|
50
|
+
default: ';'
|
|
51
|
+
})
|
|
22
52
|
.option('non-interactive', {
|
|
23
53
|
describe: '',
|
|
24
54
|
type: 'bool'
|
|
25
55
|
})
|
|
26
|
-
.option('
|
|
27
|
-
alias: '
|
|
28
|
-
describe: '
|
|
56
|
+
.option('fromDir', {
|
|
57
|
+
alias: 'd',
|
|
58
|
+
describe: 'Dir path that contains csv files to import',
|
|
29
59
|
type: 'string'
|
|
30
60
|
})
|
|
31
|
-
.option('
|
|
32
|
-
|
|
61
|
+
.option('fromGoogleDocId', {
|
|
62
|
+
alias: 'g',
|
|
63
|
+
describe: 'Google Doc ID that contains sheets to import',
|
|
64
|
+
type: 'string'
|
|
65
|
+
})
|
|
66
|
+
.option('keepFiles', {
|
|
67
|
+
describe: 'Keep csv files after import',
|
|
33
68
|
type: 'bool'
|
|
34
69
|
})
|
|
35
|
-
.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
70
|
+
.conflicts('fromDir', 'fromGoogleDocId')
|
|
71
|
+
.check(argv => {
|
|
72
|
+
if (!argv.fromDir && !argv.fromGoogleDocId) {
|
|
73
|
+
throw new Error('Missing required argument: fromDir OR fromGoogleDocId')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (argv.fromDir) {
|
|
77
|
+
fs.accessSync(argv.fromDir, fs.constants.F_OK)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return true
|
|
39
81
|
})
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function handler(argv) {
|
|
85
|
+
const spinner = new Ora()
|
|
86
|
+
const interactive = !argv.nonInteractive
|
|
87
|
+
|
|
88
|
+
// const ws = {
|
|
89
|
+
// endpoint: argv.endpoint,
|
|
90
|
+
// secret: argv.secret
|
|
91
|
+
// }
|
|
92
|
+
|
|
93
|
+
let csvChoices = []
|
|
94
|
+
if (argv.fromDir) {
|
|
95
|
+
const csvFilesFromDir = getCsvFilesFromDir(argv.fromDir)
|
|
96
|
+
|
|
97
|
+
csvChoices = CsvType.map(csvType => {
|
|
98
|
+
const fileName = csvFilesFromDir.find(filename => filename.endsWith(`${csvType}.csv`))
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
title: csvType,
|
|
102
|
+
csvType,
|
|
103
|
+
value: {
|
|
104
|
+
file: fileName ? path.resolve(argv.fromDir, fileName) : null,
|
|
105
|
+
csvType
|
|
106
|
+
},
|
|
107
|
+
disabled: !fileName
|
|
108
|
+
}
|
|
45
109
|
})
|
|
46
|
-
|
|
47
|
-
.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
110
|
+
} else {
|
|
111
|
+
csvChoices = CsvType.map(csvType => ({
|
|
112
|
+
title: csvType,
|
|
113
|
+
csvType,
|
|
114
|
+
value: {
|
|
115
|
+
url: getGoogleSheetCsvUrl(argv.fromGoogleDocId, csvType),
|
|
116
|
+
csvType
|
|
117
|
+
}
|
|
118
|
+
}))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let files
|
|
122
|
+
if (interactive && !argv.csvType) {
|
|
123
|
+
const answers = await prompts(
|
|
124
|
+
[
|
|
125
|
+
{
|
|
126
|
+
instructions: false,
|
|
127
|
+
name: 'files',
|
|
128
|
+
type: 'multiselect',
|
|
129
|
+
message: argv.fromDir ? 'Select Files' : 'Select Sheets',
|
|
130
|
+
warn: argv.fromDir && 'File not found',
|
|
131
|
+
choices: csvChoices,
|
|
132
|
+
min: 1
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
{ onCancel: () => process.exit() }
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
files = answers.files
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!interactive && argv.fromDir) {
|
|
142
|
+
files = csvChoices.filter(csvChoice => !csvChoice.disabled).map(csvChoice => csvChoice.value)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let mode
|
|
146
|
+
if (interactive && !argv.mode) {
|
|
147
|
+
const answers = await prompts(
|
|
148
|
+
[
|
|
149
|
+
{
|
|
150
|
+
type: 'select',
|
|
151
|
+
name: 'mode',
|
|
152
|
+
message: 'Import Mode',
|
|
153
|
+
choices: [
|
|
154
|
+
{
|
|
155
|
+
title: 'Incremental',
|
|
156
|
+
value: 'incremental'
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
title: 'Full',
|
|
160
|
+
description: 'Old db records will be deleted (soft) based on Partitions',
|
|
161
|
+
value: 'full'
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
hint: ' '
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
{ onCancel: () => process.exit() }
|
|
52
168
|
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
169
|
+
|
|
170
|
+
mode = answers.mode
|
|
171
|
+
} else {
|
|
172
|
+
mode = argv.mode
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const parsedFiles = []
|
|
176
|
+
|
|
177
|
+
if (argv.fromGoogleDocId) {
|
|
178
|
+
console.log('')
|
|
179
|
+
spinner.start('Downloading Google sheets...')
|
|
180
|
+
const dirName = getImportDirName(argv.endpoint)
|
|
181
|
+
const dirPath = path.resolve(dirName, argv.name)
|
|
182
|
+
if (!fs.existsSync(dirPath)) {
|
|
183
|
+
fs.mkdirSync(dirPath, { recursive: true })
|
|
184
|
+
}
|
|
185
|
+
for (const item of files) {
|
|
186
|
+
const filePath = await download(item.url, dirPath, `${argv.name}_${item.csvType}.csv`)
|
|
187
|
+
parsedFiles.push({ file: filePath, csvType: item.csvType })
|
|
188
|
+
}
|
|
189
|
+
spinner.succeed('Google sheets downloaded')
|
|
190
|
+
} else {
|
|
191
|
+
for (const file of files) {
|
|
192
|
+
parsedFiles.push({ file: file.file, csvType: file.csvType })
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (interactive) {
|
|
197
|
+
console.log('')
|
|
198
|
+
const { confirmed } = await prompts(
|
|
199
|
+
[
|
|
200
|
+
{
|
|
201
|
+
type: 'confirm',
|
|
202
|
+
name: 'confirmed',
|
|
203
|
+
message: 'Continue?',
|
|
204
|
+
initial: false
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
{ onCancel: () => process.exit() }
|
|
58
208
|
)
|
|
59
|
-
}
|
|
60
209
|
|
|
61
|
-
|
|
210
|
+
if (!confirmed) {
|
|
211
|
+
process.exit()
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const data = {
|
|
216
|
+
name: argv.name,
|
|
217
|
+
mode,
|
|
218
|
+
delimiter: argv.csvDelimiter,
|
|
219
|
+
files: parsedFiles
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const formData = new FormData()
|
|
223
|
+
|
|
224
|
+
for (const file of data.files) {
|
|
225
|
+
formData.append(file.csvType, fs.createReadStream(file.file))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const query = qs.stringify(
|
|
229
|
+
{
|
|
230
|
+
name: data.name,
|
|
231
|
+
mode: data.mode,
|
|
232
|
+
delimiter: data.delimiter
|
|
233
|
+
},
|
|
234
|
+
{ skipNulls: true }
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
const protocol =
|
|
238
|
+
argv.endpoint.includes('localhost') || argv.endpoint.includes('127.0.0.1') ? 'http' : 'https'
|
|
239
|
+
|
|
240
|
+
const url = `${protocol}://${argv.endpoint}/import/data?${query}`
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const response = await axios.post(url, formData, {
|
|
244
|
+
headers: { ...formData.getHeaders(), 'x-closer-secret': argv.secretKey }
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
if (response.data.error) {
|
|
248
|
+
console.error(response.data.message)
|
|
249
|
+
process.exit(1)
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (error.response?.data?.message) {
|
|
253
|
+
console.error(error.response.data.message)
|
|
254
|
+
}
|
|
255
|
+
console.error(error.message)
|
|
256
|
+
process.exit(1)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!argv.keepFiles) {
|
|
260
|
+
await deleteAsync(
|
|
261
|
+
data.files.map(file => file.file),
|
|
262
|
+
{ force: true }
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
console.log('\n')
|
|
267
|
+
console.log('✅ Thank you for initiating the data import process!')
|
|
268
|
+
console.log('📫 You will receive an email notification once the import is completed.')
|
|
269
|
+
|
|
270
|
+
process.exit()
|
|
271
|
+
}
|
package/cmds/importMedia.js
CHANGED
|
@@ -1,67 +1,61 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
import chalk from 'chalk'
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
importMedia,
|
|
5
|
-
flushMedia,
|
|
6
|
-
getExecutionTime,
|
|
7
|
-
callAfterMediaImportProcess
|
|
8
|
-
} from '../helpers.js'
|
|
3
|
+
import axios from 'axios'
|
|
4
|
+
import qs from 'qs'
|
|
9
5
|
|
|
10
6
|
export const command = 'import:media'
|
|
11
|
-
export const desc = 'Import media
|
|
7
|
+
export const desc = 'Import media to Closer'
|
|
12
8
|
|
|
13
9
|
export function builder(yargs) {
|
|
14
10
|
yargs
|
|
15
|
-
.usage(`Usage: ${chalk.cyan('$0 import
|
|
16
|
-
|
|
11
|
+
.usage(`Usage: ${chalk.cyan('$0 import media')} [options]`)
|
|
17
12
|
.option('flush', {
|
|
18
|
-
describe: '
|
|
13
|
+
describe: 'Truncate tables (ProductAssets, Assets) and delete files from disk',
|
|
19
14
|
type: 'bool'
|
|
20
15
|
})
|
|
21
|
-
|
|
22
16
|
.option('reprocess', {
|
|
23
|
-
describe: 'Reprocess
|
|
17
|
+
describe: 'Reprocess medias already processed by previous import',
|
|
24
18
|
type: 'bool'
|
|
25
19
|
})
|
|
26
20
|
}
|
|
27
21
|
|
|
28
22
|
export async function handler(argv) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
23
|
+
const query = qs.stringify(
|
|
24
|
+
{
|
|
25
|
+
flush: argv.flush,
|
|
26
|
+
reprocess: argv.reprocess
|
|
27
|
+
},
|
|
28
|
+
{ skipNulls: true }
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const protocol =
|
|
32
|
+
argv.endpoint.includes('localhost') || argv.endpoint.includes('127.0.0.1') ? 'http' : 'https'
|
|
33
|
+
|
|
34
|
+
const url = `${protocol}://${argv.endpoint}/import/media?${query}`
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const response = await axios.post(
|
|
38
|
+
url,
|
|
39
|
+
{},
|
|
40
|
+
{
|
|
41
|
+
headers: { 'x-closer-secret': argv.secretKey }
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if (response.data.error) {
|
|
46
|
+
console.error(response.data.message)
|
|
51
47
|
process.exit(1)
|
|
52
48
|
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error.response?.data?.message) {
|
|
51
|
+
console.error(error.response.data.message)
|
|
52
|
+
}
|
|
53
|
+
console.error(error.message)
|
|
54
|
+
process.exit(1)
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
// Report
|
|
59
|
-
//
|
|
60
|
-
report.end = new Date()
|
|
61
|
-
report.time = getExecutionTime(hrstart)
|
|
62
|
-
report.result = result
|
|
63
|
-
|
|
64
|
-
await callAfterMediaImportProcess(spinner, ws, report)
|
|
57
|
+
console.log('✅ Thank you for initiating the media import process!')
|
|
58
|
+
console.log('📫 You will receive an email notification once the import is completed.')
|
|
65
59
|
|
|
66
60
|
process.exit()
|
|
67
61
|
}
|