closer-cli 2.48.0 → 2.48.2

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 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.2](https://code.hfarm.dev/closer/closer/compare/v2.48.1...v2.48.2) (2023-03-03)
7
+
8
+ **Note:** Version bump only for package closer-cli
9
+
10
+
11
+
12
+
13
+
14
+ ## [2.48.1](https://code.hfarm.dev/closer/closer/compare/v2.48.0...v2.48.1) (2023-02-27)
15
+
16
+ **Note:** Version bump only for package closer-cli
17
+
18
+
19
+
20
+
21
+
6
22
  # [2.48.0](https://code.hfarm.dev/closer/closer/compare/v2.47.0...v2.48.0) (2023-02-17)
7
23
 
8
24
  **Note:** Version bump only for package closer-cli
@@ -1,61 +1,271 @@
1
- import { ALLOWED_CSV_FILES, getHumanTimestamp } from '../helpers.js'
2
- import * as ImportDataMultipleCmd from './importData_cmds/multiple.js'
3
- import * as ImportDataSingleCmd from './importData_cmds/single.js'
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
- export const command = 'import:data <command>'
6
- export const desc = 'Import CSV data to Closer'
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
- // .commandDir('importData_cmds')
11
- .option('type', {
12
- alias: 't',
13
- describe: 'CSV type to consider during import',
14
- choices: Object.keys(ALLOWED_CSV_FILES),
15
- array: true
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" all database records will be logically deleted',
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('out', {
27
- alias: 'o',
28
- describe: 'Directory path that will contain errored CSV files',
56
+ .option('fromDir', {
57
+ alias: 'd',
58
+ describe: 'Dir path that contains csv files to import',
29
59
  type: 'string'
30
60
  })
31
- .option('skipTasksBeforeImport', {
32
- describe: 'Skip tasks before start import process',
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
- .option('csvDelimiter', {
36
- describe: 'CSV Delimiter',
37
- type: 'string',
38
- default: ';'
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
- .option('n', {
41
- alias: 'name',
42
- describe: 'Define import process name',
43
- type: 'string',
44
- default: getHumanTimestamp(new Date())
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
- .implies('non-interactive', ['type', 'mode'])
47
- .command(
48
- ImportDataMultipleCmd.command,
49
- ImportDataMultipleCmd.desc,
50
- ImportDataMultipleCmd.builder,
51
- ImportDataMultipleCmd.handler
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
- .command(
54
- ImportDataSingleCmd.command,
55
- ImportDataSingleCmd.desc,
56
- ImportDataSingleCmd.builder,
57
- ImportDataSingleCmd.handler
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
- export function handler() {}
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
+ }
@@ -1,67 +1,61 @@
1
+ /* eslint-disable no-console */
1
2
  import chalk from 'chalk'
2
- import Ora from 'ora'
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 (images, 360, videos) to Closer'
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:media')} [options]`)
16
-
11
+ .usage(`Usage: ${chalk.cyan('$0 import media')} [options]`)
17
12
  .option('flush', {
18
- describe: 'Delete all before the import process (DB records and files from disk).',
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 media that were already processed by previous import.',
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 spinner = new Ora()
30
- const hrstart = process.hrtime()
31
-
32
- const report = {
33
- start: new Date(),
34
- end: null,
35
- time: null,
36
- endpoint: argv.endpoint,
37
- flush: argv.flush,
38
- reprocess: argv.reprocess,
39
- result: null
40
- }
41
-
42
- const ws = {
43
- endpoint: argv.endpoint,
44
- secret: argv.secret
45
- }
46
-
47
- if (argv.flush) {
48
- try {
49
- await flushMedia(spinner, ws)
50
- } catch (e) {
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
- const result = await importMedia(argv.reprocess, spinner, ws)
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
  }