Skip to content

Commit

Permalink
add function to repluggable app debug (#262)
Browse files Browse the repository at this point in the history
add function to repluggable app debug
  • Loading branch information
nircwix authored Dec 11, 2024
1 parent 8194b97 commit e023878
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/repluggableAppDebug/debug.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EntryPoint, AppHost, AnySlotKey, LazyEntryPointFactory, PrivateShell } from '../API'
import { EntryPoint, AppHost, AnySlotKey, LazyEntryPointFactory, PrivateShell, SlotKey } from '../API'
import { AnyExtensionSlot } from '../extensionSlot'
import { Hot } from '../hot'

Expand All @@ -23,6 +23,7 @@ export interface RepluggableAppDebugInfo {
export interface RepluggableDebugUtils {
apis(): APIDebugInfo[]
unReadyEntryPoints(): EntryPoint[]
getRootUnreadyAPI: () => SlotKey<any>[]
whyEntryPointUnready(name: string): void
findAPI(name: string): APIDebugInfo[]
}
Expand Down
52 changes: 52 additions & 0 deletions src/repluggableAppDebug/repluggableAppDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,57 @@ function mapApiToEntryPoint(allPackages: EntryPoint[]) {
return apiToEntryPoint
}

/**
* a function that returns all the entry points in the system with their declared APIs and dependencies
*/
const getAllEntryPoints = () => {
return [
...window.repluggableAppDebug.utils.unReadyEntryPoints(),
...[...window.repluggableAppDebug.addedShells].map(([_, shell]) => shell.entryPoint)
]
}

/**
* this function is used to get the root unready API in case there are too many to understand.
* for example if you have 200 unready entry points, running this function will give you the first unready API that
* will unblock the rest of the entry point (note that there might be more than one)
*
* this function basically takes the first unready entry point, get its dependencies and iterates over them to find an API that is not ready
* at this point it follows the same process recursively until it reaced the target API.
*/
const getRootUnreadyAPI = (host: AppHost) => {
return () => {
// get all unready entry points
const allEntryPoints = getAllEntryPoints()
const unReadyAPIsArray = []
// get the depdenencies of the first unready entry point
let dependenciesOfUnreadyEntryPoint = allEntryPoints?.[0]?.getDependencyAPIs?.()

while (dependenciesOfUnreadyEntryPoint?.length) {
const currentAPI = dependenciesOfUnreadyEntryPoint.pop()

if (!currentAPI) {
continue
}
// try to get the API from this host, we are looking for an API that is not ready
try {
const api = host.getAPI(currentAPI as SlotKey<any>)
if (api) {
continue
}
} catch (e) {
unReadyAPIsArray.push(currentAPI)
// we found an API that is unready, lets find which entry point declares it
const declarer = allEntryPoints.find(entryPointData =>
entryPointData.declareAPIs?.().some(api => currentAPI?.name === api.name)
)
dependenciesOfUnreadyEntryPoint = declarer?.getDependencyAPIs?.()
}
}
return unReadyAPIsArray.reverse()
}
}

const getAPIOrEntryPointsDependencies = (
apisOrEntryPointsNames: string[],
entryPoints: EntryPoint[]
Expand Down Expand Up @@ -95,6 +146,7 @@ export function setupDebugInfo({
}
})
},
getRootUnreadyAPI: getRootUnreadyAPI(host),
unReadyEntryPoints: (): EntryPoint[] => getUnreadyEntryPoints(),
whyEntryPointUnready: (name: string) => {
const unreadyEntryPoint = _.find(
Expand Down
118 changes: 118 additions & 0 deletions test/repluggableAppDebug.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { createAppHost } from '../src'

const unreadAPI = { name: 'unreadyAPI' }

const getUnreadyAPIs = async () => {
await new Promise(resolve => setTimeout(resolve, 0))
return window.repluggableAppDebug.utils.getRootUnreadyAPI()
}

describe('RepluggableAppDebug', () => {
describe('getRootUnreadyAPI', () => {
it('should not report any issues in case there are no entry points', async () => {
createAppHost([])

const unreadyAPIs = await getUnreadyAPIs()

expect(unreadyAPIs).toEqual([])
})

it('should not report any issues in case all entry points are loaded', async () => {
createAppHost([
{
name: 'entryPoint',
declareAPIs: () => []
}
])

const unreadyAPIs = await getUnreadyAPIs()

expect(unreadyAPIs).toEqual([])
})

it('should return an API if its not ready', async () => {
createAppHost([
{
name: 'entryPoint',
getDependencyAPIs: () => [unreadAPI]
}
])

const unreadyAPIs = await getUnreadyAPIs()

expect(unreadyAPIs).toEqual([{ name: 'unreadyAPI' }])
})

it('should return the root unready API when there are multiple entry points that depend on the same API', async () => {
createAppHost([
{
name: 'entryPoint A',
getDependencyAPIs: () => [unreadAPI]
},
{
name: 'entryPoint B',
getDependencyAPIs: () => [unreadAPI]
},
{
name: 'entryPoint C',
getDependencyAPIs: () => [unreadAPI]
}
])

const unreadyAPIs = await getUnreadyAPIs()

expect(unreadyAPIs).toEqual([{ name: 'unreadyAPI' }])
})

it('should return the root unready API when there is a graph of dependencies', async () => {
createAppHost([
{
name: 'entryPoint A',
declareAPIs: () => [{ name: 'API A' }],
getDependencyAPIs: () => [{ name: 'API B' }]
},
{
name: 'entryPoint B',
declareAPIs: () => [{ name: 'API B' }],
getDependencyAPIs: () => [{ name: 'API C' }]
},
{
name: 'entryPoint C',
declareAPIs: () => [{ name: 'API C' }],
getDependencyAPIs: () => [unreadAPI]
}
])

const unreadyAPIs = await getUnreadyAPIs()

expect(unreadyAPIs).toEqual([{ name: 'unreadyAPI' }, { name: 'API C' }, { name: 'API B' }])
})

it('should return the root unready API when there is a graph of dependencies (declation order is reversed)', async () => {
createAppHost(
[
{
name: 'entryPoint A',
declareAPIs: () => [{ name: 'API A' }],
getDependencyAPIs: () => [{ name: 'API B' }]
},
{
name: 'entryPoint B',
declareAPIs: () => [{ name: 'API B' }],
getDependencyAPIs: () => [{ name: 'API C' }]
},
{
name: 'entryPoint C',
declareAPIs: () => [{ name: 'API C' }],
getDependencyAPIs: () => [unreadAPI]
}
].reverse()
)

const unreadyAPIs = await getUnreadyAPIs()
// because we take the first unready entry point, and in this case its the root,
// we don't get the rest of the unready APIs
expect(unreadyAPIs).toEqual([{ name: 'unreadyAPI' }])
})
})
})

0 comments on commit e023878

Please sign in to comment.