masterrecord 0.2.17 → 0.2.20

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/Migrations/cli.js CHANGED
@@ -561,6 +561,61 @@ program.option('-V', 'output the version');
561
561
  });
562
562
 
563
563
 
564
+ program
565
+ .command('add-migration-all <name>')
566
+ .alias('ama')
567
+ .description('Create a migration with the given name for all detected contexts')
568
+ .action(function(name){
569
+ var executedLocation = process.cwd();
570
+ try{
571
+ var snapshotFiles = globSearch.sync('**/*_contextSnapShot.json', { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
572
+ if(!(snapshotFiles && snapshotFiles.length)){
573
+ console.log('No context snapshots found. Run enable-migrations-all first.');
574
+ return;
575
+ }
576
+ var created = 0;
577
+ for(const snapRel of snapshotFiles){
578
+ try{
579
+ const snapFile = path.resolve(executedLocation, snapRel);
580
+ let cs;
581
+ try{ cs = require(snapFile); }catch(_){ continue; }
582
+ const snapDir = path.dirname(snapFile);
583
+ const contextAbs = path.resolve(snapDir, cs.contextLocation || '');
584
+ const migBase = path.resolve(snapDir, cs.migrationFolder || '.');
585
+ // Load context
586
+ let ContextCtor;
587
+ try{ ContextCtor = require(contextAbs); }catch(_){
588
+ console.log(`Skipping: cannot load Context at '${contextAbs}'.`);
589
+ continue;
590
+ }
591
+ let contextInstance;
592
+ try{ contextInstance = new ContextCtor(); }catch(_){
593
+ console.log(`Skipping: failed to construct Context from '${contextAbs}'.`);
594
+ continue;
595
+ }
596
+ var migration = new Migration();
597
+ var cleanEntities = migration.cleanEntities(contextInstance.__entities);
598
+ var newEntity = migration.template(name, cs.schema, cleanEntities);
599
+ if(!fs.existsSync(migBase)){
600
+ try{ fs.mkdirSync(migBase, { recursive: true }); }catch(_){ /* ignore */ }
601
+ }
602
+ var migrationDate = Date.now();
603
+ var outputFile = path.join(migBase, `${migrationDate}_${name}_migration.js`);
604
+ fs.writeFileSync(outputFile, newEntity, 'utf8');
605
+ console.log(`Created migration '${path.basename(outputFile)}' for ${path.basename(contextAbs)}`);
606
+ created++;
607
+ }catch(err){
608
+ console.log('Skipping snapshot due to error: ', err);
609
+ }
610
+ }
611
+ if(created === 0){
612
+ console.log('No migrations created.');
613
+ }
614
+ }catch(e){
615
+ console.log('Error - Cannot create migrations for all contexts ', e);
616
+ }
617
+ });
618
+
564
619
  program
565
620
  .command('update-database-all')
566
621
  .alias('uda')
@@ -569,7 +624,7 @@ program.option('-V', 'output the version');
569
624
  var executedLocation = process.cwd();
570
625
  try{
571
626
  // Find all context snapshots and run update per snapshot (avoids unrelated framework contexts)
572
- var snapshotFiles = globSearch.sync('**/*_contextSnapShot.json', executedLocation);
627
+ var snapshotFiles = globSearch.sync('**/*_contextSnapShot.json', { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true });
573
628
  if(!(snapshotFiles && snapshotFiles.length)){
574
629
  console.log('No context snapshots found. Run enable-migrations for each context first.');
575
630
  return;
@@ -580,14 +635,21 @@ program.option('-V', 'output the version');
580
635
  const snapFile = path.resolve(executedLocation, snapRel);
581
636
  let cs;
582
637
  try{ cs = require(snapFile); }catch(_){ continue; }
638
+ const snapDir = path.dirname(snapFile);
639
+ const contextAbs = path.resolve(snapDir, cs.contextLocation || '');
640
+ let migBase = path.resolve(snapDir, cs.migrationFolder || '.');
583
641
  const nameFromPath = path.basename(snapFile).replace(/_contextSnapShot\.json$/i, '').toLowerCase();
584
- const ctxName = (cs && cs.contextLocation)
585
- ? path.basename(cs.contextLocation).replace(/\.js$/i, '').toLowerCase()
586
- : nameFromPath;
587
- const migRel = globSearch.sync('**/*_migration.js', cs.migrationFolder) || [];
588
- const migs = migRel.map(f => path.resolve(cs.migrationFolder, f));
642
+ const ctxName = contextAbs ? path.basename(contextAbs).replace(/\.js$/i, '').toLowerCase() : nameFromPath;
643
+ // Find migrations in snapshot's migrationFolder; fallback to <ContextDir>/db/migrations
644
+ let migRel = globSearch.sync('**/*_migration.js', { cwd: migBase, dot: true, windowsPathsNoEscape: true, nocase: true }) || [];
645
+ if(!(migRel && migRel.length)){
646
+ const defaultFolder = path.join(path.dirname(contextAbs || snapFile), 'db', 'migrations');
647
+ migRel = globSearch.sync('**/*_migration.js', { cwd: defaultFolder, dot: true, windowsPathsNoEscape: true, nocase: true }) || [];
648
+ if(migRel && migRel.length){ migBase = defaultFolder; }
649
+ }
650
+ const migs = migRel.map(f => path.resolve(migBase, f));
589
651
  if(!groups[ctxName]) groups[ctxName] = [];
590
- groups[ctxName].push({ snapFile, cs, ctxName, migs });
652
+ groups[ctxName].push({ snapFile, snapDir, cs, ctxName, migs, contextAbs, migBase });
591
653
  }
592
654
 
593
655
  var migration = new Migration();
@@ -608,8 +670,8 @@ program.option('-V', 'output the version');
608
670
  var mFile = mFiles[mFiles.length - 1];
609
671
 
610
672
  var ContextCtor;
611
- try{ ContextCtor = require(entry.cs.contextLocation); }catch(_){
612
- console.log(`Skipping ${entry.ctxName}: cannot load Context at '${entry.cs.contextLocation}'.`);
673
+ try{ ContextCtor = require(entry.contextAbs); }catch(_){
674
+ console.log(`Skipping ${entry.ctxName}: cannot load Context at '${entry.contextAbs}'.`);
613
675
  continue;
614
676
  }
615
677
  var contextInstance;
@@ -623,7 +685,7 @@ program.option('-V', 'output the version');
623
685
  var tableObj = migration.buildUpObject(entry.cs.schema, cleanEntities);
624
686
  newMigrationProjectInstance.up(tableObj);
625
687
  var snap = {
626
- file : entry.cs.contextLocation,
688
+ file : entry.contextAbs,
627
689
  executedLocation : executedLocation,
628
690
  context : contextInstance,
629
691
  contextEntities : cleanEntities,
@@ -640,6 +702,58 @@ program.option('-V', 'output the version');
640
702
  }
641
703
  });
642
704
 
705
+ program
706
+ .command('enable-migrations-all')
707
+ .alias('ema')
708
+ .description('Enable migrations for all detected MasterRecord Context files')
709
+ .action(function(){
710
+ var executedLocation = process.cwd();
711
+ try{
712
+ // Find candidate Context files
713
+ var candidates = globSearch.sync('**/*Context.js', { cwd: executedLocation, dot: true, windowsPathsNoEscape: true, nocase: true }) || [];
714
+ if(!(candidates && candidates.length)){
715
+ console.log('No Context files found.');
716
+ return;
717
+ }
718
+ var seen = new Set();
719
+ var enabled = 0;
720
+ var migration = new Migration();
721
+ for(const rel of candidates){
722
+ try{
723
+ const abs = path.resolve(executedLocation, rel);
724
+ // Skip node_modules
725
+ if(abs.indexOf('node_modules') !== -1){ continue; }
726
+ // Heuristic filter: file must look like a MasterRecord context
727
+ let text = '';
728
+ try{ text = fs.readFileSync(abs, 'utf8'); }catch(_){ continue; }
729
+ const looksLikeContext = /extends\s+masterrecord\.context/i.test(text) || /require\(['"]masterrecord['"]\)/i.test(text);
730
+ if(!looksLikeContext){ continue; }
731
+ const ctxName = path.basename(abs).replace(/\.js$/i,'');
732
+ const key = ctxName.toLowerCase();
733
+ if(seen.has(key)){ continue; }
734
+ seen.add(key);
735
+ // Create snapshot relative to the context file directory
736
+ var snap = {
737
+ file : abs,
738
+ executedLocation : executedLocation,
739
+ contextEntities : [],
740
+ contextFileName: key
741
+ };
742
+ migration.createSnapShot(snap);
743
+ console.log(`migrations enabled for ${ctxName}`);
744
+ enabled++;
745
+ }catch(err){
746
+ console.log('Skipping candidate due to error: ', err);
747
+ }
748
+ }
749
+ if(enabled === 0){
750
+ console.log('No eligible MasterRecord Contexts detected.');
751
+ }
752
+ }catch(e){
753
+ console.log('Error - Failed to enable migrations for all contexts ', e);
754
+ }
755
+ });
756
+
643
757
 
644
758
  program.parse(process.argv);
645
759
 
@@ -218,10 +218,14 @@ class Migrations{
218
218
  }
219
219
 
220
220
  const snapshotPath = path.join(migrationsDirectory, `${snap.contextFileName}_contextSnapShot.json`);
221
+ // Store relative paths (portable): values are relative to the snapshot file directory (migrationsDirectory)
222
+ const relContextLocation = path.relative(migrationsDirectory, snap.file);
223
+ const relMigrationFolder = '.'; // the snapshot sits inside migrationsDirectory
224
+ const relSnapshotLocation = path.basename(snapshotPath);
221
225
  var content = {
222
- contextLocation: snap.file,
223
- migrationFolder: migrationsDirectory,
224
- snapShotLocation: snapshotPath,
226
+ contextLocation: relContextLocation,
227
+ migrationFolder: relMigrationFolder,
228
+ snapShotLocation: relSnapshotLocation,
225
229
  schema : snap.contextEntities
226
230
  };
227
231
 
@@ -317,7 +321,6 @@ class Migrations{
317
321
  return MT.get();
318
322
  }
319
323
 
320
-
321
324
  }
322
325
 
323
326
  module.exports = Migrations;
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "app-root-path": "^3.1.0",
10
10
  "better-sqlite3": "^12.4.1"
11
11
  },
12
- "version": "0.2.17",
12
+ "version": "0.2.20",
13
13
  "description": "An Object-relational mapping for the Master framework. Master Record connects classes to relational database tables to establish a database with almost zero-configuration ",
14
14
  "homepage": "https://github.com/Tailor/MasterRecord#readme",
15
15
  "repository": {
package/readme.md CHANGED
@@ -179,4 +179,106 @@ Notes:
179
179
  - For large SQLite tables, a rebuild copies data; consider maintenance windows.
180
180
  - Use `master=development masterrecord get-migrations AppContext` to inspect migration order.
181
181
 
182
+ ## Multi-context (multi-database) projects
183
+
184
+ When your project defines multiple Context files (e.g., `userContext.js`, `modelContext.js`, `mailContext.js`, `chatContext.js`) across different packages or feature directories, MasterRecord can auto-detect and operate on all of them.
185
+
186
+ ### New bulk commands
187
+
188
+ - enable-migrations-all (alias: ema)
189
+ - Scans the project for MasterRecord Context files (heuristic) and enables migrations for each by writing a portable snapshot next to the context at `<ContextDir>/db/migrations/<context>_contextSnapShot.json`.
190
+
191
+ - add-migration-all <Name> (alias: ama)
192
+ - Creates a migration named `<Name>` (e.g., `Init`) for every detected context that has a snapshot. Migrations are written into each context’s own migrations folder.
193
+
194
+ - update-database-all (alias: uda)
195
+ - Applies the latest migration for every detected context with migrations.
196
+
197
+ - update-database-down <ContextName> (alias: udd)
198
+ - Runs the latest migration’s `down()` for the specified context.
199
+
200
+ - update-database-target <migrationFileName> (alias: udt)
201
+ - Rolls back migrations newer than the given migration file within that context’s migrations folder.
202
+
203
+ - ensure-database <ContextName> (alias: ed)
204
+ - For MySQL contexts, ensures the database exists (like EF’s `Database.EnsureCreated`). Auto-detects connection info from your Context env settings.
205
+
206
+ ### Portable snapshots (no hardcoded absolute paths)
207
+
208
+ Snapshots are written with relative paths, so moving/renaming the project root does not break CLI resolution:
209
+ - `contextLocation`: path from the migrations folder to the Context file
210
+ - `migrationFolder`: `.` (the snapshot resides in the migrations folder)
211
+ - `snapShotLocation`: the snapshot filename
212
+
213
+ ### Typical flow for multiple contexts
214
+
215
+ 1) Enable migrations everywhere:
216
+ ```bash
217
+ # macOS/Linux
218
+ master=development masterrecord enable-migrations-all
219
+
220
+ # Windows PowerShell
221
+ $env:master = 'development'
222
+ masterrecord enable-migrations-all
223
+ ```
224
+
225
+ 2) Create an initial migration for all contexts:
226
+ ```bash
227
+ # macOS/Linux
228
+ master=development masterrecord add-migration-all Init
229
+
230
+ # Windows PowerShell
231
+ $env:master = 'development'
232
+ masterrecord add-migration-all Init
233
+ ```
234
+
235
+ 3) Apply migrations everywhere:
236
+ ```bash
237
+ # macOS/Linux
238
+ master=development masterrecord update-database-all
239
+
240
+ # Windows PowerShell
241
+ $env:master = 'development'
242
+ masterrecord update-database-all
243
+ ```
244
+
245
+ 4) Inspect migrations for a specific context:
246
+ ```bash
247
+ # macOS/Linux
248
+ master=development masterrecord get-migrations userContext
249
+
250
+ # Windows PowerShell
251
+ $env:master = 'development'
252
+ masterrecord get-migrations userContext
253
+ ```
254
+
255
+ 5) Roll back latest for a specific context:
256
+ ```bash
257
+ # macOS/Linux
258
+ master=development masterrecord update-database-down userContext
259
+
260
+ # Windows PowerShell
261
+ $env:master = 'development'
262
+ masterrecord update-database-down userContext
263
+ ```
264
+
265
+ ### Environment selection (cross-platform)
266
+ - macOS/Linux prefix: `master=development ...` or `NODE_ENV=development ...`
267
+ - Windows PowerShell:
268
+ ```powershell
269
+ $env:master = 'development'
270
+ masterrecord update-database-all
271
+ ```
272
+ - Windows cmd.exe:
273
+ ```cmd
274
+ set master=development && masterrecord update-database-all
275
+ ```
276
+
277
+ ### Notes and tips
278
+ - Each Context should define its own env settings and tables; `update-database-all` operates context-by-context so separate databases are handled cleanly.
279
+ - For SQLite contexts, the `connection` path will be created if the directory does not exist.
280
+ - For MySQL contexts, `ensure-database <ContextName>` can create the DB (permissions required) before migrations run.
281
+ - If you rename/move the project root, re-run `enable-migrations-all` or any single-context command once; snapshots use relative paths and will continue working.
282
+ - If `update-database-all` reports “no migration files found” for a context, run `get-migrations <ContextName>`. If empty, create a migration with `add-migration <Name> <ContextName>` or use `add-migration-all <Name>`.
283
+
182
284