import { readdir, readFile, realpath } from 'fs/promises' import { resolve, sep } from 'path' import type { LocalGitModule } from '../shared/gitModuleImport' export async function localGitModulePath(root: string, folder: string): Promise { if (!/^\[lms-nav2-custom\][a-z][a-z0-9_]*_v[^/\\]+_ws$/.test(folder)) throw new Error('Invalid custom module folder') const base = await realpath(root) const target = await realpath(resolve(base, folder)) if (!target.startsWith(base + sep)) throw new Error('Module is outside the local workspace') return target } export async function listLocalGitModules(root: string): Promise { const entries = await readdir(root, { withFileTypes: true }).catch((error: NodeJS.ErrnoException) => { if (error.code === 'ENOENT') return [] throw error }) const modules: LocalGitModule[] = [] for (const entry of entries) { if (!entry.isDirectory() || !entry.name.startsWith('[lms-nav2-custom]')) continue try { const target = await localGitModulePath(root, entry.name) const data = JSON.parse(await readFile(resolve(target, '.autonomy-studio-module.json'), 'utf8')) if (data.sourceType !== 'CUSTOM' || !['name', 'category', 'packageName', 'launchFile'].every(key => typeof data[key] === 'string' && data[key])) continue modules.push({ name: data.name, category: data.category, packageName: data.packageName, launchFile: data.launchFile, folderName: entry.name }) } catch { /* Ignore incomplete module folders. */ } } return modules.sort((a, b) => a.name.localeCompare(b.name)) } export function repositoryAddress(value: string): string { // Do not allow local paths, Git remote helpers, or credentials in the UI. const normalized = /^git@[^:]+:.+/.test(value) ? value.replace(/^git@([^:]+):(.+)$/, 'ssh://git@$1/$2') : value const url = new URL(normalized) if (!['https:', 'http:', 'ssh:'].includes(url.protocol)) throw new Error('Unsupported Git repository protocol') url.password = '' if (url.protocol !== 'ssh:') url.username = '' url.search = '' url.hash = '' return url.toString() } export function remoteBranches(output: string): Array<{ name: string; commit: string }> { return output.split('\n').flatMap(line => { const match = line.trim().match(/^([a-f0-9]{40,64})\s+refs\/heads\/(.+)$/) return match ? [{ name: match[2], commit: match[1] }] : [] }).sort((a, b) => a.name.localeCompare(b.name)) }