dobo-extra 1.0.2 → 1.0.4
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/bajo/.dependencies +2 -0
- package/bajo/config.json +1 -2
- package/bajo/init.js +4 -4
- package/bajo/method/export-to.js +2 -2
- package/bajo/method/import-from.js +2 -2
- package/bajo/start.js +2 -2
- package/bajoCli/applet/archive.js +16 -16
- package/bajoCli/applet/export-to.js +8 -8
- package/bajoCli/applet/import-from.js +8 -8
- package/bajoI18N/resource/en-US.json +37 -0
- package/bajoI18N/resource/id.json +35 -35
- package/package.json +1 -1
package/bajo/config.json
CHANGED
package/bajo/init.js
CHANGED
|
@@ -5,15 +5,15 @@ async function handler ({ item }) {
|
|
|
5
5
|
const { getSchema } = this.app.dobo
|
|
6
6
|
const { has } = this.app.bajo.lib._
|
|
7
7
|
for (const f of ['source', 'destination']) {
|
|
8
|
-
if (!has(item, f)) throw this.error('
|
|
8
|
+
if (!has(item, f)) throw this.error('taskMustHaveModel%s', f)
|
|
9
9
|
const key = `${f}Field`
|
|
10
10
|
item[key] = item[key] ?? 'createdAt'
|
|
11
11
|
const schema = getSchema(item[f])
|
|
12
12
|
const prop = schema.properties.find(p => p.name === item[key])
|
|
13
|
-
if (!prop) throw this.error('
|
|
14
|
-
if (!types.includes(prop.type)) throw this.error('
|
|
13
|
+
if (!prop) throw this.error('unknownField%s%s', item[key], item[f])
|
|
14
|
+
if (!types.includes(prop.type)) throw this.error('isNotSupported%s%s%s%s', item[key], item[f], prop.type, join(types))
|
|
15
15
|
}
|
|
16
|
-
if (item.source === item.destination) throw this.error('
|
|
16
|
+
if (item.source === item.destination) throw this.error('sourceDestMustBeDifferent')
|
|
17
17
|
item.maxAge = item.maxAge ?? 1 // in days, less then 1 is ignored
|
|
18
18
|
}
|
|
19
19
|
|
package/bajo/method/export-to.js
CHANGED
|
@@ -21,7 +21,7 @@ async function getFile (dest, ensureDir) {
|
|
|
21
21
|
const dir = path.dirname(file)
|
|
22
22
|
if (!fs.existsSync(dir)) {
|
|
23
23
|
if (ensureDir) fs.ensureDirSync(dir)
|
|
24
|
-
else throw this.error('
|
|
24
|
+
else throw this.error('dirNotExists%s', dir)
|
|
25
25
|
}
|
|
26
26
|
let compress = false
|
|
27
27
|
let ext = path.extname(file)
|
|
@@ -30,7 +30,7 @@ async function getFile (dest, ensureDir) {
|
|
|
30
30
|
ext = path.extname(path.basename(file).replace('.gz', ''))
|
|
31
31
|
// file = file.slice(0, file.length - 3)
|
|
32
32
|
}
|
|
33
|
-
if (!supportedExt.includes(ext)) throw this.error('
|
|
33
|
+
if (!supportedExt.includes(ext)) throw this.error('unsupportedFormat%s', ext.slice(1))
|
|
34
34
|
return { file, ext, compress }
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -20,14 +20,14 @@ async function importFrom (source, dest, { trashOld = true, batch = 1, progressF
|
|
|
20
20
|
file = `${getPluginDataDir(this.name)}/import/${source}`
|
|
21
21
|
fs.ensureDirSync(path.dirname(file))
|
|
22
22
|
}
|
|
23
|
-
if (!fs.existsSync(file)) throw this.error('
|
|
23
|
+
if (!fs.existsSync(file)) throw this.error('sourceFileNotExists%s', file)
|
|
24
24
|
let ext = fileType ? `.${fileType}` : path.extname(file)
|
|
25
25
|
let decompress = false
|
|
26
26
|
if (ext === '.gz') {
|
|
27
27
|
ext = path.extname(path.basename(file, '.gz'))
|
|
28
28
|
decompress = true
|
|
29
29
|
}
|
|
30
|
-
if (!supportedExt.includes(ext)) throw this.error('
|
|
30
|
+
if (!supportedExt.includes(ext)) throw this.error('unsupportedFormat%s', ext.slice(1))
|
|
31
31
|
if (trashOld && dest !== false) await this.app.dobo.modelClear(dest)
|
|
32
32
|
const reader = fs.createReadStream(file)
|
|
33
33
|
batch = parseInt(batch) || 100
|
package/bajo/start.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
async function start () {
|
|
2
2
|
if (this.config.archive.checkInterval === false || this.config.archive.checkInterval <= 0) {
|
|
3
|
-
this.log.warn('
|
|
3
|
+
this.log.warn('autoArchiveDisabled')
|
|
4
4
|
return
|
|
5
5
|
}
|
|
6
6
|
if (this.config.archive.runEarly) await this.archive()
|
|
7
7
|
this.archiveIntv = setInterval(() => {
|
|
8
8
|
this.archive().then().catch(err => {
|
|
9
|
-
this.log.error('
|
|
9
|
+
this.log.error('archiveError%s', err.message)
|
|
10
10
|
})
|
|
11
11
|
}, this.config.archive.checkInterval * 60 * 1000)
|
|
12
12
|
}
|
|
@@ -7,25 +7,25 @@ async function move (task) {
|
|
|
7
7
|
const prompts = await importPkg('bajoCli:@inquirer/prompts')
|
|
8
8
|
const { confirm } = prompts
|
|
9
9
|
// get relevant record
|
|
10
|
-
this.print.info('
|
|
10
|
+
this.print.info('copying%s%s', task.source, task.destination)
|
|
11
11
|
const mark = dayjs().subtract(task.maxAge, 'day').toDate()
|
|
12
12
|
const query = set({}, task.sourceField, { $lt: mark })
|
|
13
13
|
let count = await statAggregate(task.source, { query }, { aggregate: 'count' })
|
|
14
14
|
count = count[0].count
|
|
15
15
|
if (count === 0) {
|
|
16
|
-
this.print.warn('
|
|
16
|
+
this.print.warn('notFoundSkipped')
|
|
17
17
|
return
|
|
18
18
|
}
|
|
19
19
|
if (this.config.prompt !== false) {
|
|
20
20
|
const answer = await confirm({
|
|
21
|
-
message: this.print.write('%d
|
|
21
|
+
message: this.print.write('recordsArchivedContinue%d', count),
|
|
22
22
|
default: true
|
|
23
23
|
})
|
|
24
24
|
if (!answer) {
|
|
25
|
-
this.print.warn('
|
|
25
|
+
this.print.warn('taskCancelled%s%s', task.source, task.destination)
|
|
26
26
|
return
|
|
27
27
|
}
|
|
28
|
-
} else this.print.info('%d
|
|
28
|
+
} else this.print.info('recordsArchived%d', count)
|
|
29
29
|
let page = 1
|
|
30
30
|
let total = 0
|
|
31
31
|
let affected = 0
|
|
@@ -36,7 +36,7 @@ async function move (task) {
|
|
|
36
36
|
for (;;) {
|
|
37
37
|
const results = await recordFind(task.source, { query, page, limit: 100 }, { noCache: true, noHook: true })
|
|
38
38
|
if (results.length === 0) break
|
|
39
|
-
if (this.bajo.config.tool && total % 1000 === 0 && total !== 0) this.print.succeed('
|
|
39
|
+
if (this.bajo.config.tool && total % 1000 === 0 && total !== 0) this.print.succeed('milestoneCopied%s', formatInteger(total))
|
|
40
40
|
for (const r of results) {
|
|
41
41
|
if (task.maxRecords && task.maxRecords < total) {
|
|
42
42
|
isMax = true
|
|
@@ -46,7 +46,7 @@ async function move (task) {
|
|
|
46
46
|
try {
|
|
47
47
|
await recordCreate(task.destination, r, { noSanitize: true, noHook: true, noResult: true, noCheckUnique: true })
|
|
48
48
|
ids.push(r.id)
|
|
49
|
-
spin.setText('
|
|
49
|
+
spin.setText('id%s', r.id)
|
|
50
50
|
} catch (err) {
|
|
51
51
|
error++
|
|
52
52
|
}
|
|
@@ -55,18 +55,18 @@ async function move (task) {
|
|
|
55
55
|
affected = total - error
|
|
56
56
|
page++
|
|
57
57
|
}
|
|
58
|
-
if (isMax) this.print.warn('
|
|
59
|
-
this.print.info('
|
|
58
|
+
if (isMax) this.print.warn('maxReached%d', task.maxRecords)
|
|
59
|
+
this.print.info('removing%s', task.source)
|
|
60
60
|
for (const idx in ids) {
|
|
61
61
|
const id = ids[idx]
|
|
62
62
|
try {
|
|
63
63
|
await recordRemove(task.source, id, { noHook: true, noResult: true })
|
|
64
|
-
spin.setText('
|
|
65
|
-
if (this.bajo.config.tool && idx % 1000 === 0 && idx !== '0') this.print.succeed('
|
|
64
|
+
spin.setText('id%s', id)
|
|
65
|
+
if (this.bajo.config.tool && idx % 1000 === 0 && idx !== '0') this.print.succeed('milestoneRemoved%s', formatInteger(idx))
|
|
66
66
|
} catch (err) {}
|
|
67
67
|
}
|
|
68
68
|
spin.stop()
|
|
69
|
-
this.print.info('
|
|
69
|
+
this.print.info('archivingEnded%s%s%s%s', task.source, task.destination, formatInteger(affected), formatInteger(error))
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
async function archive (...args) {
|
|
@@ -75,17 +75,17 @@ async function archive (...args) {
|
|
|
75
75
|
const { confirm } = prompts
|
|
76
76
|
if (this.config.prompt !== false) {
|
|
77
77
|
const answer = await confirm({
|
|
78
|
-
message: this.print.write('
|
|
78
|
+
message: this.print.write('manuallyRunTaskContinue'),
|
|
79
79
|
default: false
|
|
80
80
|
})
|
|
81
81
|
|
|
82
|
-
if (!answer) this.print.fatal('
|
|
82
|
+
if (!answer) this.print.fatal('aborted')
|
|
83
83
|
}
|
|
84
84
|
await this.app.dobo.start()
|
|
85
|
-
if (this.archivers.length === 0) this.print.fatal('
|
|
85
|
+
if (this.archivers.length === 0) this.print.fatal('nothingToArchive')
|
|
86
86
|
for (const t of this.archivers.tasks) {
|
|
87
87
|
if (t.maxAge < 1) {
|
|
88
|
-
this.print.warn('
|
|
88
|
+
this.print.warn('archiveDisabled%s%s', t.source, t.destination)
|
|
89
89
|
continue
|
|
90
90
|
}
|
|
91
91
|
await move.call(this, t)
|
|
@@ -6,7 +6,7 @@ function makeProgress (spin) {
|
|
|
6
6
|
const { secToHms } = this.app.bajo
|
|
7
7
|
return async function ({ batchNo, data, batchStart, batchEnd } = {}) {
|
|
8
8
|
if (data.length === 0) return
|
|
9
|
-
spin.setText('
|
|
9
|
+
spin.setText('batch%d%s', batchNo, secToHms(batchEnd.toTime() - batchStart.toTime(), true))
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -19,37 +19,37 @@ async function exportTo (...args) {
|
|
|
19
19
|
const [input, select] = await importPkg('bajoCli:@inquirer/input',
|
|
20
20
|
'bajoCli:@inquirer/select')
|
|
21
21
|
const schemas = map(this.app.dobo.schemas, 'name')
|
|
22
|
-
if (isEmpty(schemas)) return this.print.fatal('
|
|
22
|
+
if (isEmpty(schemas)) return this.print.fatal('notFound%s', this.print.write('field.schema'))
|
|
23
23
|
let [model, dest, query] = args
|
|
24
24
|
if (isEmpty(model)) {
|
|
25
25
|
model = await select({
|
|
26
|
-
message: this.print.write('
|
|
26
|
+
message: this.print.write('chooseModel'),
|
|
27
27
|
choices: map(schemas, s => ({ value: s }))
|
|
28
28
|
})
|
|
29
29
|
}
|
|
30
30
|
if (isEmpty(dest)) {
|
|
31
31
|
dest = await input({
|
|
32
|
-
message: this.print.write('
|
|
32
|
+
message: this.print.write('enterDestFile'),
|
|
33
33
|
default: `${model}-${dayjs().format('YYYYMMDD')}.ndjson`,
|
|
34
34
|
validate: (item) => !isEmpty(item)
|
|
35
35
|
})
|
|
36
36
|
}
|
|
37
37
|
if (isEmpty(query)) {
|
|
38
38
|
query = await input({
|
|
39
|
-
message: this.print.write('
|
|
39
|
+
message: this.print.write('enterQueryIfAny')
|
|
40
40
|
})
|
|
41
41
|
}
|
|
42
|
-
const spin = this.print.spinner().start('
|
|
42
|
+
const spin = this.print.spinner().start('exporting')
|
|
43
43
|
const progressFn = makeProgress.call(this, spin)
|
|
44
44
|
const { connection } = getInfo(model)
|
|
45
45
|
await start(connection.name)
|
|
46
46
|
try {
|
|
47
47
|
const filter = { query }
|
|
48
48
|
const result = await this.exportTo(model, dest, { filter, batch, progressFn })
|
|
49
|
-
spin.succeed('%d
|
|
49
|
+
spin.succeed('exported%d%s', result.count, _path.resolve(result.file))
|
|
50
50
|
} catch (err) {
|
|
51
51
|
console.log(err)
|
|
52
|
-
spin.fatal('
|
|
52
|
+
spin.fatal('error%s', err.message)
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -5,7 +5,7 @@ const batch = 100
|
|
|
5
5
|
function makeProgress (spin) {
|
|
6
6
|
const { secToHms } = this.app.bajo
|
|
7
7
|
return async function ({ batchNo, data, batchStart, batchEnd } = {}) {
|
|
8
|
-
spin.setText('
|
|
8
|
+
spin.setText('batch%d%s', batchNo, secToHms(batchEnd.toTime() - batchStart.toTime(), true))
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -17,32 +17,32 @@ async function importFrom (...args) {
|
|
|
17
17
|
const [input, select, confirm] = await importPkg('bajoCli:@inquirer/input',
|
|
18
18
|
'bajoCli:@inquirer/select', 'bajoCli:@inquirer/confirm')
|
|
19
19
|
const schemas = map(this.app.dobo.schemas, 'name')
|
|
20
|
-
if (isEmpty(schemas)) return this.print.fatal('
|
|
20
|
+
if (isEmpty(schemas)) return this.print.fatal('notFound%s', this.print.write('field.schema'))
|
|
21
21
|
let [dest, model] = args
|
|
22
22
|
if (isEmpty(dest)) {
|
|
23
23
|
dest = await input({
|
|
24
|
-
message: this.print.write('
|
|
24
|
+
message: this.print.write('enterSourceFile'),
|
|
25
25
|
validate: (item) => !isEmpty(item)
|
|
26
26
|
})
|
|
27
27
|
}
|
|
28
28
|
if (isEmpty(model)) {
|
|
29
29
|
model = await select({
|
|
30
|
-
message: this.print.write('
|
|
30
|
+
message: this.print.write('chooseModel'),
|
|
31
31
|
choices: map(schemas, s => ({ value: s }))
|
|
32
32
|
})
|
|
33
33
|
}
|
|
34
34
|
const answer = await confirm({
|
|
35
|
-
message: this.print.write('
|
|
35
|
+
message: this.print.write('aboutToReplaceAllRecords'),
|
|
36
36
|
default: false
|
|
37
37
|
})
|
|
38
|
-
if (!answer) return this.print.fatal('
|
|
39
|
-
const spin = this.print.spinner({ showCounter: true }).start('
|
|
38
|
+
if (!answer) return this.print.fatal('aborted')
|
|
39
|
+
const spin = this.print.spinner({ showCounter: true }).start('importing')
|
|
40
40
|
const progressFn = makeProgress.call(this, spin)
|
|
41
41
|
const { connection } = getInfo(model)
|
|
42
42
|
await start(connection.name)
|
|
43
43
|
try {
|
|
44
44
|
const result = await importFrom(dest, model, { batch, progressFn })
|
|
45
|
-
spin.succeed('%d
|
|
45
|
+
spin.succeed('recordsImported%d%s', result.count, _path.resolve(result.file))
|
|
46
46
|
} catch (err) {
|
|
47
47
|
spin.fatal('Error: %s', err.message)
|
|
48
48
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"archiveError%s": "Archive error: %s",
|
|
3
|
+
"dirNotExists%s": "Directory '%s' doesn't exist",
|
|
4
|
+
"unsupportedFormat%s": "Unsupported format '%s'",
|
|
5
|
+
"sourceFileNotExists%s": "Source file '%s' doesn't exist",
|
|
6
|
+
"taskMissingModel%s": "Task must have %s model",
|
|
7
|
+
"unknownField%s%s": "Unknown field '%s@%s'",
|
|
8
|
+
"isNotSupported%s%s%s%s": "'%s@%s (%s)' is not supported (must be one of %s)",
|
|
9
|
+
"sourceDestMustBeDifferent": "Source & destination must be different",
|
|
10
|
+
"autoArchiveDisabled": "Automatic archiving is disabled",
|
|
11
|
+
"copying%s%s": "Copying %s -> %s...",
|
|
12
|
+
"notFoundSkipped": "No record found, skipped",
|
|
13
|
+
"recordsArchivedContinue%d": "%d records will be archived. Continue?",
|
|
14
|
+
"taskCancelled%s%s": "Task %s -> %s cancelled",
|
|
15
|
+
"recordsArchived%d": "%d records will be archived",
|
|
16
|
+
"milestoneCopied%s": "Milestone #%s records copied",
|
|
17
|
+
"id%s": "ID #%s",
|
|
18
|
+
"maxReached%d": "Max of %d records reached",
|
|
19
|
+
"removing%s": "Removing %s...",
|
|
20
|
+
"milestoneRemoved%s": "Milestone #%s records removed",
|
|
21
|
+
"archivingEnded%s%s%s%s": "Archiving %s -> %s ended, moved: %s, error: %s",
|
|
22
|
+
"manuallyRunTaskContinue": "You're about to manually run task archiver. Continue?",
|
|
23
|
+
"nothingToArchive": "Nothing to archive",
|
|
24
|
+
"archiveDisabled%s%s": "Archive %s -> %s is disabled",
|
|
25
|
+
"batch%d%s": "Batch #%d (%s)",
|
|
26
|
+
"noSchemaFound": "Batch #%d (%s)",
|
|
27
|
+
"chooseModel": "Please choose model:",
|
|
28
|
+
"enterDestFile": "Please enter destination file:",
|
|
29
|
+
"enterQueryIfAny": "Please enter a query (if any):",
|
|
30
|
+
"exporting": "Exporting...",
|
|
31
|
+
"exported%d%s": "%d records successfully exported to '%s'",
|
|
32
|
+
"enterSourceFile": "Please enter source file:",
|
|
33
|
+
"aboutToReplaceAllRecords": "You're about to replace ALL records with the new ones. Are you really sure?",
|
|
34
|
+
"importing": "Importing...",
|
|
35
|
+
"recordsImported%d%s": "%d records successfully imported from '%s'",
|
|
36
|
+
"aborted": "Aborted!"
|
|
37
|
+
}
|
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"Copying %s -> %s..."
|
|
12
|
-
"No record found, skipped"
|
|
13
|
-
"%d records will be archived. Continue?"
|
|
14
|
-
"Task %s -> %s cancelled"
|
|
15
|
-
"%d records will be archived"
|
|
16
|
-
"Milestone #%s records copied"
|
|
17
|
-
"
|
|
18
|
-
"Max of %d records reached"
|
|
19
|
-
"
|
|
20
|
-
"Milestone #%s records removed"
|
|
21
|
-
"Archiving %s -> %s ended, moved: %s, error: %s"
|
|
22
|
-
"You're about to manually run task archiver. Continue?"
|
|
23
|
-
"Nothing to archive"
|
|
24
|
-
"Archive %s -> %s is disabled"
|
|
25
|
-
"Batch #%d (%s)"
|
|
26
|
-
"
|
|
27
|
-
"Please choose model:"
|
|
28
|
-
"Please enter destination file:"
|
|
29
|
-
"Please enter a query (if any):"
|
|
30
|
-
"
|
|
31
|
-
"%d records successfully exported to '%s'"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
2
|
+
"archiveError%s": "Archive error: %s",
|
|
3
|
+
"dirNotExists%s": "Directory '%s' doesn't exist",
|
|
4
|
+
"unsupportedFormat%s": "Unsupported format '%s'",
|
|
5
|
+
"sourceFileNotExists%s": "Source file '%s' doesn't exist",
|
|
6
|
+
"taskMissingModel%s": "Task must have %s model",
|
|
7
|
+
"unknownField%s%s": "Unknown field '%s@%s'",
|
|
8
|
+
"isNotSupported%s%s%s%s": "'%s@%s (%s)' is not supported (must be one of %s)",
|
|
9
|
+
"sourceDestMustBeDifferent": "Source & destination must be different",
|
|
10
|
+
"autoArchiveDisabled": "Automatic archiving is disabled",
|
|
11
|
+
"copying%s%s": "Copying %s -> %s...",
|
|
12
|
+
"notFoundSkipped": "No record found, skipped",
|
|
13
|
+
"recordsArchivedContinue%d": "%d records will be archived. Continue?",
|
|
14
|
+
"taskCancelled%s%s": "Task %s -> %s cancelled",
|
|
15
|
+
"recordsArchived%d": "%d records will be archived",
|
|
16
|
+
"milestoneCopied%s": "Milestone #%s records copied",
|
|
17
|
+
"id%s": "ID #%s",
|
|
18
|
+
"maxReached%d": "Max of %d records reached",
|
|
19
|
+
"removing%s": "Removing %s...",
|
|
20
|
+
"milestoneRemoved%s": "Milestone #%s records removed",
|
|
21
|
+
"archivingEnded%s%s%s%s": "Archiving %s -> %s ended, moved: %s, error: %s",
|
|
22
|
+
"manuallyRunTaskContinue": "You're about to manually run task archiver. Continue?",
|
|
23
|
+
"nothingToArchive": "Nothing to archive",
|
|
24
|
+
"archiveDisabled%s%s": "Archive %s -> %s is disabled",
|
|
25
|
+
"batch%d%s": "Batch #%d (%s)",
|
|
26
|
+
"noSchemaFound": "Batch #%d (%s)",
|
|
27
|
+
"chooseModel": "Please choose model:",
|
|
28
|
+
"enterDestFile": "Please enter destination file:",
|
|
29
|
+
"enterQueryIfAny": "Please enter a query (if any):",
|
|
30
|
+
"exporting": "Exporting...",
|
|
31
|
+
"exported%d%s": "%d records successfully exported to '%s'",
|
|
32
|
+
"enterSourceFile": "Please enter source file:",
|
|
33
|
+
"aboutToReplaceAllRecords": "You're about to replace ALL records with the new ones. Are you really sure?",
|
|
34
|
+
"importing": "Importing...",
|
|
35
|
+
"recordsImported%d%s": "%d records successfully imported from '%s'",
|
|
36
|
+
"aborted": "Aborted!"
|
|
37
37
|
}
|