@intecoag/inteco-cli 0.5.1 → 1.0.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/.github/workflows/publish.yml +31 -0
- package/README.md +3 -3
- package/package.json +44 -35
- package/src/index.js +85 -85
- package/src/modules/adbBridge.js +51 -51
- package/src/modules/adbIntentSender.js +81 -81
- package/src/modules/bundleProduct.js +160 -160
- package/src/modules/csvMerger.js +117 -117
- package/src/modules/deleteDB.js +83 -83
- package/src/modules/dumpDB.js +215 -215
- package/src/modules/dumpTableToCSV.js +153 -153
- package/src/modules/extdSearch.js +226 -226
- package/src/modules/graphqlSchemaExport.js +60 -60
- package/src/modules/importDB.js +120 -120
- package/src/modules/rewriteConfig.js +78 -78
- package/src/modules/setCLIConfig.js +32 -32
- package/src/modules/syncConfig.js +264 -264
- package/src/modules/t003Rewrite.js +63 -63
- package/src/ressources/cmds.json +46 -46
- package/src/utils/config/config.js +70 -70
- package/src/utils/config/default.json +8 -8
- package/src/utils/db/DB.js +53 -53
- package/src/utils/fs/FS.js +87 -87
package/src/modules/importDB.js
CHANGED
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
import prompts from "prompts"
|
|
2
|
-
import { writeFileSync, readFileSync, readdirSync, writeFile, unlinkSync, mkdirSync, renameSync, rmSync, copyFileSync } from "fs";
|
|
3
|
-
import Seven from 'node-7z'
|
|
4
|
-
import ora from "ora";
|
|
5
|
-
import { DB } from "../utils/db/DB.js";
|
|
6
|
-
import { exec, execFile, execFileSync, execSync } from "child_process";
|
|
7
|
-
import { Config } from "../utils/config/config.js";
|
|
8
|
-
import chalk from "chalk";
|
|
9
|
-
import path from "path";
|
|
10
|
-
import sevenBin from '7zip-bin'
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export default async function importDB(cli) {
|
|
14
|
-
console.log()
|
|
15
|
-
|
|
16
|
-
const config = await Config.getConfig();
|
|
17
|
-
|
|
18
|
-
const files = readdirSync(process.cwd(), { withFileTypes: true }).filter(dirent => dirent.isFile()).map(dirent => { return { title: dirent.name } })
|
|
19
|
-
|
|
20
|
-
let success = true;
|
|
21
|
-
|
|
22
|
-
const results = await prompts([
|
|
23
|
-
{
|
|
24
|
-
// Ordnerauswahl von vorhandenen Ordner in configIndividual
|
|
25
|
-
type: 'autocomplete',
|
|
26
|
-
name: 'file',
|
|
27
|
-
message: 'Dump-Name?',
|
|
28
|
-
choices: files
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
// Ordnerauswahl von vorhandenen Ordner in configIndividual
|
|
32
|
-
type: 'text',
|
|
33
|
-
name: 'dbName',
|
|
34
|
-
message: 'DB-Name?'
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
type: 'toggle',
|
|
38
|
-
name: 'dropDB',
|
|
39
|
-
message: 'Delete and Recreate DB before import?',
|
|
40
|
-
initial: true,
|
|
41
|
-
active: 'yes',
|
|
42
|
-
inactive: 'no'
|
|
43
|
-
}
|
|
44
|
-
], {
|
|
45
|
-
onCancel: () => {
|
|
46
|
-
console.log()
|
|
47
|
-
console.log(chalk.red("Cancelled Import!"))
|
|
48
|
-
console.log()
|
|
49
|
-
success = false
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
if (success) {
|
|
54
|
-
console.log()
|
|
55
|
-
rmSync("." + path.sep + "dump", { recursive: true, force: true })
|
|
56
|
-
mkdirSync("." + path.sep + "/dump", { recursive: true })
|
|
57
|
-
|
|
58
|
-
if (await isArchive(results.file)) {
|
|
59
|
-
|
|
60
|
-
const spinnerZIP = ora('Unpacking Archive').start();
|
|
61
|
-
|
|
62
|
-
const unpack = Seven.extract(results.file, "." + path.sep + "dump", {
|
|
63
|
-
$bin: sevenBin.path7za
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
unpack.on('end', async () => {
|
|
67
|
-
const filename = readdirSync("." + path.sep + "dump")[0]
|
|
68
|
-
renameSync("." + path.sep + "dump" + path.sep + "" + filename, "." + path.sep + "dump" + path.sep + "dump.sql")
|
|
69
|
-
spinnerZIP.succeed("Archive unpacked")
|
|
70
|
-
|
|
71
|
-
await importDBActual(config, results)
|
|
72
|
-
})
|
|
73
|
-
}else{
|
|
74
|
-
const spinnerCopy = ora('Copying Dump').start();
|
|
75
|
-
|
|
76
|
-
copyFileSync("."+path.sep+results.file, "." + path.sep + "dump" + path.sep + "dump.sql")
|
|
77
|
-
|
|
78
|
-
spinnerCopy.succeed("Dump copied")
|
|
79
|
-
|
|
80
|
-
await importDBActual(config, results)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function isArchive(file) {
|
|
86
|
-
return new Promise((resolve, reject) => {
|
|
87
|
-
const test = Seven.list("."+path.sep+file, {
|
|
88
|
-
$bin: sevenBin.path7za
|
|
89
|
-
});
|
|
90
|
-
test.on('end', (stream) => {
|
|
91
|
-
resolve(true)
|
|
92
|
-
})
|
|
93
|
-
test.on('error', (stream) => {
|
|
94
|
-
resolve(false)
|
|
95
|
-
})
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function importDBActual(config, results) {
|
|
100
|
-
const spinnerImport = ora('Importing DB').start();
|
|
101
|
-
|
|
102
|
-
if (results.dropDB) {
|
|
103
|
-
await DB.executeQuery("DROP DATABASE IF EXISTS " + results.dbName);
|
|
104
|
-
await DB.executeQuery("CREATE DATABASE " + results.dbName);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
exec("mysql -u " + config.dbUser + " -p" + config.dbPassword + " " + results.dbName + " < dump" + path.sep + "dump.sql", (error, stdout, stderr) => {
|
|
108
|
-
if (error != null) {
|
|
109
|
-
spinnerImport.fail("Database-Import failed!")
|
|
110
|
-
console.log(error)
|
|
111
|
-
} else {
|
|
112
|
-
spinnerImport.succeed("Database imported")
|
|
113
|
-
|
|
114
|
-
const deleteSpinner = ora("Delete temporary dump").start()
|
|
115
|
-
rmSync("." + path.sep + "dump", { recursive: true, force: true })
|
|
116
|
-
deleteSpinner.succeed("Dump deleted")
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
console.log()
|
|
120
|
-
})
|
|
1
|
+
import prompts from "prompts"
|
|
2
|
+
import { writeFileSync, readFileSync, readdirSync, writeFile, unlinkSync, mkdirSync, renameSync, rmSync, copyFileSync } from "fs";
|
|
3
|
+
import Seven from 'node-7z'
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import { DB } from "../utils/db/DB.js";
|
|
6
|
+
import { exec, execFile, execFileSync, execSync } from "child_process";
|
|
7
|
+
import { Config } from "../utils/config/config.js";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import sevenBin from '7zip-bin'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export default async function importDB(cli) {
|
|
14
|
+
console.log()
|
|
15
|
+
|
|
16
|
+
const config = await Config.getConfig();
|
|
17
|
+
|
|
18
|
+
const files = readdirSync(process.cwd(), { withFileTypes: true }).filter(dirent => dirent.isFile()).map(dirent => { return { title: dirent.name } })
|
|
19
|
+
|
|
20
|
+
let success = true;
|
|
21
|
+
|
|
22
|
+
const results = await prompts([
|
|
23
|
+
{
|
|
24
|
+
// Ordnerauswahl von vorhandenen Ordner in configIndividual
|
|
25
|
+
type: 'autocomplete',
|
|
26
|
+
name: 'file',
|
|
27
|
+
message: 'Dump-Name?',
|
|
28
|
+
choices: files
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
// Ordnerauswahl von vorhandenen Ordner in configIndividual
|
|
32
|
+
type: 'text',
|
|
33
|
+
name: 'dbName',
|
|
34
|
+
message: 'DB-Name?'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: 'toggle',
|
|
38
|
+
name: 'dropDB',
|
|
39
|
+
message: 'Delete and Recreate DB before import?',
|
|
40
|
+
initial: true,
|
|
41
|
+
active: 'yes',
|
|
42
|
+
inactive: 'no'
|
|
43
|
+
}
|
|
44
|
+
], {
|
|
45
|
+
onCancel: () => {
|
|
46
|
+
console.log()
|
|
47
|
+
console.log(chalk.red("Cancelled Import!"))
|
|
48
|
+
console.log()
|
|
49
|
+
success = false
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
if (success) {
|
|
54
|
+
console.log()
|
|
55
|
+
rmSync("." + path.sep + "dump", { recursive: true, force: true })
|
|
56
|
+
mkdirSync("." + path.sep + "/dump", { recursive: true })
|
|
57
|
+
|
|
58
|
+
if (await isArchive(results.file)) {
|
|
59
|
+
|
|
60
|
+
const spinnerZIP = ora('Unpacking Archive').start();
|
|
61
|
+
|
|
62
|
+
const unpack = Seven.extract(results.file, "." + path.sep + "dump", {
|
|
63
|
+
$bin: sevenBin.path7za
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
unpack.on('end', async () => {
|
|
67
|
+
const filename = readdirSync("." + path.sep + "dump")[0]
|
|
68
|
+
renameSync("." + path.sep + "dump" + path.sep + "" + filename, "." + path.sep + "dump" + path.sep + "dump.sql")
|
|
69
|
+
spinnerZIP.succeed("Archive unpacked")
|
|
70
|
+
|
|
71
|
+
await importDBActual(config, results)
|
|
72
|
+
})
|
|
73
|
+
}else{
|
|
74
|
+
const spinnerCopy = ora('Copying Dump').start();
|
|
75
|
+
|
|
76
|
+
copyFileSync("."+path.sep+results.file, "." + path.sep + "dump" + path.sep + "dump.sql")
|
|
77
|
+
|
|
78
|
+
spinnerCopy.succeed("Dump copied")
|
|
79
|
+
|
|
80
|
+
await importDBActual(config, results)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isArchive(file) {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const test = Seven.list("."+path.sep+file, {
|
|
88
|
+
$bin: sevenBin.path7za
|
|
89
|
+
});
|
|
90
|
+
test.on('end', (stream) => {
|
|
91
|
+
resolve(true)
|
|
92
|
+
})
|
|
93
|
+
test.on('error', (stream) => {
|
|
94
|
+
resolve(false)
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function importDBActual(config, results) {
|
|
100
|
+
const spinnerImport = ora('Importing DB').start();
|
|
101
|
+
|
|
102
|
+
if (results.dropDB) {
|
|
103
|
+
await DB.executeQuery("DROP DATABASE IF EXISTS " + results.dbName);
|
|
104
|
+
await DB.executeQuery("CREATE DATABASE " + results.dbName);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
exec("mysql -u " + config.dbUser + " -p" + config.dbPassword + " " + results.dbName + " < dump" + path.sep + "dump.sql", (error, stdout, stderr) => {
|
|
108
|
+
if (error != null) {
|
|
109
|
+
spinnerImport.fail("Database-Import failed!")
|
|
110
|
+
console.log(error)
|
|
111
|
+
} else {
|
|
112
|
+
spinnerImport.succeed("Database imported")
|
|
113
|
+
|
|
114
|
+
const deleteSpinner = ora("Delete temporary dump").start()
|
|
115
|
+
rmSync("." + path.sep + "dump", { recursive: true, force: true })
|
|
116
|
+
deleteSpinner.succeed("Dump deleted")
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log()
|
|
120
|
+
})
|
|
121
121
|
}
|
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
import prompts from "prompts";
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import { createEditor } from "properties-parser";
|
|
4
|
-
import { writeFileSync, readFileSync, readdirSync } from "fs";
|
|
5
|
-
import YAML from 'yaml'
|
|
6
|
-
import chalk from "chalk";
|
|
7
|
-
import { Config } from "../utils/config/config.js";
|
|
8
|
-
import { DB } from "../utils/db/DB.js";
|
|
9
|
-
import path from "path";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
async function configRewrite(cli) {
|
|
14
|
-
console.log()
|
|
15
|
-
|
|
16
|
-
const homedir = os.homedir();
|
|
17
|
-
|
|
18
|
-
const config = await Config.getConfig();
|
|
19
|
-
|
|
20
|
-
const databaseNames = await DB.getDatabaseNames();
|
|
21
|
-
|
|
22
|
-
const configDirectories = readdirSync(config.configIndividualPath, { withFileTypes: true }).filter(dirent => dirent.isDirectory()).map(dirent => { return { title: dirent.name } })
|
|
23
|
-
|
|
24
|
-
let success = true;
|
|
25
|
-
|
|
26
|
-
const responses = await prompts([{
|
|
27
|
-
// DB-Auswahl von DB
|
|
28
|
-
type: 'autocomplete',
|
|
29
|
-
name: 'dbName',
|
|
30
|
-
message: 'DB-Name?',
|
|
31
|
-
choices: databaseNames.map(name => { return { title: name.name } })
|
|
32
|
-
}, {
|
|
33
|
-
// Ordnerauswahl von vorhandenen Ordner in configIndividual
|
|
34
|
-
type: 'autocomplete',
|
|
35
|
-
name: 'configName',
|
|
36
|
-
message: 'ConfigIndividual-Name?',
|
|
37
|
-
choices: configDirectories
|
|
38
|
-
}, {
|
|
39
|
-
type: 'number',
|
|
40
|
-
name: 'mnr',
|
|
41
|
-
message: 'Mandant?',
|
|
42
|
-
initial: '1'
|
|
43
|
-
}], {
|
|
44
|
-
onCancel: () => {
|
|
45
|
-
console.log()
|
|
46
|
-
console.log(chalk.red("Cancelled Rewrite!"))
|
|
47
|
-
console.log()
|
|
48
|
-
success = false
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
if (success) {
|
|
53
|
-
// Rewrite jwegas.properties
|
|
54
|
-
const editorJwegasProperties = createEditor(homedir + path.sep+"jwegas.properties")
|
|
55
|
-
editorJwegasProperties.set("user.mandant", responses.mnr.toString());
|
|
56
|
-
editorJwegasProperties.save();
|
|
57
|
-
|
|
58
|
-
// Rewrite wegas.properties
|
|
59
|
-
const editorWegasProperties = createEditor(config.configIndividualPath + path.sep+"wegas.properties")
|
|
60
|
-
editorWegasProperties.set("db.database", responses.dbName);
|
|
61
|
-
editorWegasProperties.save();
|
|
62
|
-
|
|
63
|
-
// Rewrite path.yaml
|
|
64
|
-
const content = readFileSync(config.configIndividualPath +path.sep+"path.yaml", "utf-8");
|
|
65
|
-
|
|
66
|
-
const doc = YAML.parseDocument(content)
|
|
67
|
-
|
|
68
|
-
doc.set("pathIndividual", config.configIndividualPathWrite + "\\\\" + responses.configName + "\\\\")
|
|
69
|
-
|
|
70
|
-
writeFileSync(config.configIndividualPath +path.sep+"path.yaml", doc.toString());
|
|
71
|
-
|
|
72
|
-
console.log()
|
|
73
|
-
console.log(chalk.green("Config-Rewrite successful!"))
|
|
74
|
-
console.log()
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
1
|
+
import prompts from "prompts";
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import { createEditor } from "properties-parser";
|
|
4
|
+
import { writeFileSync, readFileSync, readdirSync } from "fs";
|
|
5
|
+
import YAML from 'yaml'
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { Config } from "../utils/config/config.js";
|
|
8
|
+
import { DB } from "../utils/db/DB.js";
|
|
9
|
+
import path from "path";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async function configRewrite(cli) {
|
|
14
|
+
console.log()
|
|
15
|
+
|
|
16
|
+
const homedir = os.homedir();
|
|
17
|
+
|
|
18
|
+
const config = await Config.getConfig();
|
|
19
|
+
|
|
20
|
+
const databaseNames = await DB.getDatabaseNames();
|
|
21
|
+
|
|
22
|
+
const configDirectories = readdirSync(config.configIndividualPath, { withFileTypes: true }).filter(dirent => dirent.isDirectory()).map(dirent => { return { title: dirent.name } })
|
|
23
|
+
|
|
24
|
+
let success = true;
|
|
25
|
+
|
|
26
|
+
const responses = await prompts([{
|
|
27
|
+
// DB-Auswahl von DB
|
|
28
|
+
type: 'autocomplete',
|
|
29
|
+
name: 'dbName',
|
|
30
|
+
message: 'DB-Name?',
|
|
31
|
+
choices: databaseNames.map(name => { return { title: name.name } })
|
|
32
|
+
}, {
|
|
33
|
+
// Ordnerauswahl von vorhandenen Ordner in configIndividual
|
|
34
|
+
type: 'autocomplete',
|
|
35
|
+
name: 'configName',
|
|
36
|
+
message: 'ConfigIndividual-Name?',
|
|
37
|
+
choices: configDirectories
|
|
38
|
+
}, {
|
|
39
|
+
type: 'number',
|
|
40
|
+
name: 'mnr',
|
|
41
|
+
message: 'Mandant?',
|
|
42
|
+
initial: '1'
|
|
43
|
+
}], {
|
|
44
|
+
onCancel: () => {
|
|
45
|
+
console.log()
|
|
46
|
+
console.log(chalk.red("Cancelled Rewrite!"))
|
|
47
|
+
console.log()
|
|
48
|
+
success = false
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (success) {
|
|
53
|
+
// Rewrite jwegas.properties
|
|
54
|
+
const editorJwegasProperties = createEditor(homedir + path.sep+"jwegas.properties")
|
|
55
|
+
editorJwegasProperties.set("user.mandant", responses.mnr.toString());
|
|
56
|
+
editorJwegasProperties.save();
|
|
57
|
+
|
|
58
|
+
// Rewrite wegas.properties
|
|
59
|
+
const editorWegasProperties = createEditor(config.configIndividualPath + path.sep+"wegas.properties")
|
|
60
|
+
editorWegasProperties.set("db.database", responses.dbName);
|
|
61
|
+
editorWegasProperties.save();
|
|
62
|
+
|
|
63
|
+
// Rewrite path.yaml
|
|
64
|
+
const content = readFileSync(config.configIndividualPath +path.sep+"path.yaml", "utf-8");
|
|
65
|
+
|
|
66
|
+
const doc = YAML.parseDocument(content)
|
|
67
|
+
|
|
68
|
+
doc.set("pathIndividual", config.configIndividualPathWrite + "\\\\" + responses.configName + "\\\\")
|
|
69
|
+
|
|
70
|
+
writeFileSync(config.configIndividualPath +path.sep+"path.yaml", doc.toString());
|
|
71
|
+
|
|
72
|
+
console.log()
|
|
73
|
+
console.log(chalk.green("Config-Rewrite successful!"))
|
|
74
|
+
console.log()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
79
|
export default configRewrite;
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import prompts from "prompts";
|
|
2
|
-
import { Config } from "../utils/config/config.js";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
|
|
5
|
-
export default async function writeCLIConfig(){
|
|
6
|
-
console.log()
|
|
7
|
-
|
|
8
|
-
let data = await Config.getConfig();
|
|
9
|
-
|
|
10
|
-
let keys = Object.keys(data);
|
|
11
|
-
let success = true;
|
|
12
|
-
|
|
13
|
-
const responses = await prompts(keys.map(key => {
|
|
14
|
-
return {
|
|
15
|
-
// Ordnerauswahl von vorhandenen Ordner in configIndividual
|
|
16
|
-
type: 'text',
|
|
17
|
-
name: key,
|
|
18
|
-
message: key+":",
|
|
19
|
-
initial: data[key]
|
|
20
|
-
}
|
|
21
|
-
}), {
|
|
22
|
-
onCancel: () => {
|
|
23
|
-
console.log()
|
|
24
|
-
console.log(chalk.red("Cancelled Config!"))
|
|
25
|
-
console.log()
|
|
26
|
-
success = false
|
|
27
|
-
}
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
if(success){
|
|
31
|
-
Config.setConfig(responses)
|
|
32
|
-
}
|
|
1
|
+
import prompts from "prompts";
|
|
2
|
+
import { Config } from "../utils/config/config.js";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
|
|
5
|
+
export default async function writeCLIConfig(){
|
|
6
|
+
console.log()
|
|
7
|
+
|
|
8
|
+
let data = await Config.getConfig();
|
|
9
|
+
|
|
10
|
+
let keys = Object.keys(data);
|
|
11
|
+
let success = true;
|
|
12
|
+
|
|
13
|
+
const responses = await prompts(keys.map(key => {
|
|
14
|
+
return {
|
|
15
|
+
// Ordnerauswahl von vorhandenen Ordner in configIndividual
|
|
16
|
+
type: 'text',
|
|
17
|
+
name: key,
|
|
18
|
+
message: key+":",
|
|
19
|
+
initial: data[key]
|
|
20
|
+
}
|
|
21
|
+
}), {
|
|
22
|
+
onCancel: () => {
|
|
23
|
+
console.log()
|
|
24
|
+
console.log(chalk.red("Cancelled Config!"))
|
|
25
|
+
console.log()
|
|
26
|
+
success = false
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if(success){
|
|
31
|
+
Config.setConfig(responses)
|
|
32
|
+
}
|
|
33
33
|
}
|