@verdnatura/myt 1.5.22 → 1.5.24

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
@@ -64,7 +64,7 @@ Each command can have its own specific commandline options.
64
64
 
65
65
  ## Basic information
66
66
 
67
- First of all you have to initalize the workspace.
67
+ First of all you have to initialize the workspace.
68
68
 
69
69
  ```text
70
70
  $ myt init
@@ -72,7 +72,7 @@ $ myt init
72
72
 
73
73
  Now you can configure Myt using *myt.config.yml* file, located at the root of
74
74
  your workspace. This file should include the project codename and schemas/tables
75
- wich are exported when you use *pull* or *dump* commands.
75
+ which are exported when you use *pull* or *dump* commands.
76
76
 
77
77
  Don't forget to initialize git (if it isn't initialized yet).
78
78
 
@@ -143,6 +143,38 @@ Don't place your PL/SQL objects here, use the routines folder!
143
143
  `- 00-sameNumbers.sql
144
144
  ```
145
145
 
146
+ ### Environment-Specific Versioning with Realms
147
+
148
+ #### Overview
149
+
150
+ We have introduced a new feature that allows users to apply version-specific changes based on their configured environment, or "realm". This ensures that certain changes are only applied when the user is operating within a specific realm, providing an additional layer of customization and control.
151
+
152
+ #### Configuration
153
+
154
+ To make use of this feature, you need to configure your realm in the `versionConfig` table. Set your realm by inserting or updating a record in this table. The `realm` field should contain the identifier of your environment.
155
+
156
+ #### File Naming Convention
157
+
158
+ To designate a file as realm-specific, include a `.your_realm.` segment before the file extension. For example, if your realm is set to 'ab', the file should be named like this: `filename.ab.sql`.
159
+
160
+ #### How It Works
161
+
162
+ 1. **Set your Realm**: Configure your realm in the `versionConfig` table.
163
+
164
+ 2. **Add Files**: Place your realm-specific files in the `versions` folder. Make sure to follow the naming convention.
165
+
166
+ 3. **Version Order**: Files are applied in the existing version order. In addition to that, the realm is validated.
167
+
168
+ 4. **Apply Changes**: Run the usual versioning commands. The realm-specific files will only be applied if your configured realm matches the realm in the file name.
169
+
170
+ #### Important Notes
171
+
172
+ - If no realm is configured, realm-specific files will be ignored.
173
+
174
+ - If you have a realm configured but the realm-specific files belong to a different realm, those files will also be ignored.
175
+
176
+ This feature allows for greater flexibility when working in different environments, making it easier to manage realm-specific changes in a shared repository.
177
+
146
178
  ### Local server
147
179
 
148
180
  The local server is created as a MariaDB Docker container using the base dump
@@ -169,6 +201,7 @@ Initializes an empty workspace.
169
201
  $ myt init
170
202
  ```
171
203
 
204
+
172
205
  ### pull
173
206
 
174
207
  Incorporates database routine changes into workspace.
@@ -263,14 +296,14 @@ $ myt start
263
296
 
264
297
  ## Why
265
298
 
266
- The main reason for starting this project it's because there are no fully free
299
+ The main reason for starting this project is because there are no fully free
267
300
  and open source migration tools available that allow versioning database
268
- routines with an standard CVS system as if they were normal application code.
301
+ routines with a standard CVS system as if they were normal application code.
269
302
 
270
303
  Also, the existing tools are too complex and require too much knowledge to
271
- start a small project.
304
+ initiate a small project.
272
305
 
273
- ## ToDo
306
+ ## To-Do
274
307
 
275
308
  * Undo changes when there is an error applying a version using "undo" files.
276
309
  * Console logging via events.
@@ -280,6 +313,6 @@ start a small project.
280
313
  ## Built With
281
314
 
282
315
  * [Git](https://git-scm.com/)
283
- * [nodejs](https://nodejs.org/)
316
+ * [Node.js](https://nodejs.org/)
284
317
  * [NodeGit](https://www.nodegit.org/)
285
- * [docker](https://www.docker.com/)
318
+ * [Docker](https://www.docker.com/)
@@ -22,3 +22,8 @@ CREATE TABLE `versionLog` (
22
22
 
23
23
  ALTER TABLE `versionLog`
24
24
  ADD PRIMARY KEY (`code`,`number`,`file`);
25
+
26
+ CREATE TABLE `util`.`versionConfig` (
27
+ `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
28
+ `realm` VARCHAR(16) NULL DEFAULT NULL COMMENT 'Data set on which the project runs'
29
+ ) ENGINE=InnoDB;
package/myt-clean.js CHANGED
@@ -19,16 +19,19 @@ class Clean extends Command {
19
19
 
20
20
  async run(myt, opts) {
21
21
  await myt.dbConnect();
22
- const version = await myt.fetchDbVersion() || {};
23
- const number = version.number;
22
+ const dbVersion = await myt.fetchDbVersion() || {};
23
+ const number = parseInt(dbVersion.number);
24
24
 
25
25
  const oldVersions = [];
26
26
  const versionDirs = await fs.readdir(opts.versionsDir);
27
27
  for (const versionDir of versionDirs) {
28
- const dirVersion = myt.parseVersionDir(versionDir);
29
- if (!dirVersion) continue;
28
+ const version = await myt.loadVersion(versionDir);
29
+ const shouldArchive = version
30
+ && version.matchRegex
31
+ && !version.apply
32
+ && parseInt(version.number) < number;
30
33
 
31
- if (parseInt(dirVersion.number) < parseInt(number))
34
+ if (shouldArchive)
32
35
  oldVersions.push(versionDir);
33
36
  }
34
37
 
@@ -46,9 +49,9 @@ class Clean extends Command {
46
49
  path.join(archiveDir, oldVersion)
47
50
  );
48
51
 
49
- console.log(`Old versions deleted: ${oldVersions.length}`);
52
+ console.log(`Old versions archived: ${oldVersions.length}`);
50
53
  } else
51
- console.log(`No versions to delete.`);
54
+ console.log(`No versions to archive.`);
52
55
  }
53
56
  }
54
57
 
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,15 +142,24 @@ 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',
@@ -159,78 +168,49 @@ class Push extends Command {
159
168
 
160
169
  if (await fs.pathExists(versionsDir)) {
161
170
  const versionDirs = await fs.readdir(versionsDir);
162
-
163
171
  for (const versionDir of versionDirs) {
164
172
  if (skipFiles.has(versionDir)) continue;
173
+ const version = await myt.loadVersion(versionDir);
165
174
 
166
- const dirVersion = myt.parseVersionDir(versionDir);
167
- if (!dirVersion) {
168
- logVersion('[?????]'.yellow, versionDir,
175
+ if (!version) {
176
+ logVersion('[?????]'.yellow, versionDir, 'warn',
169
177
  `Wrong directory name.`
170
178
  );
171
179
  continue;
172
180
  }
173
-
174
- const versionNumber = dirVersion.number;
175
- const versionName = dirVersion.name;
176
-
177
- if (versionNumber.length != version.number.length) {
178
- logVersion('[*****]'.gray, versionDir,
179
- `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.`
180
184
  );
181
185
  continue;
182
186
  }
183
187
 
184
- const scriptsDir = `${versionsDir}/${versionDir}`;
185
- const scripts = await fs.readdir(scriptsDir);
186
-
187
- const [versionLog] = await conn.query(
188
- `SELECT file FROM versionLog
189
- WHERE code = ?
190
- AND number = ?
191
- AND errorNumber IS NULL`,
192
- [opts.code, versionNumber]
193
- );
194
-
195
- for (const script of scripts)
196
- if (!isUndoScript(script)
197
- && versionLog.findIndex(x => x.file == script) === -1) {
198
- silent = false;
199
- break;
200
- }
201
-
188
+ const {apply} = version;
189
+ if (apply) silent = false;
202
190
  if (silent) continue;
203
- logVersion(`[${versionNumber}]`.cyan, versionName);
204
191
 
205
- for (const script of scripts) {
206
- if (!/^[0-9]{2}-[a-zA-Z0-9_]+(.undo)?\.sql$/.test(script)) {
207
- logScript('[W]'.yellow, script, `Wrong file name.`);
192
+ const action = apply ? 'apply' : 'ignore';
193
+ logVersion(`[${version.number}]`.cyan, version.name, action);
194
+
195
+ if (!apply) continue;
196
+
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.`);
208
202
  continue;
209
203
  }
210
- if (isUndoScript(script))
211
- continue;
212
204
 
213
- const [[row]] = await conn.query(
214
- `SELECT errorNumber FROM versionLog
215
- WHERE code = ?
216
- AND number = ?
217
- AND file = ?`,
218
- [
219
- opts.code,
220
- versionNumber,
221
- script
222
- ]
223
- );
224
- const apply = !row || row.errorNumber;
225
- const actionMsg = apply ? '[+]'.green : '[I]'.blue;
226
-
227
- logScript(actionMsg, script);
228
- if (!apply) continue;
205
+ const actionMsg = script.apply ? '[+]'.green : '[I]'.blue;
206
+ logScript(actionMsg, scriptFile);
207
+
208
+ if (!script.apply) continue;
229
209
 
230
210
  let err;
231
211
  try {
232
212
  await connExt.queryFromFile(pushConn,
233
- `${scriptsDir}/${script}`);
213
+ `${versionsDir}/${versionDir}/${scriptFile}`);
234
214
  } catch (e) {
235
215
  err = e;
236
216
  }
@@ -251,8 +231,8 @@ class Push extends Command {
251
231
  errorMessage = VALUES(errorMessage)`,
252
232
  [
253
233
  opts.code,
254
- versionNumber,
255
- script,
234
+ version.number,
235
+ scriptFile,
256
236
  err && err.errno,
257
237
  err && err.message
258
238
  ]
@@ -262,7 +242,7 @@ class Push extends Command {
262
242
  nChanges++;
263
243
  }
264
244
 
265
- await this.updateVersion('number', versionNumber);
245
+ await this.updateVersion('number', version.number);
266
246
  }
267
247
  }
268
248
 
@@ -273,7 +253,7 @@ class Push extends Command {
273
253
  const gitExists = await fs.pathExists(`${opts.workspace}/.git`);
274
254
 
275
255
  let nRoutines = 0;
276
- const changes = await this.changedRoutines(version.gitCommit);
256
+ const changes = await this.changedRoutines(dbVersion.gitCommit);
277
257
 
278
258
  const routines = [];
279
259
  for (const change of changes)
@@ -350,7 +330,7 @@ class Push extends Command {
350
330
 
351
331
  const typeMsg = `[${change.type.abbr}]`[change.type.color];
352
332
  console.log('',
353
- (statusMsg + actionMsg).bold,
333
+ (actionMsg + statusMsg).bold,
354
334
  typeMsg.bold,
355
335
  change.fullName
356
336
  );
@@ -410,7 +390,7 @@ class Push extends Command {
410
390
  const repo = await nodegit.Repository.open(this.opts.workspace);
411
391
  const head = await repo.getHeadCommit();
412
392
 
413
- if (head && version.gitCommit !== head.sha())
393
+ if (head && dbVersion.gitCommit !== head.sha())
414
394
  await this.updateVersion('gitCommit', head.sha());
415
395
  }
416
396
 
package/myt-run.js CHANGED
@@ -140,7 +140,7 @@ class Run extends Command {
140
140
  const hasTriggers = await fs.exists(`${dumpDataDir}/triggers.sql`);
141
141
 
142
142
  Object.assign(opts, {
143
- triggers: hasTriggers,
143
+ triggers: !hasTriggers,
144
144
  commit: true,
145
145
  dbConfig
146
146
  });
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',
@@ -172,7 +174,7 @@ class Myt {
172
174
 
173
175
  const defaultConfig = require(`${__dirname}/assets/myt.default.yml`);
174
176
  const config = Object.assign({}, defaultConfig);
175
-
177
+
176
178
  const configFile = 'myt.config.yml';
177
179
  const configPath = path.join(opts.workspace, configFile);
178
180
 
@@ -205,7 +207,7 @@ class Myt {
205
207
  opts.dumpDir = path.join(opts.mytDir, 'dump');
206
208
 
207
209
  // Database configuration
208
-
210
+
209
211
  let iniDir = path.join(__dirname, 'assets');
210
212
  let iniFile = 'db.ini';
211
213
 
@@ -214,10 +216,10 @@ class Myt {
214
216
  iniFile = `${opts.remote}.ini`;
215
217
  }
216
218
  const iniPath = path.join(iniDir, iniFile);
217
-
219
+
218
220
  if (!await fs.pathExists(iniPath))
219
221
  throw new Error(`Database config file not found: ${iniPath}`);
220
-
222
+
221
223
  let dbConfig;
222
224
  try {
223
225
  const iniData = ini.parse(await fs.readFile(iniPath, 'utf8')).client;
@@ -299,12 +301,17 @@ class Myt {
299
301
  AND TABLE_NAME = 'version'`,
300
302
  [opts.versionSchema]
301
303
  );
302
-
304
+
303
305
  if (!res.tableExists) {
304
306
  const structure = await fs.readFile(
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.22",
3
+ "version": "1.5.24",
4
4
  "author": "Verdnatura Levante SL",
5
5
  "description": "MySQL version control",
6
6
  "license": "GPL-3.0",