feat: list discoverable modules in creation wizard
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { dialog, ipcMain } from 'electron'
|
||||
import { getProjectRoot, listInstalledModules, listModules, scaffoldApplicationModule, setModule } from '../auroraEngine'
|
||||
import { runCommandStreamed } from '../commandRunner'
|
||||
import { installModulePackage, uninstallModulePackage } from '../moduleRegistry'
|
||||
import { getAvailableModulePackages, installModulePackage, uninstallModulePackage } from '../moduleRegistry'
|
||||
|
||||
export function registerModulesIpc(): void {
|
||||
ipcMain.handle('modules:listRegistry', () => listModules())
|
||||
ipcMain.handle('modules:listAvailable', () => getAvailableModulePackages())
|
||||
ipcMain.handle('modules:pickAndInstallPackage', async () => {
|
||||
const picked = await dialog.showOpenDialog({ properties: ['openDirectory'] })
|
||||
if (picked.canceled || !picked.filePaths[0]) return null
|
||||
|
||||
@@ -3,8 +3,8 @@ import { join } from 'path'
|
||||
import { tmpdir } from 'os'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('electron', () => ({ app: { getPath: () => '/unused' } }))
|
||||
import { getModuleRegistry, installModulePackage, uninstallModulePackage, validateModuleManifest } from './moduleRegistry'
|
||||
vi.mock('electron', () => ({ app: { getPath: () => '/unused', getAppPath: () => '/unused/app' } }))
|
||||
import { getAvailableModulePackages, getModuleRegistry, installModulePackage, uninstallModulePackage, validateModuleManifest } from './moduleRegistry'
|
||||
|
||||
const roots: string[] = []
|
||||
async function temp(prefix: string): Promise<string> { const root = await mkdtemp(join(tmpdir(), prefix)); roots.push(root); return root }
|
||||
@@ -15,6 +15,12 @@ afterEach(async () => { const { rm } = await import('fs/promises'); await Promis
|
||||
|
||||
describe('external module registry', () => {
|
||||
it('starts empty', async () => expect(await getModuleRegistry(await temp('aurora-user-'))).toEqual([]))
|
||||
it('lists discoverable packages for the module library', async () => {
|
||||
const catalog = await temp('aurora-catalog-'); const source = join(catalog, 'sample-app'); await mkdir(source); await writeFile(join(source, 'manifest.json'), JSON.stringify(manifest))
|
||||
process.env.AURORA_MODULE_PATH = catalog
|
||||
expect((await getAvailableModulePackages()).map((item) => item.manifest.id)).toContain('sample-app')
|
||||
delete process.env.AURORA_MODULE_PATH
|
||||
})
|
||||
it('installs a valid local package and refreshes after install', async () => {
|
||||
const userData = await temp('aurora-user-'); const source = await packageDir()
|
||||
await installModulePackage(source, userData)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { app } from 'electron'
|
||||
import { cp, lstat, mkdir, readFile, readdir, realpath, rename, rm, stat } from 'fs/promises'
|
||||
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'path'
|
||||
import type { AuroraModuleInstallResult, AuroraModuleManifest, AuroraModuleSetting } from '../shared/types'
|
||||
import type { AuroraAvailableModule, AuroraModuleInstallResult, AuroraModuleManifest, AuroraModuleSetting } from '../shared/types'
|
||||
|
||||
export const CORE_VERSION = '2.0.0-alpha.24'
|
||||
export const MODULE_API_VERSION = '1.0.0'
|
||||
@@ -78,6 +78,29 @@ export async function getModuleManifest(id: string): Promise<AuroraModuleManifes
|
||||
}
|
||||
export function invalidateModuleRegistry(): void { cache = null }
|
||||
|
||||
function catalogDirectories(): string[] {
|
||||
const configured = (process.env.AURORA_MODULE_PATH ?? '').split(process.platform === 'win32' ? ';' : ':').filter(Boolean)
|
||||
const besideImage = process.env.APPIMAGE ? [join(dirname(process.env.APPIMAGE), 'modules')] : []
|
||||
return [...configured, ...besideImage, join(process.cwd(), 'modules'), join(process.cwd(), 'packages'), join(app.getAppPath(), 'packages')]
|
||||
}
|
||||
|
||||
export async function getAvailableModulePackages(): Promise<AuroraAvailableModule[]> {
|
||||
const found = new Map<string, AuroraAvailableModule>()
|
||||
for (const directory of catalogDirectories()) {
|
||||
try {
|
||||
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) continue
|
||||
const sourcePath = join(directory, entry.name)
|
||||
try {
|
||||
const manifest = validateModuleManifest(JSON.parse(await readFile(join(sourcePath, 'manifest.json'), 'utf8')))
|
||||
if (!found.has(manifest.id)) found.set(manifest.id, { manifest, sourcePath })
|
||||
} catch { /* unrelated or incompatible package */ }
|
||||
}
|
||||
} catch { /* optional catalog directory */ }
|
||||
}
|
||||
return [...found.values()].sort((a, b) => a.manifest.name.localeCompare(b.manifest.name))
|
||||
}
|
||||
|
||||
export async function installModulePackage(source: string, userData?: string): Promise<AuroraModuleInstallResult> {
|
||||
if (!isAbsolute(source)) throw new Error('Module package path must be absolute')
|
||||
if (!(await stat(source)).isDirectory()) throw new Error('Select an unpacked local module directory')
|
||||
|
||||
Reference in New Issue
Block a user