@datatruck/cli 0.21.0 → 0.22.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.
@@ -0,0 +1,39 @@
1
+ export type MysqlCliOptions = {
2
+ password: string | {
3
+ path: string;
4
+ };
5
+ hostname: string;
6
+ port?: number;
7
+ username: string;
8
+ verbose?: boolean;
9
+ };
10
+ export declare function createMysqlCli(options: MysqlCliOptions): {
11
+ options: MysqlCliOptions;
12
+ initSharedDir: (sharedDir?: string) => Promise<string>;
13
+ args: () => Promise<string[]>;
14
+ run: (query: string, database?: string) => Promise<import("./process").ExecResultType>;
15
+ fetchAll: (query: string, database?: string) => Promise<string[][]>;
16
+ dump: (input: {
17
+ output: string;
18
+ database: string;
19
+ items?: string[] | undefined;
20
+ onlyStoredPrograms?: boolean | undefined;
21
+ onProgress?: ((data: {
22
+ totalBytes: number;
23
+ }) => void) | undefined;
24
+ }) => Promise<[void, import("./process").ExecResultType]>;
25
+ fetchTableNames: (database: string, include?: string[], exclude?: string[]) => Promise<string[]>;
26
+ importFile: (path: string, database: string) => Promise<import("./process").ExecResultType>;
27
+ isDatabaseEmpty: (database: string) => Promise<boolean>;
28
+ createDatabase: (database: {
29
+ name: string;
30
+ charset?: string;
31
+ }) => Promise<void>;
32
+ csvDump: (input: {
33
+ database: string;
34
+ sharedPath: string;
35
+ items?: string[];
36
+ }) => Promise<void>;
37
+ importCsvFile: (path: string, database: string, table: string) => Promise<import("./process").ExecResultType>;
38
+ fetchVariable: (name: string) => Promise<string | undefined>;
39
+ };
package/utils/mysql.js ADDED
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMysqlCli = void 0;
4
+ const AppError_1 = require("../Error/AppError");
5
+ const fs_1 = require("./fs");
6
+ const process_1 = require("./process");
7
+ const string_1 = require("./string");
8
+ const crypto_1 = require("crypto");
9
+ const fs_2 = require("fs");
10
+ const promises_1 = require("fs/promises");
11
+ const os_1 = require("os");
12
+ const path_1 = require("path");
13
+ function createMysqlCli(options) {
14
+ async function args() {
15
+ const password = await (0, fs_1.fetchData)(options.password, (p) => p.path);
16
+ return [
17
+ `--host=${options.hostname}`,
18
+ ...(options.port ? [`--port=${options.port}`] : []),
19
+ `--user=${options.username}`,
20
+ `--password=${password ?? ""}`,
21
+ ];
22
+ }
23
+ async function run(query, database) {
24
+ return await (0, process_1.exec)("mysql", [
25
+ ...(await args()),
26
+ ...(database ? [database] : []),
27
+ "-e",
28
+ query.replace(/\s{1,}/g, " "),
29
+ "-N",
30
+ "--silent",
31
+ ], undefined, {
32
+ log: options.verbose,
33
+ stderr: { toExitCode: true },
34
+ stdout: { save: true },
35
+ });
36
+ }
37
+ async function fetchAll(query, database) {
38
+ return (0, string_1.splitLines)((await run(query, database)).stdout).map((line) => line.split("\t"));
39
+ }
40
+ async function fetchTableNames(database, include, exclude) {
41
+ return (await fetchAll(`
42
+ SELECT
43
+ table_name
44
+ FROM
45
+ information_schema.tables
46
+ WHERE
47
+ table_schema = '${database}'
48
+ ORDER BY
49
+ table_name
50
+ `))
51
+ .map((r) => r[0])
52
+ .filter((0, string_1.createMatchFilter)(include, exclude));
53
+ }
54
+ async function dump(input) {
55
+ const stream = (0, fs_2.createWriteStream)(input.output);
56
+ return await Promise.all([
57
+ new Promise((resolve, reject) => {
58
+ stream.on("close", resolve);
59
+ stream.on("error", reject);
60
+ }),
61
+ await (0, process_1.exec)("mysqldump", [
62
+ ...(await args()),
63
+ input.database,
64
+ "--lock-tables=false",
65
+ "--skip-add-drop-table=false",
66
+ ...(input.onlyStoredPrograms
67
+ ? [
68
+ "--routines",
69
+ "--events",
70
+ "--skip-triggers",
71
+ "--no-create-info",
72
+ "--no-data",
73
+ "--no-create-db",
74
+ "--skip-opt",
75
+ ]
76
+ : []),
77
+ ...(input.items || []),
78
+ ], null, {
79
+ stderr: { toExitCode: true },
80
+ pipe: {
81
+ stream,
82
+ onWriteProgress: input.onProgress,
83
+ },
84
+ log: {
85
+ exec: options.verbose,
86
+ stderr: options.verbose,
87
+ allToStderr: true,
88
+ },
89
+ }),
90
+ ]);
91
+ }
92
+ async function csvDump(input) {
93
+ await (0, process_1.exec)("mysqldump", [
94
+ ...(await args()),
95
+ input.database,
96
+ "--lock-tables=false",
97
+ "--skip-add-drop-table=false",
98
+ "-T",
99
+ input.sharedPath,
100
+ ...(input.items || []),
101
+ ], null, {
102
+ stderr: { toExitCode: true },
103
+ log: {
104
+ exec: options.verbose,
105
+ stderr: options.verbose,
106
+ allToStderr: true,
107
+ },
108
+ });
109
+ }
110
+ async function importFile(path, database) {
111
+ return await (0, process_1.exec)("mysql", [
112
+ `--init-command=SET ${[
113
+ "autocommit=0",
114
+ "unique_checks=0",
115
+ "foreign_key_checks=0",
116
+ ].join(",")};`,
117
+ ...(await args()),
118
+ database,
119
+ ], null, {
120
+ pipe: {
121
+ stream: (0, fs_2.createReadStream)(path),
122
+ onReadProgress: (data) => {
123
+ if (options.verbose)
124
+ (0, process_1.logExecStdout)({
125
+ data: JSON.stringify(data),
126
+ colorize: true,
127
+ stderr: true,
128
+ lineSalt: true,
129
+ });
130
+ },
131
+ },
132
+ stderr: { toExitCode: true },
133
+ log: options.verbose,
134
+ });
135
+ }
136
+ async function importCsvFile(path, database, table) {
137
+ return run(`
138
+ LOAD DATA LOCAL INFILE '${path.replaceAll("\\", "/")}'
139
+ INTO TABLE ${table}
140
+ FIELDS TERMINATED BY ','
141
+ ENCLOSED BY '"'
142
+ LINES TERMINATED BY '\\n'`, database);
143
+ }
144
+ async function isDatabaseEmpty(database) {
145
+ const [total] = await fetchAll(`
146
+ SELECT
147
+ COUNT(*) AS total
148
+ FROM
149
+ information_schema.tables
150
+ WHERE
151
+ table_schema = '${database}'
152
+ `);
153
+ return Number(total) ? false : true;
154
+ }
155
+ async function createDatabase(database) {
156
+ await run(`
157
+ CREATE DATABASE IF NOT EXISTS \`${database.name}\`
158
+ CHARACTER SET ${database.charset ?? "utf8"}
159
+ COLLATE ${database.charset ?? "utf8_general_ci"}
160
+ `);
161
+ }
162
+ async function fetchVariable(name) {
163
+ const stdout = (0, string_1.undefIfEmpty)((await run(`SHOW VARIABLES LIKE "${name}"`)).stdout.trim());
164
+ return stdout ? (0, string_1.undefIfEmpty)(stdout.slice(name.length).trim()) : undefined;
165
+ }
166
+ async function initSharedDir(sharedDir) {
167
+ const secure_file_priv = await fetchVariable("secure_file_priv");
168
+ if (secure_file_priv?.toUpperCase() === "NULL")
169
+ throw new AppError_1.AppError("'secure_file_priv' is null in MySQL Server");
170
+ const dir = sharedDir ??
171
+ secure_file_priv ??
172
+ (await fetchVariable("tmpdir")) ??
173
+ (0, os_1.tmpdir)();
174
+ await checkSharedDir(dir);
175
+ return dir;
176
+ }
177
+ async function checkSharedDir(dir) {
178
+ const id = (0, crypto_1.randomBytes)(8).toString("hex");
179
+ const outFile = (0, path_1.join)(dir, `dtt_test_${id}`);
180
+ const outFileVar = JSON.stringify(outFile.replaceAll("\\", "/"));
181
+ try {
182
+ await (0, fs_1.mkdirIfNotExists)(dir);
183
+ await run(`SELECT 1 INTO OUTFILE ${outFileVar}`);
184
+ const exists = await (0, fs_1.existsFile)(outFile);
185
+ if (!exists)
186
+ throw new AppError_1.AppError(`MySQL shared dir is not reached: ${dir}`);
187
+ }
188
+ finally {
189
+ try {
190
+ await (0, promises_1.rm)(outFile);
191
+ }
192
+ catch (e) { }
193
+ }
194
+ }
195
+ return {
196
+ options,
197
+ initSharedDir,
198
+ args,
199
+ run,
200
+ fetchAll,
201
+ dump,
202
+ fetchTableNames,
203
+ importFile,
204
+ isDatabaseEmpty,
205
+ createDatabase,
206
+ csvDump,
207
+ importCsvFile,
208
+ fetchVariable,
209
+ };
210
+ }
211
+ exports.createMysqlCli = createMysqlCli;
package/utils/string.d.ts CHANGED
@@ -13,6 +13,10 @@ export type UriType = {
13
13
  export declare function formatUri(input: UriType, hidePassword?: boolean): string;
14
14
  export declare function formatSeconds(seconds: number): string;
15
15
  export declare function makePathPatterns(values: string[] | undefined): string[] | undefined;
16
- export declare function checkPath(path: string, include: string[], exclude?: string[]): boolean;
16
+ export declare function match(path: string, include?: string[], exclude?: string[]): boolean;
17
+ export declare function endsWith(input: string, patterns: string[]): boolean;
18
+ export declare function createMatchFilter(include?: string[], exclude?: string[]): (input: string) => boolean;
17
19
  export declare function checkMatch(subject: string | undefined, patterns: string[]): boolean;
18
20
  export declare function formatDateTime(datetime: string): string;
21
+ export declare function splitLines(input: string, satinize?: boolean): string[];
22
+ export declare function undefIfEmpty(input: string): string | undefined;
package/utils/string.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatDateTime = exports.checkMatch = exports.checkPath = exports.makePathPatterns = exports.formatSeconds = exports.formatUri = exports.parseStringList = exports.render = exports.snakeCase = exports.serialize = void 0;
3
+ exports.undefIfEmpty = exports.splitLines = exports.formatDateTime = exports.checkMatch = exports.createMatchFilter = exports.endsWith = exports.match = exports.makePathPatterns = exports.formatSeconds = exports.formatUri = exports.parseStringList = exports.render = exports.snakeCase = exports.serialize = void 0;
4
4
  const AppError_1 = require("../Error/AppError");
5
5
  const micromatch_1 = require("micromatch");
6
6
  function serialize(message, data) {
@@ -91,13 +91,19 @@ function makePathPatterns(values) {
91
91
  });
92
92
  }
93
93
  exports.makePathPatterns = makePathPatterns;
94
- function checkPath(path, include, exclude) {
95
- return ((0, micromatch_1.isMatch)(path, include, {
96
- dot: true,
97
- }) &&
94
+ function match(path, include, exclude) {
95
+ return ((!include || (0, micromatch_1.isMatch)(path, include, { dot: true })) &&
98
96
  (!exclude || !(0, micromatch_1.isMatch)(path, exclude, { dot: true })));
99
97
  }
100
- exports.checkPath = checkPath;
98
+ exports.match = match;
99
+ function endsWith(input, patterns) {
100
+ return patterns.some((pattern) => input.endsWith(pattern));
101
+ }
102
+ exports.endsWith = endsWith;
103
+ function createMatchFilter(include, exclude) {
104
+ return (input) => match(input, include, exclude);
105
+ }
106
+ exports.createMatchFilter = createMatchFilter;
101
107
  function checkMatch(subject, patterns) {
102
108
  if (!subject?.length)
103
109
  subject = "<empty>";
@@ -114,3 +120,19 @@ function formatDateTime(datetime) {
114
120
  return result;
115
121
  }
116
122
  exports.formatDateTime = formatDateTime;
123
+ function splitLines(input, satinize = true) {
124
+ const lines = input.split(/\r?\n/);
125
+ return satinize
126
+ ? input.split(/\r?\n/).reduce((result, value) => {
127
+ value = value.trim();
128
+ if (value.length)
129
+ result.push(value);
130
+ return result;
131
+ }, [])
132
+ : lines;
133
+ }
134
+ exports.splitLines = splitLines;
135
+ function undefIfEmpty(input) {
136
+ return input.length ? input : undefined;
137
+ }
138
+ exports.undefIfEmpty = undefIfEmpty;
package/utils/tar.js CHANGED
@@ -137,21 +137,23 @@ async function createTar(options) {
137
137
  }, {
138
138
  log: options.verbose ? { envNames: Object.keys(env) } : false,
139
139
  stderr: { toExitCode: true },
140
- stdout: {
141
- parseLines: "skip-empty",
142
- onData: (line) => {
143
- current++;
144
- const path = vendor === "bsdtar" ? line.slice(2) : line;
145
- options.onEntry?.({
146
- path,
147
- progress: {
148
- total,
149
- current,
150
- percent: (0, math_1.progressPercent)(total, current),
151
- },
152
- });
153
- },
154
- },
140
+ stdout: options.onEntry
141
+ ? {
142
+ parseLines: "skip-empty",
143
+ onData: (line) => {
144
+ current++;
145
+ const path = vendor === "bsdtar" ? line.slice(2) : line;
146
+ options.onEntry?.({
147
+ path,
148
+ progress: {
149
+ total,
150
+ current,
151
+ percent: (0, math_1.progressPercent)(total, current),
152
+ },
153
+ });
154
+ },
155
+ }
156
+ : undefined,
155
157
  });
156
158
  }
157
159
  exports.createTar = createTar;
@@ -164,10 +166,11 @@ function toLocalPath(path) {
164
166
  async function extractTar(options) {
165
167
  let total = options.total ??
166
168
  (await listTar({ input: options.input, verbose: options.verbose }));
167
- if (options.verbose)
168
- (0, cli_1.logExec)("mkdir", ["-p", options.output]);
169
- await (0, promises_1.mkdir)(options.output, { recursive: true });
170
- await (0, fs_1.ensureEmptyDir)(options.output);
169
+ if (!(await (0, fs_1.existsDir)(options.output))) {
170
+ if (options.verbose)
171
+ (0, cli_1.logExec)("mkdir", ["-p", options.output]);
172
+ await (0, promises_1.mkdir)(options.output, { recursive: true });
173
+ }
171
174
  const decompress = await ifX(options.decompress, async (decompress) => ({
172
175
  ...decompress,
173
176
  cores: await resolveCores(decompress.cores),
@@ -192,20 +195,22 @@ async function extractTar(options) {
192
195
  stderr: {
193
196
  toExitCode: true,
194
197
  },
195
- stdout: {
196
- parseLines: "skip-empty",
197
- onData: (path) => {
198
- current++;
199
- options.onEntry?.({
200
- path,
201
- progress: {
202
- total,
203
- current,
204
- percent: (0, math_1.progressPercent)(total, current),
205
- },
206
- });
207
- },
208
- },
198
+ stdout: options.onEntry
199
+ ? {
200
+ parseLines: "skip-empty",
201
+ onData: (path) => {
202
+ current++;
203
+ options.onEntry?.({
204
+ path,
205
+ progress: {
206
+ total,
207
+ current,
208
+ percent: (0, math_1.progressPercent)(total, current),
209
+ },
210
+ });
211
+ },
212
+ }
213
+ : undefined,
209
214
  });
210
215
  }
211
216
  exports.extractTar = extractTar;