@verdnatura/myt 1.5.23 → 1.5.25

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/README.md CHANGED
@@ -250,7 +250,7 @@ $ myt create [-t <type>] <schema>.<name>
250
250
  Cleans all already applied versions older than *maxOldVersions*.
251
251
 
252
252
  ```text
253
- $ myt clean
253
+ $ myt clean [-p|--purge]
254
254
  ```
255
255
 
256
256
  ## Local server commands
@@ -276,8 +276,7 @@ $ myt fixtures [<remote>]
276
276
  ### run
277
277
 
278
278
  Builds and starts local database server container. It only rebuilds the image
279
- when fixtures have been modified or when the day on which the image was built
280
- is different to today.
279
+ dump has been modified.
281
280
 
282
281
  ```text
283
282
  $ myt run [-c|--ci] [-r|--random]
package/myt-clean.js CHANGED
@@ -8,27 +8,40 @@ const path = require('path');
8
8
  */
9
9
  class Clean extends Command {
10
10
  static usage = {
11
- description: 'Cleans old applied versions'
11
+ description: 'Cleans old applied versions',
12
+ params: {
13
+ purge: 'Wether to remove non-existent scripts from DB log'
14
+ }
12
15
  };
13
16
 
14
17
  static opts = {
18
+ alias: {
19
+ purge: 'p'
20
+ },
21
+ boolean: [
22
+ 'purge'
23
+ ],
15
24
  default: {
16
25
  remote: 'production'
17
26
  }
18
27
  };
19
28
 
20
29
  async run(myt, opts) {
21
- await myt.dbConnect();
22
- const version = await myt.fetchDbVersion() || {};
23
- const number = version.number;
30
+ const conn = await myt.dbConnect();
31
+ const versionDirs = await fs.readdir(opts.versionsDir);
32
+ const archiveDir = path.join(opts.versionsDir, '.archive');
33
+
34
+ const dbVersion = await myt.fetchDbVersion() || {};
35
+ const number = parseInt(dbVersion.number);
24
36
 
25
37
  const oldVersions = [];
26
- const versionDirs = await fs.readdir(opts.versionsDir);
27
38
  for (const versionDir of versionDirs) {
28
- const dirVersion = myt.parseVersionDir(versionDir);
29
- if (!dirVersion) continue;
39
+ const version = await myt.loadVersion(versionDir);
40
+ const shouldArchive = version
41
+ && !version.apply
42
+ && parseInt(version.number) < number;
30
43
 
31
- if (parseInt(dirVersion.number) < parseInt(number))
44
+ if (shouldArchive)
32
45
  oldVersions.push(versionDir);
33
46
  }
34
47
 
@@ -36,7 +49,6 @@ class Clean extends Command {
36
49
  && oldVersions.length > opts.maxOldVersions) {
37
50
  oldVersions.splice(-opts.maxOldVersions);
38
51
 
39
- const archiveDir = path.join(opts.versionsDir, '.archive');
40
52
  if (!await fs.pathExists(archiveDir))
41
53
  await fs.mkdir(archiveDir);
42
54
 
@@ -46,9 +58,63 @@ class Clean extends Command {
46
58
  path.join(archiveDir, oldVersion)
47
59
  );
48
60
 
49
- console.log(`Old versions deleted: ${oldVersions.length}`);
61
+ console.log(`Old versions archived: ${oldVersions.length}`);
50
62
  } else
51
- console.log(`No versions to delete.`);
63
+ console.log(`No versions to archive.`);
64
+
65
+ if (opts.purge) {
66
+ const versionDb = new VersionDb(myt, opts.versionsDir);
67
+ versionDb.load();
68
+
69
+ const archiveDb = new VersionDb(myt, archiveDir);
70
+ archiveDb.load();
71
+
72
+ const [res] = await conn.query(
73
+ `SELECT number, file FROM versionLog
74
+ WHERE code = ?
75
+ ORDER BY number, file`,
76
+ [opts.code]
77
+ );
78
+
79
+ for (const script of res) {
80
+ const hasVersion = await versionDb.hasScript(script);
81
+ const hasArchive = await archiveDb.hasScript(script);
82
+
83
+ if (!hasVersion && !hasArchive) {
84
+ await conn.query(
85
+ `DELETE FROM versionLog
86
+ WHERE code = ? AND number = ? AND file = ?`,
87
+ [opts.code, script.number, script.file]
88
+ );
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ class VersionDb {
96
+ constructor(myt, baseDir) {
97
+ Object.assign(this, {myt, baseDir});
98
+ }
99
+
100
+ async load() {
101
+ const versionMap = this.versionMap = new Map();
102
+ if (await fs.pathExists(this.baseDir)) {
103
+ const dirs = await fs.readdir(this.baseDir);
104
+ for (const dir of dirs) {
105
+ const version = this.myt.parseVersionDir(dir);
106
+ if (!version) continue;
107
+ versionMap.set(version.number, dir);
108
+ }
109
+ }
110
+ return versionMap;
111
+ }
112
+
113
+ async hasScript(script) {
114
+ const dir = this.versionMap.get(script.number);
115
+ if (!dir) return false;
116
+ const scriptPath = path.join(this.baseDir, dir, script.file);
117
+ return await fs.pathExists(scriptPath);
52
118
  }
53
119
  }
54
120
 
package/myt-push.js CHANGED
@@ -90,17 +90,17 @@ class Push extends Command {
90
90
 
91
91
  // Get database version
92
92
 
93
- const version = await myt.fetchDbVersion() || {};
93
+ const dbVersion = await myt.fetchDbVersion() || {};
94
94
 
95
95
  console.log(
96
96
  `Database information:`
97
- + `\n -> Version: ${version.number}`
98
- + `\n -> Commit: ${version.gitCommit}`
97
+ + `\n -> Version: ${dbVersion.number}`
98
+ + `\n -> Commit: ${dbVersion.gitCommit}`
99
99
  );
100
100
 
101
- if (!version.number)
102
- version.number = String('0').padStart(opts.versionDigits, '0');
103
- if (!/^[0-9]*$/.test(version.number))
101
+ if (!dbVersion.number)
102
+ dbVersion.number = String('0').padStart(opts.versionDigits, '0');
103
+ if (!/^[0-9]*$/.test(dbVersion.number))
104
104
  throw new Error('Wrong database version');
105
105
 
106
106
  // Prevent push to production by mistake
@@ -142,16 +142,25 @@ class Push extends Command {
142
142
  let silent = true;
143
143
  const versionsDir = opts.versionsDir;
144
144
 
145
- function logVersion(version, name, error) {
146
- console.log('', version.bold, name);
145
+ function logVersion(version, name, action, error) {
146
+ let actionMsg;
147
+ switch(action) {
148
+ case 'apply':
149
+ actionMsg = '[A]'.green;
150
+ break;
151
+ case 'ignore':
152
+ actionMsg = '[I]'.blue;
153
+ break;
154
+ default:
155
+ actionMsg = '[W]'.yellow;
156
+ }
157
+
158
+ console.log('', (actionMsg + version).bold, name);
147
159
  }
148
160
  function logScript(type, message, error) {
149
161
  console.log(' ', type.bold, message);
150
162
  }
151
- function isUndoScript(script) {
152
- return /\.undo\.sql$/.test(script);
153
- }
154
-
163
+
155
164
  const skipFiles = new Set([
156
165
  'README.md',
157
166
  '.archive'
@@ -159,87 +168,49 @@ class Push extends Command {
159
168
 
160
169
  if (await fs.pathExists(versionsDir)) {
161
170
  const versionDirs = await fs.readdir(versionsDir);
162
- const [[realm]] = await this.conn.query(
163
- `SELECT realm
164
- FROM versionConfig`
165
- );
166
-
167
171
  for (const versionDir of versionDirs) {
168
172
  if (skipFiles.has(versionDir)) continue;
173
+ const version = await myt.loadVersion(versionDir);
169
174
 
170
- const dirVersion = myt.parseVersionDir(versionDir);
171
- if (!dirVersion) {
172
- logVersion('[?????]'.yellow, versionDir,
175
+ if (!version) {
176
+ logVersion('[?????]'.yellow, versionDir, 'warn',
173
177
  `Wrong directory name.`
174
178
  );
175
179
  continue;
176
180
  }
177
-
178
- const versionNumber = dirVersion.number;
179
- const versionName = dirVersion.name;
180
-
181
- if (versionNumber.length != version.number.length) {
182
- logVersion('[*****]'.gray, versionDir,
183
- `Bad version length, should have ${version.number.length} characters.`
181
+ if (version.number.length != dbVersion.number.length) {
182
+ logVersion('[*****]'.gray, versionDir, 'warn'
183
+ `Bad version length, should have ${dbVersion.number.length} characters.`
184
184
  );
185
185
  continue;
186
186
  }
187
187
 
188
- const scriptsDir = `${versionsDir}/${versionDir}`;
189
- const scripts = await fs.readdir(scriptsDir);
190
-
191
- const [versionLog] = await conn.query(
192
- `SELECT file FROM versionLog
193
- WHERE code = ?
194
- AND number = ?
195
- AND errorNumber IS NULL`,
196
- [opts.code, versionNumber]
197
- );
198
-
199
- for (const script of scripts)
200
- if (!isUndoScript(script)
201
- && versionLog.findIndex(x => x.file == script) === -1) {
202
- silent = false;
203
- break;
204
- }
205
-
188
+ const {apply} = version;
189
+ if (apply) silent = false;
206
190
  if (silent) continue;
207
- logVersion(`[${versionNumber}]`.cyan, versionName);
208
191
 
209
- for (const script of scripts) {
210
- const match = script.match(/^[0-9]{2}-[a-zA-Z0-9_]+(?:\.(?!undo)([a-zA-Z0-9_]+))?(?:\.undo)?\.sql$/);
211
-
212
- if (!match) {
213
- logScript('[W]'.yellow, script, `Wrong file name.`);
214
- continue;
215
- }
192
+ const action = apply ? 'apply' : 'ignore';
193
+ logVersion(`[${version.number}]`.cyan, version.name, action);
216
194
 
217
- const skipRealm = match[1] && match[1] !== realm;
195
+ if (!apply) continue;
218
196
 
219
- if (isUndoScript(script) || skipRealm)
197
+ for (const script of version.scripts) {
198
+ const scriptFile = script.file;
199
+
200
+ if (!script.matchRegex) {
201
+ logScript('[W]'.yellow, scriptFile, `Wrong file name.`);
220
202
  continue;
203
+ }
221
204
 
222
- const [[row]] = await conn.query(
223
- `SELECT errorNumber FROM versionLog
224
- WHERE code = ?
225
- AND number = ?
226
- AND file = ?`,
227
- [
228
- opts.code,
229
- versionNumber,
230
- script
231
- ]
232
- );
233
- const apply = !row || row.errorNumber;
234
- const actionMsg = apply ? '[+]'.green : '[I]'.blue;
235
-
236
- logScript(actionMsg, script);
237
- if (!apply) continue;
205
+ const actionMsg = script.apply ? '[+]'.green : '[I]'.blue;
206
+ logScript(actionMsg, scriptFile);
207
+
208
+ if (!script.apply) continue;
238
209
 
239
210
  let err;
240
211
  try {
241
212
  await connExt.queryFromFile(pushConn,
242
- `${scriptsDir}/${script}`);
213
+ `${versionsDir}/${versionDir}/${scriptFile}`);
243
214
  } catch (e) {
244
215
  err = e;
245
216
  }
@@ -260,8 +231,8 @@ class Push extends Command {
260
231
  errorMessage = VALUES(errorMessage)`,
261
232
  [
262
233
  opts.code,
263
- versionNumber,
264
- script,
234
+ version.number,
235
+ scriptFile,
265
236
  err && err.errno,
266
237
  err && err.message
267
238
  ]
@@ -271,7 +242,7 @@ class Push extends Command {
271
242
  nChanges++;
272
243
  }
273
244
 
274
- await this.updateVersion('number', versionNumber);
245
+ await this.updateVersion('number', version.number);
275
246
  }
276
247
  }
277
248
 
@@ -282,7 +253,7 @@ class Push extends Command {
282
253
  const gitExists = await fs.pathExists(`${opts.workspace}/.git`);
283
254
 
284
255
  let nRoutines = 0;
285
- const changes = await this.changedRoutines(version.gitCommit);
256
+ const changes = await this.changedRoutines(dbVersion.gitCommit);
286
257
 
287
258
  const routines = [];
288
259
  for (const change of changes)
@@ -359,7 +330,7 @@ class Push extends Command {
359
330
 
360
331
  const typeMsg = `[${change.type.abbr}]`[change.type.color];
361
332
  console.log('',
362
- (statusMsg + actionMsg).bold,
333
+ (actionMsg + statusMsg).bold,
363
334
  typeMsg.bold,
364
335
  change.fullName
365
336
  );
@@ -419,7 +390,7 @@ class Push extends Command {
419
390
  const repo = await nodegit.Repository.open(this.opts.workspace);
420
391
  const head = await repo.getHeadCommit();
421
392
 
422
- if (head && version.gitCommit !== head.sha())
393
+ if (head && dbVersion.gitCommit !== head.sha())
423
394
  await this.updateVersion('gitCommit', head.sha());
424
395
  }
425
396
 
package/myt-run.js CHANGED
@@ -9,10 +9,9 @@ const connExt = require('./lib/conn');
9
9
  const SqlString = require('sqlstring');
10
10
 
11
11
  /**
12
- * Builds the database image and runs a container. It only rebuilds the
13
- * image when fixtures have been modified or when the day on which the
14
- * image was built is different to today. Some workarounds have been used
15
- * to avoid a bug with OverlayFS driver on MacOS.
12
+ * Builds the database image and runs a container. It only rebuilds the image
13
+ * when dump has been modified. Some workarounds have been used to avoid a bug
14
+ * with OverlayFS driver on MacOS.
16
15
  */
17
16
  class Run extends Command {
18
17
  static usage = {
@@ -140,7 +139,7 @@ class Run extends Command {
140
139
  const hasTriggers = await fs.exists(`${dumpDataDir}/triggers.sql`);
141
140
 
142
141
  Object.assign(opts, {
143
- triggers: hasTriggers,
142
+ triggers: !hasTriggers,
144
143
  commit: true,
145
144
  dbConfig
146
145
  });
package/myt-version.js CHANGED
@@ -74,9 +74,9 @@ class Version extends Command {
74
74
  const versionNames = new Set();
75
75
  const versionDirs = await fs.readdir(opts.versionsDir);
76
76
  for (const versionDir of versionDirs) {
77
- const dirVersion = myt.parseVersionDir(versionDir);
78
- if (!dirVersion) continue;
79
- versionNames.add(dirVersion.name);
77
+ const version = myt.parseVersionDir(versionDir);
78
+ if (!version) continue;
79
+ versionNames.add(version.name);
80
80
  }
81
81
 
82
82
  if (!versionName) {
package/myt.js CHANGED
@@ -11,6 +11,8 @@ const mysql = require('mysql2/promise');
11
11
  const nodegit = require('nodegit');
12
12
  const camelToSnake = require('./lib/util').camelToSnake;
13
13
 
14
+ const scriptRegex = /^[0-9]{2}-[a-zA-Z0-9_]+(?:\.(?!undo)([a-zA-Z0-9_]+))?(\.undo)?\.sql$/;
15
+
14
16
  class Myt {
15
17
  static usage = {
16
18
  description: 'Utility for database versioning',
@@ -305,6 +307,11 @@ class Myt {
305
307
  `${__dirname}/assets/structure.sql`, 'utf8');
306
308
  await conn.query(structure);
307
309
  }
310
+
311
+ const [[realm]] = await conn.query(
312
+ `SELECT realm FROM versionConfig`
313
+ );
314
+ this.realm = realm;
308
315
  }
309
316
 
310
317
  return this.conn;
@@ -332,6 +339,58 @@ class Myt {
332
339
  };
333
340
  }
334
341
 
342
+ async loadVersion(versionDir) {
343
+ const {opts} = this;
344
+
345
+ const info = this.parseVersionDir(versionDir);
346
+ if (!info) return null;
347
+
348
+ const versionsDir = opts.versionsDir;
349
+ const scriptsDir = `${versionsDir}/${versionDir}`;
350
+ const scriptList = await fs.readdir(scriptsDir);
351
+
352
+ const [res] = await this.conn.query(
353
+ `SELECT file, errorNumber IS NOT NULL hasError
354
+ FROM versionLog
355
+ WHERE code = ?
356
+ AND number = ?`,
357
+ [opts.code, info.number]
358
+ );
359
+ const versionLog = new Map();
360
+ res.map(x => versionLog.set(x.file, x));
361
+
362
+ let applyVersion = false;
363
+ const scripts = [];
364
+
365
+ for (const file of scriptList) {
366
+ const match = file.match(scriptRegex);
367
+ if (match) {
368
+ const scriptRealm = match[1];
369
+ const isUndo = !!match[2];
370
+
371
+ if ((scriptRealm && scriptRealm !== this.realm) || isUndo)
372
+ continue;
373
+ }
374
+
375
+ const logInfo = versionLog.get(file);
376
+ const apply = !logInfo || logInfo.hasError;
377
+ if (apply) applyVersion = true;
378
+
379
+ scripts.push({
380
+ file,
381
+ matchRegex: !!match,
382
+ apply
383
+ });
384
+ }
385
+
386
+ return {
387
+ number: info.number,
388
+ name: info.name,
389
+ scripts,
390
+ apply: applyVersion
391
+ };
392
+ }
393
+
335
394
  async openRepo() {
336
395
  const {opts} = this;
337
396
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verdnatura/myt",
3
- "version": "1.5.23",
3
+ "version": "1.5.25",
4
4
  "author": "Verdnatura Levante SL",
5
5
  "description": "MySQL version control",
6
6
  "license": "GPL-3.0",