-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: enable framework detection on js workspace root
Fixes FRB-793 It is valid to have a monorepo (PNPM workspace) but still have a site on the root of it This enables the support of still having detection inside monorepo packages but additionally on the root as well
- Loading branch information
1 parent
32f7660
commit 3d70713
Showing
7 changed files
with
255 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,5 +67,6 @@ | |
"lint-staged": { | ||
"!(packages/*/tests/**/fixtures/**/*)*.+(j|t)s": "eslint --ignore-path .gitignore --cache --fix", | ||
"*": "prettier --write --ignore-unknown" | ||
} | ||
}, | ||
"packageManager": "[email protected]+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { describe, beforeEach, test, expect } from 'vitest' | ||
|
||
import { Mutex } from './mutex.js' | ||
|
||
describe('Mutex', () => { | ||
let mutex: Mutex | ||
|
||
beforeEach(() => { | ||
mutex = new Mutex() | ||
}) | ||
|
||
test('should acquire and release the lock for a single operation', async () => { | ||
let isLocked = false | ||
|
||
await mutex.runExclusive(async () => { | ||
isLocked = mutex['_isLocked'] | ||
expect(isLocked).toBe(true) | ||
}) | ||
|
||
isLocked = mutex['_isLocked'] | ||
expect(isLocked).toBe(false) | ||
}) | ||
|
||
test('should run operations sequentially', async () => { | ||
const results: number[] = [] | ||
|
||
const task = (id: number) => { | ||
return mutex.runExclusive(async () => { | ||
results.push(id) | ||
// Simulate async operation | ||
await new Promise((resolve) => setTimeout(resolve, 10)) | ||
}) | ||
} | ||
|
||
await Promise.all([task(1), task(2), task(3)]) | ||
|
||
expect(results).toEqual([1, 2, 3]) | ||
}) | ||
|
||
test('should prevent concurrent access to critical section', async () => { | ||
let concurrentAccesses = 0 | ||
let maxConcurrentAccesses = 0 | ||
|
||
const task = () => { | ||
return mutex.runExclusive(async () => { | ||
concurrentAccesses++ | ||
if (concurrentAccesses > maxConcurrentAccesses) { | ||
maxConcurrentAccesses = concurrentAccesses | ||
} | ||
// Simulate async operation | ||
await new Promise((resolve) => setTimeout(resolve, 10)) | ||
concurrentAccesses-- | ||
}) | ||
} | ||
|
||
await Promise.all([task(), task(), task()]) | ||
|
||
expect(maxConcurrentAccesses).toBe(1) | ||
}) | ||
|
||
test('should release the lock even if an exception occurs', async () => { | ||
let isLockedDuringError = false | ||
|
||
const failingTask = () => { | ||
return mutex.runExclusive(async () => { | ||
isLockedDuringError = mutex['_isLocked'] | ||
throw new Error('Intentional Error') | ||
}) | ||
} | ||
|
||
await expect(failingTask()).rejects.toThrow('Intentional Error') | ||
|
||
const isLockedAfterError = mutex['_isLocked'] | ||
expect(isLockedDuringError).toBe(true) | ||
expect(isLockedAfterError).toBe(false) | ||
}) | ||
|
||
test('should handle mixed successful and failing operations', async () => { | ||
const results: string[] = [] | ||
|
||
const successfulTask = () => { | ||
return mutex.runExclusive(async () => { | ||
results.push('success') | ||
await new Promise((resolve) => setTimeout(resolve, 10)) | ||
}) | ||
} | ||
|
||
const failingTask = () => { | ||
return mutex.runExclusive(async () => { | ||
results.push('fail') | ||
await new Promise((resolve) => setTimeout(resolve, 10)) | ||
throw new Error('Intentional Error') | ||
}) | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
const tasks = [successfulTask(), failingTask().catch(() => {}), successfulTask()] | ||
|
||
await Promise.all(tasks) | ||
|
||
expect(results).toEqual(['success', 'fail', 'success']) | ||
expect(mutex['_isLocked']).toBe(false) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/** | ||
* A lock for synchronizing async operations. | ||
* Use this to protect a critical section | ||
* from getting modified by multiple async operations | ||
* at the same time. | ||
*/ | ||
export class Mutex { | ||
/** | ||
* When multiple operations attempt to acquire the lock, | ||
* this queue remembers the order of operations. | ||
*/ | ||
private _queue: { | ||
resolve: (release: ReleaseFunction) => void | ||
}[] = [] | ||
|
||
private _isLocked = false | ||
|
||
/** | ||
* Wait until the lock is acquired. | ||
* @returns A function that releases the acquired lock. | ||
*/ | ||
acquire() { | ||
return new Promise<ReleaseFunction>((resolve) => { | ||
this._queue.push({ resolve }) | ||
this._dispatch() | ||
}) | ||
} | ||
|
||
/** | ||
* Enqueue a function to be run serially. | ||
* | ||
* This ensures no other functions will start running | ||
* until `callback` finishes running. | ||
* @param callback Function to be run exclusively. | ||
* @returns The return value of `callback`. | ||
*/ | ||
async runExclusive<T>(callback: () => Promise<T>) { | ||
const release = await this.acquire() | ||
try { | ||
return await callback() | ||
} finally { | ||
release() | ||
} | ||
} | ||
|
||
/** | ||
* Check the availability of the resource | ||
* and provide access to the next operation in the queue. | ||
* | ||
* _dispatch is called whenever availability changes, | ||
* such as after lock acquire request or lock release. | ||
*/ | ||
private _dispatch() { | ||
if (this._isLocked) { | ||
// The resource is still locked. | ||
// Wait until next time. | ||
return | ||
} | ||
const nextEntry = this._queue.shift() | ||
if (!nextEntry) { | ||
// There is nothing in the queue. | ||
// Do nothing until next dispatch. | ||
return | ||
} | ||
// The resource is available. | ||
this._isLocked = true // Lock it. | ||
// and give access to the next operation | ||
// in the queue. | ||
nextEntry.resolve(this._buildRelease()) | ||
} | ||
|
||
/** | ||
* Build a release function for each operation | ||
* so that it can release the lock after | ||
* the operation is complete. | ||
*/ | ||
private _buildRelease(): ReleaseFunction { | ||
return () => { | ||
// Each release function make | ||
// the resource available again | ||
this._isLocked = false | ||
// and call dispatch. | ||
this._dispatch() | ||
} | ||
} | ||
} | ||
|
||
type ReleaseFunction = () => void |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters