version up!
This commit is contained in:
+274
-6
@@ -121,6 +121,7 @@ type CachedModuleFiles = {
|
||||
}
|
||||
|
||||
type CachedModuleInfo = {
|
||||
id?: string
|
||||
state: 'running' | 'stopped' | 'uninstalled'
|
||||
sortOrder: number
|
||||
category: string
|
||||
@@ -131,6 +132,9 @@ type CachedModuleInfo = {
|
||||
templateRepositoryUrl?: string | null
|
||||
sourceType?: string | null
|
||||
prebuiltArtifacts?: ModulePrebuiltArtifact[] | null
|
||||
dockerImage?: string | null
|
||||
packageName?: string | null
|
||||
launchFile?: string | null
|
||||
parameters: string[]
|
||||
cpu: number | null
|
||||
memory: number | null
|
||||
@@ -138,6 +142,7 @@ type CachedModuleInfo = {
|
||||
}
|
||||
|
||||
type CourseModuleInput = {
|
||||
id?: string
|
||||
sortOrder?: number | null
|
||||
category: string
|
||||
name: string
|
||||
@@ -145,6 +150,18 @@ type CourseModuleInput = {
|
||||
templateRepositoryUrl?: string | null
|
||||
sourceType?: string | null
|
||||
prebuiltArtifacts?: ModulePrebuiltArtifact[] | null
|
||||
dockerImage?: string | null
|
||||
packageName?: string | null
|
||||
launchFile?: string | null
|
||||
}
|
||||
|
||||
type CustomModuleScaffoldInput = {
|
||||
name: string
|
||||
category: string
|
||||
folderName: string
|
||||
dockerImage?: string | null
|
||||
packageName: string
|
||||
launchFile: string
|
||||
}
|
||||
|
||||
type ModulePrebuiltArtifact = {
|
||||
@@ -364,6 +381,173 @@ function isGazeboNav2ModuleFolder(folderName?: string | null): boolean {
|
||||
return /^\[lms-nav2-gazebo-[^\]]+\].+_v[^/]+_ws$/.test(folderName?.trim() ?? '')
|
||||
}
|
||||
|
||||
function isCustomNav2ModuleFolder(folderName?: string | null): boolean {
|
||||
return /^\[lms-nav2-custom\][a-z][a-z0-9_]*_v[^/]+_ws$/.test(folderName?.trim() ?? '')
|
||||
}
|
||||
|
||||
function validateCustomModuleScaffold(input: CustomModuleScaffoldInput): CustomModuleScaffoldInput {
|
||||
const name = input.name?.trim()
|
||||
const category = input.category?.trim()
|
||||
const folderName = input.folderName?.trim()
|
||||
const packageName = input.packageName?.trim()
|
||||
const launchFile = input.launchFile?.trim()
|
||||
const dockerImage = input.dockerImage?.trim() || null
|
||||
if (!name || !category || !folderName || !packageName || !launchFile) throw new Error('커스텀 모듈 정보가 올바르지 않습니다.')
|
||||
if (!isCustomNav2ModuleFolder(folderName) || !/^[a-z][a-z0-9_]*$/.test(packageName) || !/^[a-zA-Z0-9_.-]+\.launch\.py$/.test(launchFile)) {
|
||||
throw new Error('커스텀 모듈의 패키지명 또는 Launch 파일명이 올바르지 않습니다.')
|
||||
}
|
||||
if (dockerImage && (!/^[a-zA-Z0-9_./:@-]+$/.test(dockerImage) || dockerImage.length > 180)) {
|
||||
throw new Error('실행 이미지명이 올바르지 않습니다.')
|
||||
}
|
||||
return { name, category, folderName, packageName, launchFile, dockerImage }
|
||||
}
|
||||
|
||||
function buildCustomModuleFiles(input: CustomModuleScaffoldInput): Record<string, string> {
|
||||
const module = validateCustomModuleScaffold(input)
|
||||
const executable = `${module.packageName}_node`
|
||||
const nodePath = `${module.packageName}/node.py`
|
||||
return {
|
||||
'.autonomy-studio-module.json': `${JSON.stringify({
|
||||
sourceType: 'CUSTOM',
|
||||
name: module.name,
|
||||
category: module.category,
|
||||
dockerImage: module.dockerImage,
|
||||
packageName: module.packageName,
|
||||
launchFile: module.launchFile,
|
||||
}, null, 2)}\n`,
|
||||
'.gitignore': 'build/\ninstall/\nlog/\n__pycache__/\n*.py[cod]\n',
|
||||
'package.xml': [
|
||||
'<?xml version="1.0"?>',
|
||||
'<package format="3">',
|
||||
` <name>${module.packageName}</name>`,
|
||||
' <version>0.0.1</version>',
|
||||
` <description>${module.name}</description>`,
|
||||
' <maintainer email="student@autonomy-studio.local">Autonomy Studio Student</maintainer>',
|
||||
' <license>Apache-2.0</license>',
|
||||
' <buildtool_depend>ament_python</buildtool_depend>',
|
||||
' <exec_depend>rclpy</exec_depend>',
|
||||
' <exec_depend>launch</exec_depend>',
|
||||
' <exec_depend>launch_ros</exec_depend>',
|
||||
' <export><build_type>ament_python</build_type></export>',
|
||||
'</package>',
|
||||
'',
|
||||
].join('\n'),
|
||||
'setup.cfg': `[develop]\nscript_dir=$base/lib/${module.packageName}\n[install]\ninstall_scripts=$base/lib/${module.packageName}\n`,
|
||||
'setup.py': [
|
||||
'from setuptools import setup',
|
||||
'',
|
||||
`package_name = '${module.packageName}'`,
|
||||
'',
|
||||
'setup(',
|
||||
' name=package_name,',
|
||||
" version='0.0.1',",
|
||||
` packages=['${module.packageName}'],`,
|
||||
` data_files=[('share/ament_index/resource_index/packages', ['resource/${module.packageName}']), ('share/${module.packageName}/launch', ['launch/${module.launchFile}']), ('share/${module.packageName}', ['package.xml'])],`,
|
||||
" install_requires=['setuptools'],",
|
||||
' zip_safe=True,',
|
||||
" maintainer='Autonomy Studio Student',",
|
||||
" maintainer_email='student@autonomy-studio.local',",
|
||||
` description='${module.name}',`,
|
||||
" license='Apache-2.0',",
|
||||
` entry_points={{'console_scripts': ['${executable} = ${module.packageName}.node:main']}},`,
|
||||
')',
|
||||
'',
|
||||
].join('\n'),
|
||||
[`resource/${module.packageName}`]: '',
|
||||
[`${module.packageName}/__init__.py`]: '',
|
||||
[nodePath]: [
|
||||
'import rclpy',
|
||||
'from rclpy.node import Node',
|
||||
'',
|
||||
'class CustomModuleNode(Node):',
|
||||
' def __init__(self):',
|
||||
` super().__init__('${module.packageName}')`,
|
||||
` self.get_logger().info('${module.name} started')`,
|
||||
'',
|
||||
'def main(args=None):',
|
||||
' rclpy.init(args=args)',
|
||||
' node = CustomModuleNode()',
|
||||
' try:',
|
||||
' rclpy.spin(node)',
|
||||
' finally:',
|
||||
' node.destroy_node()',
|
||||
' rclpy.shutdown()',
|
||||
'',
|
||||
"if __name__ == '__main__':",
|
||||
' main()',
|
||||
'',
|
||||
].join('\n'),
|
||||
[`launch/${module.launchFile}`]: [
|
||||
'from launch import LaunchDescription',
|
||||
'from launch_ros.actions import Node',
|
||||
'',
|
||||
'def generate_launch_description():',
|
||||
' return LaunchDescription([',
|
||||
` Node(package='${module.packageName}', executable='${executable}', name='${module.packageName}', output='screen'),`,
|
||||
' ])',
|
||||
'',
|
||||
].join('\n'),
|
||||
}
|
||||
}
|
||||
|
||||
async function createCustomWorkspaceModule(
|
||||
workspaceRoot: string | null | undefined,
|
||||
input: CustomModuleScaffoldInput,
|
||||
): Promise<{ ok: boolean; workspacePath: string }> {
|
||||
const module = validateCustomModuleScaffold(input)
|
||||
const resolvedWorkspaceRoot = expandUserPath(workspaceRoot)
|
||||
if (!resolvedWorkspaceRoot) throw new Error('워크스페이스 경로가 설정되어 있지 않습니다.')
|
||||
const moduleRoot = resolve(resolvedWorkspaceRoot, module.folderName)
|
||||
const files = buildCustomModuleFiles(module)
|
||||
await mkdir(moduleRoot, { recursive: true })
|
||||
for (const [relativePath, content] of Object.entries(files)) {
|
||||
const targetPath = resolve(moduleRoot, relativePath)
|
||||
if (targetPath !== moduleRoot && !targetPath.startsWith(`${moduleRoot}/`)) throw new Error('허용되지 않은 모듈 파일 경로입니다.')
|
||||
await mkdir(dirname(targetPath), { recursive: true })
|
||||
await writeFile(targetPath, content, { encoding: 'utf8', flag: 'wx' }).catch((error: NodeJS.ErrnoException) => {
|
||||
if (error.code !== 'EEXIST') throw error
|
||||
})
|
||||
}
|
||||
await runCommand('git', ['init', '--initial-branch=main'], moduleRoot, undefined, 15_000).catch(() => undefined)
|
||||
return { ok: true, workspacePath: moduleRoot }
|
||||
}
|
||||
|
||||
async function createRemoteCustomWorkspaceModule(
|
||||
targetInput: RemoteWorkspaceTarget,
|
||||
input: CustomModuleScaffoldInput,
|
||||
): Promise<{ ok: boolean; workspacePath: string }> {
|
||||
const target = normalizeRemoteTarget(targetInput)
|
||||
const module = validateCustomModuleScaffold(input)
|
||||
const files = buildCustomModuleFiles(module)
|
||||
const payload = encodeBase64(JSON.stringify({ folderName: module.folderName, files }))
|
||||
const output = await runSshOutput(target, [
|
||||
'set -e',
|
||||
`ROOT=${quoteShellSingle(target.workspaceRoot)}`,
|
||||
...expandRemotePathVar('ROOT'),
|
||||
`PAYLOAD=$(printf %s ${quoteShellSingle(payload)} | base64 -d)`,
|
||||
'python3 - "$ROOT" "$PAYLOAD" <<\'PY\'',
|
||||
'import json, os, sys',
|
||||
'root = os.path.abspath(os.path.expanduser(sys.argv[1]))',
|
||||
'payload = json.loads(sys.argv[2])',
|
||||
'folder = payload["folderName"]',
|
||||
'target = os.path.abspath(os.path.join(root, folder))',
|
||||
'if target == root or not target.startswith(root + os.sep): raise SystemExit("invalid module path")',
|
||||
'os.makedirs(target, exist_ok=True)',
|
||||
'for relative, content in payload["files"].items():',
|
||||
' path = os.path.abspath(os.path.join(target, relative))',
|
||||
' if not path.startswith(target + os.sep): raise SystemExit("invalid module file path")',
|
||||
' os.makedirs(os.path.dirname(path), exist_ok=True)',
|
||||
' try:',
|
||||
' with open(path, "x", encoding="utf-8") as fp: fp.write(content)',
|
||||
' except FileExistsError:',
|
||||
' pass',
|
||||
'print(target)',
|
||||
'PY',
|
||||
`git -C "$ROOT/${module.folderName}" rev-parse --is-inside-work-tree >/dev/null 2>&1 || git -C "$ROOT/${module.folderName}" init -b main >/dev/null`,
|
||||
].join('\n'), 30_000)
|
||||
return { ok: true, workspacePath: output.trim().split('\n')[0] || remotePathJoin(target.workspaceRoot, module.folderName) }
|
||||
}
|
||||
|
||||
function resolveNav2RuntimeImage(runtimeImages?: RuntimeImageArtifact[] | null, folderName?: string | null): string {
|
||||
const gazeboModule = isGazeboNav2ModuleFolder(folderName)
|
||||
const gazeboImage = runtimeImages
|
||||
@@ -384,6 +568,16 @@ function resolveNav2RuntimeImage(runtimeImages?: RuntimeImageArtifact[] | null,
|
||||
return nav2Image || nav2DockerImage
|
||||
}
|
||||
|
||||
async function resolveCustomModuleRuntimeImage(moduleRoot: string, fallbackImage: string): Promise<string> {
|
||||
try {
|
||||
const config = JSON.parse(await readFile(resolve(moduleRoot, '.autonomy-studio-module.json'), 'utf8')) as { dockerImage?: unknown }
|
||||
const image = typeof config.dockerImage === 'string' ? config.dockerImage.trim() : ''
|
||||
return image && /^[a-zA-Z0-9_./:@-]+$/.test(image) ? image : fallbackImage
|
||||
} catch {
|
||||
return fallbackImage
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveRemoteNav2RuntimeImage(
|
||||
runtimeImages?: RuntimeImageArtifact[] | null,
|
||||
): Promise<string> {
|
||||
@@ -398,6 +592,25 @@ async function resolveRemoteNav2RuntimeImage(
|
||||
throw new Error('원격 플랫폼 NAV2 런타임 이미지가 설정되지 않았습니다.')
|
||||
}
|
||||
|
||||
async function resolveRemoteCustomModuleRuntimeImage(
|
||||
target: RemoteWorkspaceTarget,
|
||||
folderName: string,
|
||||
fallbackImage: string,
|
||||
): Promise<string> {
|
||||
const output = await runSshOutput(target, [
|
||||
`ROOT=${quoteShellSingle(target.workspaceRoot)}`,
|
||||
...expandRemotePathVar('ROOT'),
|
||||
`CONFIG="$ROOT/${folderName}/.autonomy-studio-module.json"`,
|
||||
'if [ -f "$CONFIG" ]; then python3 - "$CONFIG" <<\'PY\'',
|
||||
'import json, re, sys',
|
||||
'image = json.load(open(sys.argv[1], encoding="utf-8")).get("dockerImage")',
|
||||
'print(image.strip() if isinstance(image, str) and re.fullmatch(r"[A-Za-z0-9_./:@-]+", image.strip()) else "")',
|
||||
'PY',
|
||||
'fi',
|
||||
].join('\n'), 8_000).catch(() => '')
|
||||
return output.trim() || fallbackImage
|
||||
}
|
||||
|
||||
function isAllowedContainerName(containerKey: string): boolean {
|
||||
return /^[A-Za-z0-9_.-]+-[A-Za-z0-9_.-]+_v[A-Za-z0-9_.-]+$/.test(containerKey)
|
||||
}
|
||||
@@ -492,6 +705,7 @@ async function collectAvailableModules(
|
||||
const rosParams = installed && workspacePath ? await readModuleRosParams(workspacePath) : ''
|
||||
|
||||
return {
|
||||
id: module.id,
|
||||
state: installed ? (dockerStatus?.running ? 'running' : 'stopped') : 'uninstalled',
|
||||
sortOrder: module.sortOrder,
|
||||
category,
|
||||
@@ -500,8 +714,11 @@ async function collectAvailableModules(
|
||||
sourcePath,
|
||||
workspacePath,
|
||||
templateRepositoryUrl: module.templateRepositoryUrl ?? null,
|
||||
sourceType: module.sourceType ?? null,
|
||||
prebuiltArtifacts: module.prebuiltArtifacts ?? null,
|
||||
sourceType: module.sourceType ?? null,
|
||||
prebuiltArtifacts: module.prebuiltArtifacts ?? null,
|
||||
dockerImage: module.dockerImage ?? null,
|
||||
packageName: module.packageName ?? null,
|
||||
launchFile: module.launchFile ?? null,
|
||||
parameters: splitRosParams(rosParams),
|
||||
cpu: dockerStatus?.running ? dockerStat?.cpu ?? null : null,
|
||||
memory: dockerStatus?.running ? dockerStat?.memory ?? null : null,
|
||||
@@ -567,6 +784,7 @@ async function collectRemoteAvailableModules(
|
||||
const dockerStat = dockerInfo ? dockerStats.get(dockerInfo.containerKey) : null
|
||||
|
||||
return {
|
||||
id: module.id,
|
||||
state: installed ? (dockerStatus?.running ? 'running' : 'stopped') : 'uninstalled',
|
||||
sortOrder: module.sortOrder,
|
||||
category: module.category || parsed.category,
|
||||
@@ -575,6 +793,11 @@ async function collectRemoteAvailableModules(
|
||||
sourcePath: module.templateRepositoryUrl ?? '',
|
||||
workspacePath: installed?.path ?? null,
|
||||
templateRepositoryUrl: module.templateRepositoryUrl ?? null,
|
||||
sourceType: module.sourceType ?? null,
|
||||
prebuiltArtifacts: module.prebuiltArtifacts ?? null,
|
||||
dockerImage: module.dockerImage ?? null,
|
||||
packageName: module.packageName ?? null,
|
||||
launchFile: module.launchFile ?? null,
|
||||
parameters: installed?.parameters ?? [],
|
||||
cpu: dockerStatus?.running ? dockerStat?.cpu ?? null : null,
|
||||
memory: dockerStatus?.running ? dockerStat?.memory ?? null : null,
|
||||
@@ -2667,6 +2890,16 @@ function buildNav2DockerRunScript(): string {
|
||||
'done',
|
||||
'set -- "${filtered_args[@]}"',
|
||||
'',
|
||||
'if [[ "$MODULE_FOLDER" == *lms-nav2-custom* ]]; then',
|
||||
' MODULE_CONFIG="$MODULE_ROOT/$MODULE_FOLDER/.autonomy-studio-module.json"',
|
||||
' test -f "$MODULE_CONFIG" || { echo "Custom module configuration is missing: $MODULE_FOLDER" >&2; exit 64; }',
|
||||
' read -r PACKAGE LAUNCH < <(python3 - "$MODULE_CONFIG" <<\'PY\'',
|
||||
'import json, sys',
|
||||
'config = json.load(open(sys.argv[1], encoding="utf-8"))',
|
||||
'print(config["packageName"], config["launchFile"])',
|
||||
'PY',
|
||||
' )',
|
||||
'else',
|
||||
'case "$MODULE_FOLDER" in',
|
||||
' *lms-nav2-mkmini*) PACKAGE=vehicle_pkg; LAUNCH=vehicle.launch.py ;;',
|
||||
' *lms-nav2-gazebo-simulator*) PACKAGE=nav2_turtlebot_simulator; LAUNCH=simulator.launch.py ;;',
|
||||
@@ -2689,6 +2922,7 @@ function buildNav2DockerRunScript(): string {
|
||||
' *nav2-system*) PACKAGE=nav2_turtlebot_system; LAUNCH=system.launch.py ;;',
|
||||
' *) echo "Unknown Nav2 module: $MODULE_FOLDER" >&2; exit 64 ;;',
|
||||
'esac',
|
||||
'fi',
|
||||
'',
|
||||
'if [[ "$MODULE_FOLDER" == *lms-nav2-gazebo-slam3d* ]]; then',
|
||||
' mkdir -p "$MODULE_ROOT/maps"',
|
||||
@@ -2742,6 +2976,15 @@ function buildNav2DockerBuildScript(): string {
|
||||
'IMAGE=$3',
|
||||
'ROS_DOMAIN_ID_VALUE=${4:-0}',
|
||||
'',
|
||||
'if [[ "$MODULE_FOLDER" == *lms-nav2-custom* ]]; then',
|
||||
' MODULE_CONFIG="$MODULE_ROOT/$MODULE_FOLDER/.autonomy-studio-module.json"',
|
||||
' test -f "$MODULE_CONFIG" || { echo "Custom module configuration is missing: $MODULE_FOLDER" >&2; exit 64; }',
|
||||
' TARGET=$(python3 - "$MODULE_CONFIG" <<\'PY\'',
|
||||
'import json, sys',
|
||||
'print(json.load(open(sys.argv[1], encoding="utf-8"))["packageName"])',
|
||||
'PY',
|
||||
' )',
|
||||
'else',
|
||||
'case "$MODULE_FOLDER" in',
|
||||
' *lms-nav2-mkmini*) TARGET=vehicle_pkg ;;',
|
||||
' *lms-nav2-gazebo-simulator*) TARGET=nav2_turtlebot_simulator ;;',
|
||||
@@ -2764,6 +3007,7 @@ function buildNav2DockerBuildScript(): string {
|
||||
' *nav2-system*) TARGET=nav2_turtlebot_system ;;',
|
||||
' *) echo "Unknown Nav2 module: $MODULE_FOLDER" >&2; exit 64 ;;',
|
||||
'esac',
|
||||
'fi',
|
||||
'',
|
||||
'MODULE_PATH="$MODULE_ROOT/$MODULE_FOLDER"',
|
||||
'test -d "$MODULE_PATH/src" || { echo "Module source is missing: $MODULE_FOLDER" >&2; exit 1; }',
|
||||
@@ -3075,6 +3319,10 @@ async function startWorkspaceModule(
|
||||
await ensureNav2RuntimeAssets()
|
||||
const rosParams = typeof rosParamsOverride === 'string' ? rosParamsOverride.trim() : await readModuleRosParams(moduleRoot)
|
||||
const scriptPath = resolveNav2DockerRunScript()
|
||||
const runtimeImage = await resolveCustomModuleRuntimeImage(
|
||||
moduleRoot,
|
||||
resolveNav2RuntimeImage(runtimeImages, targetFolderName),
|
||||
)
|
||||
await runCommand(
|
||||
'bash',
|
||||
[
|
||||
@@ -3082,7 +3330,7 @@ async function startWorkspaceModule(
|
||||
dockerInfo.containerKey,
|
||||
resolvedWorkspaceRoot,
|
||||
targetFolderName,
|
||||
resolveNav2RuntimeImage(runtimeImages, targetFolderName),
|
||||
runtimeImage,
|
||||
String(rosDomainId ?? process.env.ROS_DOMAIN_ID ?? '0').trim() || '0',
|
||||
...splitRosParams(rosParams)
|
||||
],
|
||||
@@ -3149,13 +3397,17 @@ async function buildWorkspaceModule(
|
||||
await prepareRuntimeImages(runtimeImages)
|
||||
if (isNav2ModuleFolder(targetFolderName)) {
|
||||
await ensureNav2RuntimeAssets()
|
||||
const runtimeImage = await resolveCustomModuleRuntimeImage(
|
||||
moduleRoot,
|
||||
resolveNav2RuntimeImage(runtimeImages, targetFolderName),
|
||||
)
|
||||
await runCommand(
|
||||
'bash',
|
||||
[
|
||||
resolveNav2DockerBuildScript(),
|
||||
resolvedWorkspaceRoot,
|
||||
targetFolderName,
|
||||
resolveNav2RuntimeImage(runtimeImages, targetFolderName),
|
||||
runtimeImage,
|
||||
String(rosDomainId ?? process.env.ROS_DOMAIN_ID ?? '0').trim() || '0'
|
||||
],
|
||||
undefined,
|
||||
@@ -3220,7 +3472,11 @@ async function startRemoteWorkspaceModule(
|
||||
|
||||
const nav2Module = isNav2ModuleFolder(targetFolderName)
|
||||
const runtimeImage = nav2Module
|
||||
? await resolveRemoteNav2RuntimeImage(runtimeImages)
|
||||
? await resolveRemoteCustomModuleRuntimeImage(
|
||||
target,
|
||||
targetFolderName,
|
||||
await resolveRemoteNav2RuntimeImage(runtimeImages),
|
||||
)
|
||||
: null
|
||||
const runtimeRoot = nav2Module
|
||||
? await ensureRemoteNav2RuntimeAssets(target)
|
||||
@@ -3343,7 +3599,11 @@ async function buildRemoteWorkspaceModule(
|
||||
|
||||
if (isNav2ModuleFolder(targetFolderName)) {
|
||||
const runtimeRoot = await ensureRemoteNav2RuntimeAssets(target)
|
||||
const runtimeImage = await resolveRemoteNav2RuntimeImage(runtimeImages)
|
||||
const runtimeImage = await resolveRemoteCustomModuleRuntimeImage(
|
||||
target,
|
||||
targetFolderName,
|
||||
await resolveRemoteNav2RuntimeImage(runtimeImages),
|
||||
)
|
||||
await runSsh(target, [
|
||||
'set -e',
|
||||
`RUNTIME_ROOT=${quoteShellSingle(runtimeRoot)}`,
|
||||
@@ -4231,6 +4491,10 @@ function registerLocalWorkspaceHandlers(): void {
|
||||
})
|
||||
ipcMain.handle('local-workspace:modules:list', (_event, workspaceRoot?: string | null, courseModules?: CourseModuleInput[] | null) => collectAvailableModules(workspaceRoot, courseModules))
|
||||
ipcMain.handle('local-workspace:modules:install', (_event, moduleName: string, workspaceRoot?: string | null, folderName?: string | null, templateRepositoryUrl?: string | null, studentRepositoryUrl?: string | null, options?: ModuleInstallOptions | null) => installModuleFromCache(moduleName, workspaceRoot, folderName, templateRepositoryUrl, studentRepositoryUrl, options))
|
||||
ipcMain.handle('local-workspace:modules:create-custom', (_event, workspaceRoot?: string | null, input?: CustomModuleScaffoldInput | null) => {
|
||||
if (!input) throw new Error('커스텀 모듈 정보가 없습니다.')
|
||||
return createCustomWorkspaceModule(workspaceRoot, input)
|
||||
})
|
||||
ipcMain.handle('local-workspace:modules:delete', (_event, moduleName: string, workspaceRoot?: string | null, folderName?: string | null) => deleteWorkspaceModule(moduleName, workspaceRoot, folderName))
|
||||
ipcMain.handle('local-workspace:modules:start', (_event, moduleName: string, workspaceRoot?: string | null, folderName?: string | null, rosParams?: string | null, gpuEnabled?: boolean | null, rosDomainId?: string | null, runtimeImages?: RuntimeImageArtifact[] | null) => startWorkspaceModule(moduleName, workspaceRoot, folderName, rosParams, gpuEnabled, rosDomainId, runtimeImages))
|
||||
ipcMain.handle('local-workspace:modules:build', (_event, moduleName: string, workspaceRoot?: string | null, folderName?: string | null, gpuEnabled?: boolean | null, rosDomainId?: string | null, runtimeImages?: RuntimeImageArtifact[] | null) => buildWorkspaceModule(moduleName, workspaceRoot, folderName, gpuEnabled, rosDomainId, runtimeImages))
|
||||
@@ -4262,6 +4526,10 @@ function registerLocalWorkspaceHandlers(): void {
|
||||
})
|
||||
ipcMain.handle('remote-workspace:modules:list', (_event, target: RemoteWorkspaceTarget, modules?: CourseModuleInput[] | null) => collectRemoteAvailableModules(target, modules))
|
||||
ipcMain.handle('remote-workspace:modules:install', (_event, target: RemoteWorkspaceTarget, moduleName: string, folderName?: string | null, templateRepositoryUrl?: string | null, studentRepositoryUrl?: string | null, options?: ModuleInstallOptions | null) => installRemoteWorkspaceModule(target, moduleName, folderName, templateRepositoryUrl, studentRepositoryUrl, options))
|
||||
ipcMain.handle('remote-workspace:modules:create-custom', (_event, target: RemoteWorkspaceTarget, input?: CustomModuleScaffoldInput | null) => {
|
||||
if (!input) throw new Error('커스텀 모듈 정보가 없습니다.')
|
||||
return createRemoteCustomWorkspaceModule(target, input)
|
||||
})
|
||||
ipcMain.handle('remote-workspace:modules:start', (_event, target: RemoteWorkspaceTarget, moduleName: string, folderName?: string | null, rosParams?: string | null, gpuEnabled?: boolean | null, rosDomainId?: string | null, runtimeImages?: RuntimeImageArtifact[] | null) => startRemoteWorkspaceModule(target, moduleName, folderName, rosParams, gpuEnabled, rosDomainId, runtimeImages))
|
||||
ipcMain.handle('remote-workspace:modules:build', (_event, target: RemoteWorkspaceTarget, moduleName: string, folderName?: string | null, gpuEnabled?: boolean | null, rosDomainId?: string | null, runtimeImages?: RuntimeImageArtifact[] | null) => buildRemoteWorkspaceModule(target, moduleName, folderName, gpuEnabled, rosDomainId, runtimeImages))
|
||||
ipcMain.handle('remote-workspace:modules:stop', (_event, target: RemoteWorkspaceTarget, moduleName: string, folderName?: string | null) => stopRemoteWorkspaceModule(target, moduleName, folderName))
|
||||
|
||||
Reference in New Issue
Block a user