masterrecord 0.3.36 → 0.3.38
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/.claude/settings.local.json +4 -1
- package/GLOBAL_REGISTRY_VERIFICATION.md +375 -0
- package/context.js +48 -12
- package/package.json +1 -1
- package/readme.md +133 -76
- package/test/config-glob-pattern-test.js +232 -0
- package/test/global-model-registry-test.js +538 -0
package/readme.md
CHANGED
|
@@ -3369,13 +3369,103 @@ user.name = null; // Error if name is { nullable: false }
|
|
|
3369
3369
|
|
|
3370
3370
|
## Changelog
|
|
3371
3371
|
|
|
3372
|
-
### Version 0.3.
|
|
3372
|
+
### Version 0.3.38 (2026-02-06) - GLOBAL MODEL REGISTRY (UX FIX)
|
|
3373
|
+
|
|
3374
|
+
#### Enhancement: Eliminates Confusing CLI Warnings
|
|
3375
|
+
- **FIXED**: Confusing warnings during normal CLI operation when generating migrations
|
|
3376
|
+
- **Previous Behavior**: v0.3.36/0.3.37 correctly detected duplicate `dbset()` calls and emitted warnings
|
|
3377
|
+
- **Problem**: CLI instantiates the same context class multiple times to inspect schema
|
|
3378
|
+
- **Impact**: Users saw warnings during normal operation: `"Warning: dbset() called multiple times for table 'User'..."`
|
|
3379
|
+
- **User Confusion**: Warnings appeared even when code was correct, making users think they did something wrong
|
|
3380
|
+
|
|
3381
|
+
#### Implementation - Global Model Registry
|
|
3382
|
+
**The Solution** (`context.js`)
|
|
3383
|
+
- Added static `_globalModelRegistry` property to track registered models per context class
|
|
3384
|
+
- Structure: `{ 'userContext': Set(['User', 'Auth', 'Settings']), 'qaContext': Set([...]) }`
|
|
3385
|
+
- Each context instance checks if it's the first instance via `__isFirstInstance` flag
|
|
3386
|
+
- Warnings only appear on the first instance of a context class (genuine bugs)
|
|
3387
|
+
- Subsequent instances (CLI pattern) are silent since they're expected
|
|
3373
3388
|
|
|
3374
|
-
|
|
3389
|
+
**How It Works:**
|
|
3390
|
+
|
|
3391
|
+
1. **First Instance** (constructor execution):
|
|
3392
|
+
```javascript
|
|
3393
|
+
const ctx1 = new userContext();
|
|
3394
|
+
// __isFirstInstance = true (global registry empty)
|
|
3395
|
+
// dbset(User) - adds User to global registry
|
|
3396
|
+
// dbset(User) again - WARNS (duplicate in same constructor)
|
|
3397
|
+
// dbset(Auth) - adds Auth to global registry
|
|
3398
|
+
```
|
|
3399
|
+
|
|
3400
|
+
2. **Subsequent Instances** (CLI creates multiple):
|
|
3401
|
+
```javascript
|
|
3402
|
+
const ctx2 = new userContext();
|
|
3403
|
+
// __isFirstInstance = false (global registry has User, Auth)
|
|
3404
|
+
// dbset(User) - no warning (expected pattern)
|
|
3405
|
+
// dbset(User) again - no warning (expected pattern)
|
|
3406
|
+
// dbset(Auth) - no warning (expected pattern)
|
|
3407
|
+
```
|
|
3408
|
+
|
|
3409
|
+
3. **Duplicate Detection Still Works**:
|
|
3410
|
+
- If user's constructor has `dbset(User)` called twice, the first instance warns
|
|
3411
|
+
- This guides users to fix their code (remove the duplicate)
|
|
3412
|
+
- After fixing, all future CLI operations are silent
|
|
3413
|
+
|
|
3414
|
+
**Benefits:**
|
|
3415
|
+
- ✅ **Clean CLI Output**: No spurious warnings during `masterrecord add-migration`
|
|
3416
|
+
- ✅ **Genuine Bug Detection**: Still warns about actual duplicates in user code
|
|
3417
|
+
- ✅ **Better UX**: Users no longer confused by normal operation warnings
|
|
3418
|
+
- ✅ **Backward Compatible**: Existing code continues to work
|
|
3419
|
+
- ✅ **Industry-Standard Pattern**: Matches how TypeORM, Sequelize, Mongoose handle multiple instances
|
|
3420
|
+
|
|
3421
|
+
**Files Modified:**
|
|
3422
|
+
1. `context.js` - Added `_globalModelRegistry` static property, `__isFirstInstance` instance flag, updated `dbset()` logic
|
|
3423
|
+
2. `test/global-model-registry-test.js` (NEW) - 15 comprehensive tests covering:
|
|
3424
|
+
- Multiple context instances (CLI pattern) - no warnings ✅
|
|
3425
|
+
- Genuine duplicates in constructor - warns once ✅
|
|
3426
|
+
- Multiple context classes with same models - no warnings ✅
|
|
3427
|
+
- Registry isolation between context classes ✅
|
|
3428
|
+
- Edge cases (empty contexts, large contexts, mixed registration) ✅
|
|
3429
|
+
3. `package.json` - Updated version to 0.3.38
|
|
3430
|
+
4. `readme.md` - Added changelog entry
|
|
3431
|
+
|
|
3432
|
+
**Test Results:**
|
|
3433
|
+
- **15 new tests** - All passing ✅
|
|
3434
|
+
- Tests verify CLI pattern (3 instances) produces zero warnings
|
|
3435
|
+
- Tests verify genuine duplicates still warn on first instance only
|
|
3436
|
+
- Tests verify different context classes have separate registries
|
|
3437
|
+
- Tests verify large contexts (50 models) work without warnings
|
|
3438
|
+
|
|
3439
|
+
**Upgrade Path:**
|
|
3440
|
+
```bash
|
|
3441
|
+
npm install -g masterrecord@0.3.38
|
|
3442
|
+
```
|
|
3443
|
+
No code changes needed - automatic improvement to CLI experience.
|
|
3444
|
+
|
|
3445
|
+
**Real-World Example:**
|
|
3446
|
+
|
|
3447
|
+
Before v0.3.38:
|
|
3448
|
+
```bash
|
|
3449
|
+
$ masterrecord add-migration CreateUsers userContext
|
|
3450
|
+
Warning: dbset() called multiple times for table 'User' - updating existing registration
|
|
3451
|
+
Warning: dbset() called multiple times for table 'Auth' - updating existing registration
|
|
3452
|
+
Warning: dbset() called multiple times for table 'Settings' - updating existing registration
|
|
3453
|
+
✓ Migration 'CreateUsers' created successfully
|
|
3454
|
+
```
|
|
3455
|
+
|
|
3456
|
+
After v0.3.38:
|
|
3457
|
+
```bash
|
|
3458
|
+
$ masterrecord add-migration CreateUsers userContext
|
|
3459
|
+
✓ Migration 'CreateUsers' created successfully
|
|
3460
|
+
```
|
|
3461
|
+
|
|
3462
|
+
---
|
|
3463
|
+
|
|
3464
|
+
### Version 0.3.36 (2026-02-06) - ROOT CAUSE FIX + CONFIG DISCOVERY FIX
|
|
3465
|
+
|
|
3466
|
+
#### Critical Bug Fix #1: Duplicate Entities and Seed Data - Complete Resolution
|
|
3375
3467
|
- **FIXED**: Root cause of duplicate entities and seed data in migrations
|
|
3376
|
-
- **
|
|
3377
|
-
- **This Fix (v0.3.36)**: Addresses the actual root cause in context registration
|
|
3378
|
-
- **Pattern**: User contexts calling `dbset(Entity)` then later `dbset(Entity).seed(data)` caused:
|
|
3468
|
+
- **Root Cause**: User contexts calling `dbset(Entity)` then later `dbset(Entity).seed(data)` caused:
|
|
3379
3469
|
- Entity registered twice in `__entities` array
|
|
3380
3470
|
- Seed data duplicated in `__contextSeedData`
|
|
3381
3471
|
- Snapshots containing duplicate table definitions
|
|
@@ -3395,99 +3485,66 @@ user.name = null; // Error if name is { nullable: false }
|
|
|
3395
3485
|
- Emits warning: `"Warning: seed() called multiple times for table 'X' - using upsert semantics..."`
|
|
3396
3486
|
- User requested this approach to match EF Core behavior
|
|
3397
3487
|
|
|
3488
|
+
#### Critical Bug Fix #2: Config File Discovery - Glob Pattern Too Broad
|
|
3489
|
+
- **FIXED**: Glob pattern was matching non-environment config files
|
|
3490
|
+
- **Root Cause**: Pattern `${rootFolder}/**/*{env.${envType},${envType}}.json` matched ANY file ending with `.${envType}.json`
|
|
3491
|
+
- **Impact**: When multiple files matched (e.g., `free-audit-page.development.json` and `env.development.json`), glob returned them alphabetically and the wrong file was loaded first
|
|
3492
|
+
- **Real-World Example**:
|
|
3493
|
+
- User had `config/environments/free-audit-page.development.json` (no context configs)
|
|
3494
|
+
- User had `config/environments/env.development.json` (has userContext config)
|
|
3495
|
+
- Glob found both, returned `free-audit-page.development.json` first (alphabetically)
|
|
3496
|
+
- Migration command failed: "Configuration missing settings for context 'userContext'"
|
|
3497
|
+
- **Fix**: Split into two specific patterns with priority:
|
|
3498
|
+
1. `**/env.${envType}.json` (preferred - exact match)
|
|
3499
|
+
2. `**/${envType}.json` (fallback - exact match)
|
|
3500
|
+
- **Result**: Only matches actual environment config files, not arbitrary files
|
|
3501
|
+
|
|
3502
|
+
**Fix #3: Config File Priority Pattern** (`context.js` lines 445-470)
|
|
3503
|
+
- Changed from single broad pattern to prioritized specific patterns
|
|
3504
|
+
- Tries `env.<envType>.json` first (most specific)
|
|
3505
|
+
- Falls back to `<envType>.json` (less specific)
|
|
3506
|
+
- Prevents false positives from files like `my-config.development.json`
|
|
3507
|
+
|
|
3398
3508
|
#### Technical Details
|
|
3399
3509
|
**Files Modified:**
|
|
3400
3510
|
1. `context.js` - Added deduplication logic in `dbset()` and `#addSeedData()`
|
|
3401
|
-
2. `
|
|
3402
|
-
3. `test/
|
|
3403
|
-
4. `test/
|
|
3404
|
-
5. `
|
|
3405
|
-
6. `
|
|
3511
|
+
2. `context.js` - Fixed glob pattern for config file discovery (lines 445-470)
|
|
3512
|
+
3. `test/entity-deduplication-test.js` (NEW) - 5 tests for entity deduplication
|
|
3513
|
+
4. `test/seed-deduplication-test.js` (NEW) - 8 tests for EF Core seed semantics
|
|
3514
|
+
5. `test/qa-context-pattern-test.js` (NEW) - 7 tests for real-world patterns
|
|
3515
|
+
6. `test/config-glob-pattern-test.js` (NEW) - 7 tests for config file discovery
|
|
3516
|
+
7. `package.json` - Updated version to 0.3.36
|
|
3517
|
+
8. `readme.md` - Added changelog and documentation
|
|
3406
3518
|
|
|
3407
3519
|
**Test Results:**
|
|
3408
|
-
- **
|
|
3520
|
+
- **27 new tests** - All passing ✅
|
|
3521
|
+
- 20 tests for duplicate entity/seed data fixes
|
|
3522
|
+
- 7 tests for config file discovery fix
|
|
3409
3523
|
- Tests cover the exact qaContext pattern (lines 58 + 207) that caused the bug
|
|
3410
3524
|
- Tests verify 9 seeds stay as 9 (not 18), Settings stay as 2 (not 4)
|
|
3525
|
+
- Tests verify glob pattern only matches environment config files
|
|
3411
3526
|
|
|
3412
3527
|
#### Upgrade Path
|
|
3413
3528
|
1. **Update to v0.3.36**: `npm install -g masterrecord@0.3.36`
|
|
3414
|
-
2. **If you have duplicate data in your database from
|
|
3529
|
+
2. **If you have duplicate data in your database from older versions**:
|
|
3415
3530
|
- Manually remove duplicate records (check by primary key)
|
|
3416
3531
|
- Delete existing snapshots: `rm db/migrations/*_contextSnapShot.json`
|
|
3417
3532
|
- Regenerate migrations: `masterrecord add-migration YourContext "clean-regenerate"`
|
|
3418
3533
|
3. **Future migrations**: Will automatically deduplicate entities and seed data
|
|
3419
3534
|
|
|
3420
|
-
#### Why This Fix is
|
|
3421
|
-
- **
|
|
3422
|
-
- **
|
|
3423
|
-
- **Defense-in-
|
|
3535
|
+
#### Why This Fix is Comprehensive
|
|
3536
|
+
- **Root Cause Fix**: Prevents duplicates from ever being created at registration time
|
|
3537
|
+
- **EF Core Semantics**: Industry-standard seed data pattern with idempotent operations
|
|
3538
|
+
- **Defense-in-Depth**: Multiple layers of protection ensure data integrity
|
|
3424
3539
|
|
|
3425
3540
|
#### Impact
|
|
3426
3541
|
- ✅ Entities registered only once even with multiple `dbset()` calls
|
|
3427
3542
|
- ✅ Seed data uses EF Core semantics (upsert by primary key)
|
|
3428
3543
|
- ✅ Warning messages guide users to fix their code patterns
|
|
3544
|
+
- ✅ Config file discovery now specific and predictable (only matches env.*.json and *.json)
|
|
3545
|
+
- ✅ Migrations no longer fail when non-environment config files exist
|
|
3429
3546
|
- ✅ Backward compatible - existing single `dbset()` calls work as before
|
|
3430
3547
|
|
|
3431
|
-
### Version 0.3.35 (2026-02-05) - CRITICAL FIX
|
|
3432
|
-
|
|
3433
|
-
#### Critical Bug Fix
|
|
3434
|
-
- **FIXED**: Duplicate table operations in migrations when snapshots contain duplicate table definitions
|
|
3435
|
-
- **Root Cause**: If a context registered the same entity twice (via multiple `dbset()` calls), the snapshot would contain duplicate table definitions with the same `__name`
|
|
3436
|
-
- **Impact**: Migrations would generate duplicate `createTable()` and `seed()` calls, causing duplicate records in the database
|
|
3437
|
-
- **Examples**:
|
|
3438
|
-
- ragContext: Settings table appeared twice → 2x seed insertions
|
|
3439
|
-
- qaContext: TaxonomyTemplate appeared twice → 18 template records instead of 9
|
|
3440
|
-
- **Fix**: Added deduplication in `#organizeSchemaByTables()` using a `Set` to track processed table names
|
|
3441
|
-
- **User Impact**: If you ran migrations from v0.3.34 or earlier and have duplicate records:
|
|
3442
|
-
1. Manually remove duplicates from your database
|
|
3443
|
-
2. Regenerate migrations with v0.3.35: `masterrecord add-migration YourContext "regenerate"`
|
|
3444
|
-
3. Future migrations will not create duplicates
|
|
3445
|
-
|
|
3446
|
-
#### Technical Details
|
|
3447
|
-
- Modified `Migrations/migrations.js` - `#organizeSchemaByTables()` method
|
|
3448
|
-
- Added `seenTableNames` Set to track processed tables and prevent duplicates
|
|
3449
|
-
- Emits warning: `"Warning: Duplicate table definition detected for 'TableName' - using first occurrence only"`
|
|
3450
|
-
- Works for both initial migrations (`oldSchema.length === 0`) and subsequent migrations
|
|
3451
|
-
|
|
3452
|
-
#### Testing
|
|
3453
|
-
- Added 2 new tests for duplicate table scenarios (13 total tests)
|
|
3454
|
-
- All tests passing with duplicate detection and deduplication verified
|
|
3455
|
-
|
|
3456
|
-
### Version 0.3.34 (2026-02-05) - PARTIALLY EFFECTIVE
|
|
3457
|
-
|
|
3458
|
-
⚠️ **Note**: This version fixed the seed API bug but did NOT fix duplicate operations. Use v0.3.35 instead.
|
|
3459
|
-
|
|
3460
|
-
#### Bug Fixes
|
|
3461
|
-
- **Fixed**: Seed API migration generation - resolved `table.EntityName.create is not a function` error
|
|
3462
|
-
- Migrations now use `this.seed('TableName', data)` instead of `table.TableName.create(data)`
|
|
3463
|
-
- Ensures compatibility with the migration schema base class
|
|
3464
|
-
- **Fixed**: Missing `await` keywords on `createIndex()`, `dropIndex()`, `createCompositeIndex()`, and `dropCompositeIndex()` calls in migrations
|
|
3465
|
-
- All async index operations now properly awaited for consistency
|
|
3466
|
-
- ~~**Fixed**: Eliminated duplicate table creation statements~~ ❌ **NOT ACTUALLY FIXED** - see v0.3.35
|
|
3467
|
-
- ~~**Fixed**: Eliminated duplicate seed insertion statements~~ ❌ **NOT ACTUALLY FIXED** - see v0.3.35
|
|
3468
|
-
|
|
3469
|
-
#### Improvements
|
|
3470
|
-
- **Enhanced**: Query builders now use whitelist validation for column definitions
|
|
3471
|
-
- Validates that objects have both `name` and `type` properties before processing as columns
|
|
3472
|
-
- More robust metadata property filtering in schema processing
|
|
3473
|
-
- Combines blacklist (skip `indexes`, `__*` prefixed) with whitelist (require `name` and `type`) for comprehensive validation
|
|
3474
|
-
- **Documentation**: Enhanced migration generation documentation
|
|
3475
|
-
- **Testing**: Added comprehensive test suite for v0.3.34 bug fixes
|
|
3476
|
-
- Tests whitelist validation across all three database backends
|
|
3477
|
-
- Tests async/await consistency in index operations
|
|
3478
|
-
- Tests seed API correctness and deduplication
|
|
3479
|
-
|
|
3480
|
-
#### Technical Details
|
|
3481
|
-
- Query builders (SQLite, MySQL, PostgreSQL) validate column definitions have required `name` and `type` properties
|
|
3482
|
-
- Migration template generates proper ORM API calls for seed data using `this.seed()` method
|
|
3483
|
-
- Index deduplication logic prevents duplicate CREATE INDEX statements
|
|
3484
|
-
- All migration operations consistently use `await` for async consistency
|
|
3485
|
-
|
|
3486
|
-
#### Migration Notes
|
|
3487
|
-
- Existing migrations generated with older versions will continue to work
|
|
3488
|
-
- New migrations will use the corrected seed API syntax
|
|
3489
|
-
- If you have migrations with `table.EntityName.create()` that haven't been run, regenerate them with this version
|
|
3490
|
-
|
|
3491
3548
|
## Version Compatibility
|
|
3492
3549
|
|
|
3493
3550
|
| Component | Version | Notes |
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: Config File Glob Pattern Fix
|
|
3
|
+
* Verifies that the glob pattern only matches environment config files,
|
|
4
|
+
* not arbitrary files ending with .<envType>.json
|
|
5
|
+
*
|
|
6
|
+
* Bug: Old pattern matched too many files:
|
|
7
|
+
* - env.development.json (correct)
|
|
8
|
+
* - development.json (correct)
|
|
9
|
+
* - free-audit-page.development.json (WRONG - should not match)
|
|
10
|
+
*
|
|
11
|
+
* Fix: Use separate patterns with priority:
|
|
12
|
+
* - Pattern 1: env.development.json (preferred)
|
|
13
|
+
* - Pattern 2: development.json (fallback)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
console.log("╔════════════════════════════════════════════════════════════════╗");
|
|
17
|
+
console.log("║ Config File Glob Pattern Test - Specific Matching ║");
|
|
18
|
+
console.log("╚════════════════════════════════════════════════════════════════╝\n");
|
|
19
|
+
|
|
20
|
+
const glob = require('glob');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const os = require('os');
|
|
24
|
+
|
|
25
|
+
let passed = 0;
|
|
26
|
+
let failed = 0;
|
|
27
|
+
|
|
28
|
+
function test(description, fn) {
|
|
29
|
+
try {
|
|
30
|
+
fn();
|
|
31
|
+
console.log(`✓ ${description}`);
|
|
32
|
+
passed++;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log(`✗ ${description}`);
|
|
35
|
+
console.log(` Error: ${error.message}`);
|
|
36
|
+
failed++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function assertEqual(actual, expected, message) {
|
|
41
|
+
if (actual !== expected) {
|
|
42
|
+
throw new Error(`${message}\n Expected: ${expected}\n Actual: ${actual}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function assertArrayContains(array, value, message) {
|
|
47
|
+
if (!array.includes(value)) {
|
|
48
|
+
throw new Error(`${message}\n Array: ${JSON.stringify(array)}\n Missing: ${value}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function assertArrayNotContains(array, value, message) {
|
|
53
|
+
if (array.includes(value)) {
|
|
54
|
+
throw new Error(`${message}\n Array: ${JSON.stringify(array)}\n Should not contain: ${value}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// Test Suite: Config File Glob Pattern
|
|
60
|
+
// =============================================================================
|
|
61
|
+
console.log("📋 Test Suite: Environment Config File Glob Pattern\n");
|
|
62
|
+
|
|
63
|
+
test('should match env.development.json', () => {
|
|
64
|
+
// Create temp directory structure
|
|
65
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'masterrecord-test-'));
|
|
66
|
+
const configDir = path.join(tmpDir, 'config', 'environments');
|
|
67
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
68
|
+
|
|
69
|
+
// Create test file
|
|
70
|
+
fs.writeFileSync(path.join(configDir, 'env.development.json'), '{}');
|
|
71
|
+
|
|
72
|
+
// Test new pattern
|
|
73
|
+
const pattern1 = `${configDir}/**/env.development.json`;
|
|
74
|
+
const files = glob.sync(pattern1, { cwd: tmpDir, nocase: true });
|
|
75
|
+
|
|
76
|
+
assertArrayContains(files, path.join(configDir, 'env.development.json'),
|
|
77
|
+
'Should match env.development.json');
|
|
78
|
+
|
|
79
|
+
// Cleanup
|
|
80
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('should match development.json as fallback', () => {
|
|
84
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'masterrecord-test-'));
|
|
85
|
+
const configDir = path.join(tmpDir, 'config');
|
|
86
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
87
|
+
|
|
88
|
+
// Create test file
|
|
89
|
+
fs.writeFileSync(path.join(configDir, 'development.json'), '{}');
|
|
90
|
+
|
|
91
|
+
// Test fallback pattern
|
|
92
|
+
const pattern2 = `${configDir}/**/development.json`;
|
|
93
|
+
const files = glob.sync(pattern2, { cwd: tmpDir, nocase: true });
|
|
94
|
+
|
|
95
|
+
assertArrayContains(files, path.join(configDir, 'development.json'),
|
|
96
|
+
'Should match development.json as fallback');
|
|
97
|
+
|
|
98
|
+
// Cleanup
|
|
99
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('should NOT match arbitrary files ending with .development.json', () => {
|
|
103
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'masterrecord-test-'));
|
|
104
|
+
const configDir = path.join(tmpDir, 'config', 'environments');
|
|
105
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
106
|
+
|
|
107
|
+
// Create files
|
|
108
|
+
fs.writeFileSync(path.join(configDir, 'env.development.json'), '{}');
|
|
109
|
+
fs.writeFileSync(path.join(configDir, 'free-audit-page.development.json'), '{}');
|
|
110
|
+
fs.writeFileSync(path.join(configDir, 'my-config.development.json'), '{}');
|
|
111
|
+
|
|
112
|
+
// Test new specific pattern
|
|
113
|
+
const pattern1 = `${configDir}/**/env.development.json`;
|
|
114
|
+
const files = glob.sync(pattern1, { cwd: tmpDir, nocase: true });
|
|
115
|
+
|
|
116
|
+
// Should ONLY match env.development.json
|
|
117
|
+
assertEqual(files.length, 1, 'Should match exactly 1 file');
|
|
118
|
+
assertArrayContains(files, path.join(configDir, 'env.development.json'),
|
|
119
|
+
'Should match env.development.json');
|
|
120
|
+
assertArrayNotContains(files, path.join(configDir, 'free-audit-page.development.json'),
|
|
121
|
+
'Should NOT match free-audit-page.development.json');
|
|
122
|
+
assertArrayNotContains(files, path.join(configDir, 'my-config.development.json'),
|
|
123
|
+
'Should NOT match my-config.development.json');
|
|
124
|
+
|
|
125
|
+
// Cleanup
|
|
126
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('should prioritize env.development.json over development.json', () => {
|
|
130
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'masterrecord-test-'));
|
|
131
|
+
const configDir = path.join(tmpDir, 'config');
|
|
132
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
133
|
+
|
|
134
|
+
// Create both files
|
|
135
|
+
fs.writeFileSync(path.join(configDir, 'env.development.json'), '{"priority": "high"}');
|
|
136
|
+
fs.writeFileSync(path.join(configDir, 'development.json'), '{"priority": "low"}');
|
|
137
|
+
|
|
138
|
+
// Test priority: try env.development.json first
|
|
139
|
+
const pattern1 = `${configDir}/**/env.development.json`;
|
|
140
|
+
const files1 = glob.sync(pattern1, { cwd: tmpDir, nocase: true });
|
|
141
|
+
|
|
142
|
+
if (files1.length > 0) {
|
|
143
|
+
assertEqual(files1[0], path.join(configDir, 'env.development.json'),
|
|
144
|
+
'Should find env.development.json first');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Cleanup
|
|
148
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('should work with nested directory structures', () => {
|
|
152
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'masterrecord-test-'));
|
|
153
|
+
const nestedDir = path.join(tmpDir, 'app', 'config', 'environments', 'production');
|
|
154
|
+
fs.mkdirSync(nestedDir, { recursive: true });
|
|
155
|
+
|
|
156
|
+
// Create test file in nested directory
|
|
157
|
+
fs.writeFileSync(path.join(nestedDir, 'env.development.json'), '{}');
|
|
158
|
+
|
|
159
|
+
// Test pattern from root
|
|
160
|
+
const pattern = `${tmpDir}/**/env.development.json`;
|
|
161
|
+
const files = glob.sync(pattern, { cwd: tmpDir, nocase: true });
|
|
162
|
+
|
|
163
|
+
assertEqual(files.length, 1, 'Should find file in nested directory');
|
|
164
|
+
assertArrayContains(files, path.join(nestedDir, 'env.development.json'),
|
|
165
|
+
'Should match nested env.development.json');
|
|
166
|
+
|
|
167
|
+
// Cleanup
|
|
168
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('should be case insensitive', () => {
|
|
172
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'masterrecord-test-'));
|
|
173
|
+
const configDir = path.join(tmpDir, 'config');
|
|
174
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
175
|
+
|
|
176
|
+
// Create test file with different casing (if filesystem allows)
|
|
177
|
+
try {
|
|
178
|
+
fs.writeFileSync(path.join(configDir, 'ENV.DEVELOPMENT.JSON'), '{}');
|
|
179
|
+
|
|
180
|
+
const pattern = `${configDir}/**/env.development.json`;
|
|
181
|
+
const files = glob.sync(pattern, { cwd: tmpDir, nocase: true });
|
|
182
|
+
|
|
183
|
+
// On case-insensitive filesystems (macOS, Windows), this should match
|
|
184
|
+
if (files.length > 0) {
|
|
185
|
+
console.log(' (Case-insensitive filesystem detected - match confirmed)');
|
|
186
|
+
}
|
|
187
|
+
} catch (e) {
|
|
188
|
+
console.log(' (Case-sensitive filesystem - test skipped)');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Cleanup
|
|
192
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('OLD pattern would have matched too many files (regression check)', () => {
|
|
196
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'masterrecord-test-'));
|
|
197
|
+
const configDir = path.join(tmpDir, 'config', 'environments');
|
|
198
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
199
|
+
|
|
200
|
+
// Create files
|
|
201
|
+
fs.writeFileSync(path.join(configDir, 'env.development.json'), '{}');
|
|
202
|
+
fs.writeFileSync(path.join(configDir, 'free-audit-page.development.json'), '{}');
|
|
203
|
+
|
|
204
|
+
// OLD pattern (the bug)
|
|
205
|
+
const oldPattern = `${configDir}/**/*{env.development,development}.json`;
|
|
206
|
+
const oldFiles = glob.sync(oldPattern, { cwd: tmpDir, nocase: true });
|
|
207
|
+
|
|
208
|
+
// OLD pattern matches BOTH files (bad!)
|
|
209
|
+
assertEqual(oldFiles.length, 2, 'OLD pattern matched 2 files (demonstrating the bug)');
|
|
210
|
+
|
|
211
|
+
// NEW pattern (the fix)
|
|
212
|
+
const newPattern = `${configDir}/**/env.development.json`;
|
|
213
|
+
const newFiles = glob.sync(newPattern, { cwd: tmpDir, nocase: true });
|
|
214
|
+
|
|
215
|
+
// NEW pattern matches only 1 file (good!)
|
|
216
|
+
assertEqual(newFiles.length, 1, 'NEW pattern matches only 1 file (the fix)');
|
|
217
|
+
assertArrayContains(newFiles, path.join(configDir, 'env.development.json'),
|
|
218
|
+
'NEW pattern matches correct file');
|
|
219
|
+
|
|
220
|
+
// Cleanup
|
|
221
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// =============================================================================
|
|
225
|
+
// Summary
|
|
226
|
+
// =============================================================================
|
|
227
|
+
console.log("\n" + "═".repeat(64));
|
|
228
|
+
console.log(`\n✅ Passed: ${passed}`);
|
|
229
|
+
console.log(`❌ Failed: ${failed}`);
|
|
230
|
+
console.log(`\nTotal: ${passed + failed} tests\n`);
|
|
231
|
+
|
|
232
|
+
process.exit(failed > 0 ? 1 : 0);
|