diff --git a/src/lib/oclif-commands.ts b/src/lib/oclif-commands.ts index 4e5078e3b6f..51821c4e69a 100644 --- a/src/lib/oclif-commands.ts +++ b/src/lib/oclif-commands.ts @@ -50,7 +50,7 @@ import ShareCommand, { ShareMountCommand, ShareStatusCommand, ShareUnmountCommand, -} from "./share-command"; +} from "./share-cli-commands"; import SkillInstallCliCommand, { SkillCliCommand } from "./skill-install-cli-command"; import { SnapshotCommand, diff --git a/src/lib/share-cli-commands.test.ts b/src/lib/share-cli-commands.test.ts new file mode 100644 index 00000000000..1ef4eba36c6 --- /dev/null +++ b/src/lib/share-cli-commands.test.ts @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + printShareUsageAndExit: vi.fn(() => { + throw new Error("share usage requested"); + }), + runShareMount: vi.fn().mockResolvedValue(undefined), + runShareStatus: vi.fn(), + runShareUnmount: vi.fn(), +})); + +vi.mock("./share-command", () => ({ + printShareUsageAndExit: mocks.printShareUsageAndExit, + runShareMount: mocks.runShareMount, + runShareStatus: mocks.runShareStatus, + runShareUnmount: mocks.runShareUnmount, +})); + +import ShareCommand, { + ShareMountCommand, + ShareStatusCommand, + ShareUnmountCommand, +} from "./share-cli-commands"; + +const rootDir = process.cwd(); + +describe("share oclif command adapters", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("routes parent share usage through the usage action", async () => { + await expect(ShareCommand.run(["alpha"], rootDir)).rejects.toThrow("share usage requested"); + + expect(mocks.printShareUsageAndExit).toHaveBeenCalledWith(1); + }); + + it("maps share subcommand args to share actions", async () => { + await ShareMountCommand.run(["alpha", "/workspace", "/tmp/alpha"], rootDir); + await ShareUnmountCommand.run(["alpha", "/tmp/alpha"], rootDir); + await ShareStatusCommand.run(["alpha", "/tmp/alpha"], rootDir); + + expect(mocks.runShareMount).toHaveBeenCalledWith({ + sandboxName: "alpha", + remotePath: "/workspace", + localMount: "/tmp/alpha", + }); + expect(mocks.runShareUnmount).toHaveBeenCalledWith({ + sandboxName: "alpha", + localMount: "/tmp/alpha", + }); + expect(mocks.runShareStatus).toHaveBeenCalledWith({ + sandboxName: "alpha", + localMount: "/tmp/alpha", + }); + }); +}); diff --git a/src/lib/share-cli-commands.ts b/src/lib/share-cli-commands.ts new file mode 100644 index 00000000000..881a6956e9a --- /dev/null +++ b/src/lib/share-cli-commands.ts @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Args, Command, Flags } from "@oclif/core"; + +import { + printShareUsageAndExit, + runShareMount, + runShareStatus, + runShareUnmount, +} from "./share-command"; + +const sandboxNameArg = Args.string({ + name: "sandbox", + description: "Sandbox name", + required: true, +}); + +export default class ShareCommand extends Command { + static id = "sandbox:share"; + static strict = true; + static summary = "Mount/unmount sandbox filesystem on the host via SSHFS"; + static description = "Share files between host and sandbox using SSHFS over OpenShell's SSH proxy."; + static usage = [" share "]; + static examples = [ + "<%= config.bin %> alpha share mount", + "<%= config.bin %> alpha share unmount", + "<%= config.bin %> alpha share status", + ]; + static args = { + sandboxName: sandboxNameArg, + }; + static flags = { + help: Flags.help({ char: "h" }), + }; + + public async run(): Promise { + await this.parse(ShareCommand); + printShareUsageAndExit(1); + } +} + +export class ShareMountCommand extends Command { + static id = "sandbox:share:mount"; + static strict = true; + static summary = "Mount sandbox filesystem on the host"; + static description = "Mount a sandbox path on the host using SSHFS over OpenShell's SSH proxy."; + static usage = [" share mount [sandbox-path] [local-mount-point]"]; + static examples = [ + "<%= config.bin %> alpha share mount", + "<%= config.bin %> alpha share mount /workspace ~/mnt/alpha", + ]; + static args = { + sandboxName: sandboxNameArg, + sandboxPath: Args.string({ + name: "sandbox-path", + description: "Path inside the sandbox to mount", + required: false, + }), + localMountPoint: Args.string({ + name: "local-mount-point", + description: "Host path for the SSHFS mount", + required: false, + }), + }; + static flags = { + help: Flags.help({ char: "h" }), + }; + + public async run(): Promise { + const { args } = await this.parse(ShareMountCommand); + await runShareMount({ + sandboxName: args.sandboxName, + remotePath: args.sandboxPath, + localMount: args.localMountPoint, + }); + } +} + +export class ShareUnmountCommand extends Command { + static id = "sandbox:share:unmount"; + static strict = true; + static summary = "Unmount a shared sandbox filesystem"; + static description = "Unmount a previously mounted sandbox filesystem from the host."; + static usage = [" share unmount [local-mount-point]"]; + static examples = [ + "<%= config.bin %> alpha share unmount", + "<%= config.bin %> alpha share unmount ~/mnt/alpha", + ]; + static args = { + sandboxName: sandboxNameArg, + localMountPoint: Args.string({ + name: "local-mount-point", + description: "Host mount path to unmount", + required: false, + }), + }; + static flags = { + help: Flags.help({ char: "h" }), + }; + + public async run(): Promise { + const { args } = await this.parse(ShareUnmountCommand); + runShareUnmount({ sandboxName: args.sandboxName, localMount: args.localMountPoint }); + } +} + +export class ShareStatusCommand extends Command { + static id = "sandbox:share:status"; + static strict = true; + static summary = "Show sandbox share mount status"; + static description = "Check whether a sandbox filesystem share is currently mounted on the host."; + static usage = [" share status [local-mount-point]"]; + static examples = [ + "<%= config.bin %> alpha share status", + "<%= config.bin %> alpha share status ~/mnt/alpha", + ]; + static args = { + sandboxName: sandboxNameArg, + localMountPoint: Args.string({ + name: "local-mount-point", + description: "Host mount path to check", + required: false, + }), + }; + static flags = { + help: Flags.help({ char: "h" }), + }; + + public async run(): Promise { + const { args } = await this.parse(ShareStatusCommand); + runShareStatus({ sandboxName: args.sandboxName, localMount: args.localMountPoint }); + } +} diff --git a/src/lib/share-command.ts b/src/lib/share-command.ts index e1528af45e4..38767afb2f5 100644 --- a/src/lib/share-command.ts +++ b/src/lib/share-command.ts @@ -9,7 +9,6 @@ * `openssh-sftp-server` in the sandbox image. */ -import { Args, Command, Flags } from "@oclif/core"; import { spawnSync } from "child_process"; import fs from "fs"; import os from "os"; @@ -250,126 +249,3 @@ export function printShareUsageAndExit(exitCode = 1): never { console.error(" status [local-mount-point] Check current mount status"); process.exit(exitCode); } - -const sandboxNameArg = Args.string({ - name: "sandbox", - description: "Sandbox name", - required: true, -}); - -export default class ShareCommand extends Command { - static id = "sandbox:share"; - static strict = true; - static summary = "Mount/unmount sandbox filesystem on the host via SSHFS"; - static description = "Share files between host and sandbox using SSHFS over OpenShell's SSH proxy."; - static usage = [" share "]; - static examples = [ - "<%= config.bin %> alpha share mount", - "<%= config.bin %> alpha share unmount", - "<%= config.bin %> alpha share status", - ]; - static args = { - sandboxName: sandboxNameArg, - }; - static flags = { - help: Flags.help({ char: "h" }), - }; - - public async run(): Promise { - await this.parse(ShareCommand); - printShareUsageAndExit(1); - } -} - -export class ShareMountCommand extends Command { - static id = "sandbox:share:mount"; - static strict = true; - static summary = "Mount sandbox filesystem on the host"; - static description = "Mount a sandbox path on the host using SSHFS over OpenShell's SSH proxy."; - static usage = [" share mount [sandbox-path] [local-mount-point]"]; - static examples = [ - "<%= config.bin %> alpha share mount", - "<%= config.bin %> alpha share mount /workspace ~/mnt/alpha", - ]; - static args = { - sandboxName: sandboxNameArg, - sandboxPath: Args.string({ - name: "sandbox-path", - description: "Path inside the sandbox to mount", - required: false, - }), - localMountPoint: Args.string({ - name: "local-mount-point", - description: "Host path for the SSHFS mount", - required: false, - }), - }; - static flags = { - help: Flags.help({ char: "h" }), - }; - - public async run(): Promise { - const { args } = await this.parse(ShareMountCommand); - await runShareMount({ - sandboxName: args.sandboxName, - remotePath: args.sandboxPath, - localMount: args.localMountPoint, - }); - } -} - -export class ShareUnmountCommand extends Command { - static id = "sandbox:share:unmount"; - static strict = true; - static summary = "Unmount a shared sandbox filesystem"; - static description = "Unmount a previously mounted sandbox filesystem from the host."; - static usage = [" share unmount [local-mount-point]"]; - static examples = [ - "<%= config.bin %> alpha share unmount", - "<%= config.bin %> alpha share unmount ~/mnt/alpha", - ]; - static args = { - sandboxName: sandboxNameArg, - localMountPoint: Args.string({ - name: "local-mount-point", - description: "Host mount path to unmount", - required: false, - }), - }; - static flags = { - help: Flags.help({ char: "h" }), - }; - - public async run(): Promise { - const { args } = await this.parse(ShareUnmountCommand); - runShareUnmount({ sandboxName: args.sandboxName, localMount: args.localMountPoint }); - } -} - -export class ShareStatusCommand extends Command { - static id = "sandbox:share:status"; - static strict = true; - static summary = "Show sandbox share mount status"; - static description = "Check whether a sandbox filesystem share is currently mounted on the host."; - static usage = [" share status [local-mount-point]"]; - static examples = [ - "<%= config.bin %> alpha share status", - "<%= config.bin %> alpha share status ~/mnt/alpha", - ]; - static args = { - sandboxName: sandboxNameArg, - localMountPoint: Args.string({ - name: "local-mount-point", - description: "Host mount path to check", - required: false, - }), - }; - static flags = { - help: Flags.help({ char: "h" }), - }; - - public async run(): Promise { - const { args } = await this.parse(ShareStatusCommand); - runShareStatus({ sandboxName: args.sandboxName, localMount: args.localMountPoint }); - } -}