vaibify 0.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. vaibify/__init__.py +4 -0
  2. vaibify/__main__.py +5 -0
  3. vaibify/_version.py +24 -0
  4. vaibify/cli/__init__.py +1 -0
  5. vaibify/cli/commandBuild.py +133 -0
  6. vaibify/cli/commandCat.py +39 -0
  7. vaibify/cli/commandConfig.py +82 -0
  8. vaibify/cli/commandDestroy.py +72 -0
  9. vaibify/cli/commandInit.py +117 -0
  10. vaibify/cli/commandLs.py +59 -0
  11. vaibify/cli/commandPublish.py +21 -0
  12. vaibify/cli/commandRun.py +130 -0
  13. vaibify/cli/commandStart.py +69 -0
  14. vaibify/cli/commandStatus.py +79 -0
  15. vaibify/cli/commandTest.py +148 -0
  16. vaibify/cli/commandUtilsDocker.py +65 -0
  17. vaibify/cli/commandVerifyStep.py +90 -0
  18. vaibify/cli/commandWorkflow.py +122 -0
  19. vaibify/cli/configLoader.py +167 -0
  20. vaibify/cli/main.py +239 -0
  21. vaibify/config/__init__.py +51 -0
  22. vaibify/config/containerConfig.py +171 -0
  23. vaibify/config/projectConfig.py +400 -0
  24. vaibify/config/registryManager.py +288 -0
  25. vaibify/config/secretManager.py +174 -0
  26. vaibify/config/templateManager.py +105 -0
  27. vaibify/docker/__init__.py +20 -0
  28. vaibify/docker/containerManager.py +228 -0
  29. vaibify/docker/dockerConnection.py +170 -0
  30. vaibify/docker/fileTransfer.py +70 -0
  31. vaibify/docker/imageBuilder.py +232 -0
  32. vaibify/docker/volumeManager.py +70 -0
  33. vaibify/docker/x11Forwarding.py +98 -0
  34. vaibify/gui/__init__.py +0 -0
  35. vaibify/gui/commandUtilities.py +98 -0
  36. vaibify/gui/dependencyScanner.py +471 -0
  37. vaibify/gui/director.py +549 -0
  38. vaibify/gui/figureServer.py +23 -0
  39. vaibify/gui/pipelineRunner.py +1053 -0
  40. vaibify/gui/pipelineServer.py +2947 -0
  41. vaibify/gui/pipelineState.py +118 -0
  42. vaibify/gui/registryRoutes.py +468 -0
  43. vaibify/gui/resourceMonitor.py +88 -0
  44. vaibify/gui/setupServer.py +143 -0
  45. vaibify/gui/static/favicon.png +0 -0
  46. vaibify/gui/static/favicon.svg +4 -0
  47. vaibify/gui/static/index.html +544 -0
  48. vaibify/gui/static/scriptApplication.js +7219 -0
  49. vaibify/gui/static/scriptFigureViewer.js +994 -0
  50. vaibify/gui/static/scriptResourceMonitor.js +257 -0
  51. vaibify/gui/static/scriptSetupWizard.js +372 -0
  52. vaibify/gui/static/scriptStepEditor.js +286 -0
  53. vaibify/gui/static/scriptTerminal.js +570 -0
  54. vaibify/gui/static/scriptUtilities.js +45 -0
  55. vaibify/gui/static/setupWizard.html +206 -0
  56. vaibify/gui/static/styleMain.css +3031 -0
  57. vaibify/gui/static/styleSetupWizard.css +410 -0
  58. vaibify/gui/static/vaibify_logo_paleblue.png +0 -0
  59. vaibify/gui/static/vaibify_logo_paleblue_dark.png +0 -0
  60. vaibify/gui/syncDispatcher.py +693 -0
  61. vaibify/gui/terminalSession.py +85 -0
  62. vaibify/gui/testGenerator.py +3407 -0
  63. vaibify/gui/workflowManager.py +699 -0
  64. vaibify/install/__init__.py +0 -0
  65. vaibify/install/installVaibify.sh +371 -0
  66. vaibify/install/setupServer.py +300 -0
  67. vaibify/install/shellSetup.py +185 -0
  68. vaibify/install/uninstallVaibify.sh +209 -0
  69. vaibify/reproducibility/__init__.py +1 -0
  70. vaibify/reproducibility/dataArchiver.py +248 -0
  71. vaibify/reproducibility/githubWorkflow.py +155 -0
  72. vaibify/reproducibility/latexConnector.py +272 -0
  73. vaibify/reproducibility/overleafSync.py +307 -0
  74. vaibify/reproducibility/provenanceTracker.py +252 -0
  75. vaibify/reproducibility/zenodoClient.py +256 -0
  76. vaibify-0.0.0.dist-info/METADATA +171 -0
  77. vaibify-0.0.0.dist-info/RECORD +80 -0
  78. vaibify-0.0.0.dist-info/WHEEL +5 -0
  79. vaibify-0.0.0.dist-info/entry_points.txt +3 -0
  80. vaibify-0.0.0.dist-info/top_level.txt +1 -0
vaibify/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ try:
2
+ from ._version import __version__
3
+ except ImportError:
4
+ __version__ = "unknown"
vaibify/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Allow running vaibify as ``python -m vaibify``."""
2
+
3
+ from vaibify.cli.main import main
4
+
5
+ main()
vaibify/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.0.0'
22
+ __version_tuple__ = version_tuple = (0, 0, 0)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1 @@
1
+ """Vaibify CLI package."""
@@ -0,0 +1,133 @@
1
+ """CLI subcommand: vaibify build."""
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+
7
+ import click
8
+
9
+ from .configLoader import fconfigResolveProject, fsDockerDir
10
+
11
+
12
+ def fnBuildFromConfig(config, sDockerDir, bNoCache):
13
+ """Invoke the Docker image builder with the loaded configuration.
14
+
15
+ Uses lazy import so the CLI remains usable without Docker installed.
16
+ """
17
+ try:
18
+ from vaibify.docker.imageBuilder import fnBuildImage
19
+ except ImportError:
20
+ click.echo(
21
+ "Error: Docker support is not installed. "
22
+ "Install with: pip install vaibify[docker]"
23
+ )
24
+ sys.exit(1)
25
+ fnPrepareBuildContext(config, sDockerDir)
26
+ fnBuildImage(config, sDockerDir, bNoCache=bNoCache)
27
+ fnPruneDanglingImages()
28
+
29
+
30
+ def fnPruneDanglingImages():
31
+ """Remove dangling Docker images left by previous builds."""
32
+ try:
33
+ resultPrune = subprocess.run(
34
+ ["docker", "image", "prune", "-f"],
35
+ capture_output=True, text=True, timeout=30,
36
+ )
37
+ if resultPrune.returncode == 0:
38
+ listLines = resultPrune.stdout.strip().split("\n")
39
+ sLastLine = listLines[-1] if listLines else ""
40
+ if "reclaimed" in sLastLine.lower():
41
+ click.echo(f"[vaib] {sLastLine}")
42
+ except Exception:
43
+ pass
44
+
45
+
46
+ def fnPrepareBuildContext(config, sDockerDir):
47
+ """Generate all config-derived files in the Docker build context."""
48
+ from vaibify.config.containerConfig import (
49
+ fnGenerateContainerConf,
50
+ )
51
+ fnGenerateContainerConf(
52
+ config, os.path.join(sDockerDir, "container.conf")
53
+ )
54
+ fnWriteSystemPackages(config, sDockerDir)
55
+ fnWritePythonPackages(config, sDockerDir)
56
+ fnWritePipInstallFlags(config, sDockerDir)
57
+ fnWriteBinariesEnv(config, sDockerDir)
58
+ fnCopyDirectorScript(sDockerDir)
59
+
60
+
61
+ def fnWriteSystemPackages(config, sDockerDir):
62
+ """Write listSystemPackages to system-packages.txt."""
63
+ sPath = os.path.join(sDockerDir, "system-packages.txt")
64
+ sContent = "\n".join(config.listSystemPackages) + "\n"
65
+ _fnWriteFile(sPath, sContent)
66
+
67
+
68
+ def fnWritePythonPackages(config, sDockerDir):
69
+ """Write listPythonPackages to requirements.txt."""
70
+ sPath = os.path.join(sDockerDir, "requirements.txt")
71
+ sContent = "\n".join(config.listPythonPackages) + "\n"
72
+ _fnWriteFile(sPath, sContent)
73
+
74
+
75
+ def fnWritePipInstallFlags(config, sDockerDir):
76
+ """Write sPipInstallFlags to pip-flags.txt."""
77
+ sPath = os.path.join(sDockerDir, "pip-flags.txt")
78
+ _fnWriteFile(sPath, config.sPipInstallFlags.strip() + "\n")
79
+
80
+
81
+ def fnWriteBinariesEnv(config, sDockerDir):
82
+ """Write listBinaries to binaries.env as KEY=VALUE lines."""
83
+ sPath = os.path.join(sDockerDir, "binaries.env")
84
+ listLines = []
85
+ for dictBinary in config.listBinaries:
86
+ sName = dictBinary.get("name", "")
87
+ sBinPath = dictBinary.get("path", "")
88
+ if sName and sBinPath:
89
+ listLines.append(f"{sName}={sBinPath}")
90
+ sContent = "\n".join(listLines) + "\n"
91
+ _fnWriteFile(sPath, sContent)
92
+
93
+
94
+ def fnCopyDirectorScript(sDockerDir):
95
+ """Copy director.py into the Docker build context."""
96
+ import shutil
97
+ import pathlib
98
+ sSourcePath = str(
99
+ pathlib.Path(__file__).resolve().parents[1]
100
+ / "gui" / "director.py"
101
+ )
102
+ sDestPath = os.path.join(sDockerDir, "director.py")
103
+ shutil.copy2(sSourcePath, sDestPath)
104
+
105
+
106
+ def _fnWriteFile(sPath, sContent):
107
+ """Write string content to a file."""
108
+ with open(sPath, "w") as fileHandle:
109
+ fileHandle.write(sContent)
110
+
111
+
112
+ @click.command("build")
113
+ @click.option(
114
+ "--no-cache",
115
+ "bNoCache",
116
+ is_flag=True,
117
+ default=False,
118
+ help="Build the image without using Docker cache.",
119
+ )
120
+ @click.option(
121
+ "--project", "-p", "sProjectName", default=None,
122
+ help="Project name (omit if in a project directory "
123
+ "or only one project exists).",
124
+ )
125
+ def build(bNoCache, sProjectName):
126
+ """Build the Vaibify Docker image from vaibify.yml."""
127
+ config = fconfigResolveProject(sProjectName)
128
+ sDockerDir = fsDockerDir()
129
+ click.echo(
130
+ f"Building image {config.sProjectName}:latest ..."
131
+ )
132
+ fnBuildFromConfig(config, sDockerDir, bNoCache)
133
+ click.echo("Build complete.")
@@ -0,0 +1,39 @@
1
+ """CLI subcommand: vaibify cat."""
2
+
3
+ import sys
4
+
5
+ import click
6
+
7
+ from .configLoader import fconfigResolveProject
8
+ from .commandUtilsDocker import (
9
+ fconnectionRequireDocker,
10
+ fsRequireRunningContainer,
11
+ )
12
+
13
+
14
+ def _fsNormalizePath(sPath):
15
+ """Prepend /workspace/ if the path is not absolute."""
16
+ if not sPath.startswith("/"):
17
+ return f"/workspace/{sPath}"
18
+ return sPath
19
+
20
+
21
+ @click.command("cat")
22
+ @click.option(
23
+ "--project", "-p", "sProjectName", default=None,
24
+ help="Project name.",
25
+ )
26
+ @click.argument("path")
27
+ def cat(sProjectName, path):
28
+ """Print file contents from the container."""
29
+ configProject = fconfigResolveProject(sProjectName)
30
+ connectionDocker = fconnectionRequireDocker()
31
+ sContainerName = fsRequireRunningContainer(configProject)
32
+ sNormalized = _fsNormalizePath(path)
33
+ iExitCode, sOutput = connectionDocker.ftResultExecuteCommand(
34
+ sContainerName, f"cat {sNormalized}"
35
+ )
36
+ if iExitCode != 0:
37
+ click.echo(f"Error: {sOutput.strip()}")
38
+ sys.exit(2)
39
+ click.echo(sOutput, nl=False)
@@ -0,0 +1,82 @@
1
+ """CLI subcommand group: vaibify config."""
2
+
3
+ import pathlib
4
+ import sys
5
+
6
+ import click
7
+ import yaml
8
+
9
+ from .configLoader import fconfigResolveProject, fsConfigPath
10
+
11
+
12
+ def fnWriteYaml(dictData, sPath):
13
+ """Write a dictionary to a YAML file."""
14
+ try:
15
+ with open(sPath, "w") as fileHandle:
16
+ yaml.dump(dictData, fileHandle, default_flow_style=False)
17
+ except OSError as error:
18
+ click.echo(f"Error writing {sPath}: {error}")
19
+ sys.exit(1)
20
+
21
+
22
+ def fdictLoadYamlFile(sPath):
23
+ """Load and return a YAML file as a dictionary."""
24
+ if not pathlib.Path(sPath).is_file():
25
+ click.echo(f"Error: File '{sPath}' not found.")
26
+ sys.exit(1)
27
+ try:
28
+ with open(sPath, "r") as fileHandle:
29
+ dictData = yaml.safe_load(fileHandle)
30
+ except yaml.YAMLError as error:
31
+ click.echo(f"Error parsing {sPath}: {error}")
32
+ sys.exit(1)
33
+ if dictData is None:
34
+ return {}
35
+ return dictData
36
+
37
+
38
+ @click.group("config")
39
+ def config():
40
+ """View and manage Vaibify configuration."""
41
+ pass
42
+
43
+
44
+ @config.command("export")
45
+ @click.option(
46
+ "--project", "-p", "sProjectName", default=None,
47
+ help="Project name (omit if in a project directory "
48
+ "or only one project exists).",
49
+ )
50
+ @click.argument("sfilepath", metavar="FILE")
51
+ def configExport(sProjectName, sfilepath):
52
+ """Export the current configuration to a YAML file."""
53
+ from vaibify.config.projectConfig import fnSaveToFile
54
+ configProject = fconfigResolveProject(sProjectName)
55
+ fnSaveToFile(configProject, sfilepath)
56
+ click.echo(f"Configuration exported to {sfilepath}")
57
+
58
+
59
+ @config.command("import")
60
+ @click.argument("sfilepath", metavar="FILE")
61
+ def configImport(sfilepath):
62
+ """Import configuration from a YAML file and overwrite the current config."""
63
+ dictNewConfig = fdictLoadYamlFile(sfilepath)
64
+ sConfigPath = fsConfigPath()
65
+ bExists = pathlib.Path(sConfigPath).is_file()
66
+ if bExists and not click.confirm(
67
+ "Overwrite existing vaibify.yml?"
68
+ ):
69
+ click.echo("Aborted.")
70
+ return
71
+ fnWriteYaml(dictNewConfig, sConfigPath)
72
+ click.echo(f"Configuration imported from {sfilepath}")
73
+
74
+
75
+ @config.command("edit")
76
+ def configEdit():
77
+ """Open vaibify.yml in the default editor."""
78
+ sConfigPath = fsConfigPath()
79
+ if not pathlib.Path(sConfigPath).is_file():
80
+ click.echo("Error: vaibify.yml not found.")
81
+ sys.exit(1)
82
+ click.edit(filename=sConfigPath)
@@ -0,0 +1,72 @@
1
+ """CLI subcommand: vaibify destroy."""
2
+
3
+ import sys
4
+
5
+ import click
6
+
7
+ from .configLoader import fbDockerAvailable, fconfigResolveProject
8
+
9
+
10
+ def fnRemoveVolume(sVolumeName):
11
+ """Remove a Docker volume by name."""
12
+ import docker
13
+ dockerClient = docker.from_env()
14
+ try:
15
+ volume = dockerClient.volumes.get(sVolumeName)
16
+ volume.remove(force=True)
17
+ click.echo(f"Removed volume: {sVolumeName}")
18
+ except docker.errors.NotFound:
19
+ click.echo(f"Volume '{sVolumeName}' does not exist.")
20
+ except docker.errors.APIError as error:
21
+ click.echo(f"Error removing volume: {error}")
22
+ sys.exit(1)
23
+
24
+
25
+ def fnRemoveImage(sFullName):
26
+ """Remove a Docker image by full name."""
27
+ import docker
28
+ dockerClient = docker.from_env()
29
+ try:
30
+ dockerClient.images.remove(sFullName, force=True)
31
+ click.echo(f"Removed image: {sFullName}")
32
+ except docker.errors.ImageNotFound:
33
+ click.echo(f"Image '{sFullName}' does not exist.")
34
+ except docker.errors.APIError as error:
35
+ click.echo(f"Error removing image: {error}")
36
+ sys.exit(1)
37
+
38
+
39
+ def fnRequireDocker():
40
+ """Exit with an error if the Docker Python SDK is not installed."""
41
+ if not fbDockerAvailable():
42
+ click.echo(
43
+ "Error: Docker support is not installed. "
44
+ "Install with: pip install vaibify[docker]"
45
+ )
46
+ sys.exit(1)
47
+
48
+
49
+ @click.command("destroy")
50
+ @click.option(
51
+ "--project", "-p", "sProjectName", default=None,
52
+ help="Project name (omit if in a project directory "
53
+ "or only one project exists).",
54
+ )
55
+ def destroy(sProjectName):
56
+ """Remove the Vaibify workspace volume and optionally the image."""
57
+ fnRequireDocker()
58
+ config = fconfigResolveProject(sProjectName)
59
+ sVolumeName = f"{config.sProjectName}-workspace"
60
+ sFullName = f"{config.sProjectName}:latest"
61
+ if not click.confirm(
62
+ f"This will remove the workspace volume "
63
+ f"'{sVolumeName}'. Continue?"
64
+ ):
65
+ click.echo("Aborted.")
66
+ return
67
+ fnRemoveVolume(sVolumeName)
68
+ if click.confirm(
69
+ "Also remove the Docker image?", default=False
70
+ ):
71
+ fnRemoveImage(sFullName)
72
+ click.echo("Destroy complete.")
@@ -0,0 +1,117 @@
1
+ """CLI subcommand: vaibify init."""
2
+
3
+ import pathlib
4
+ import shutil
5
+ import sys
6
+
7
+ import click
8
+
9
+ from .configLoader import fsConfigPath
10
+
11
+ from vaibify.config.registryManager import fnAddProject
12
+
13
+ _sTemplatesDir = "templates"
14
+
15
+
16
+ def flistAvailableTemplates():
17
+ """Return a list of template directory names shipped with the package."""
18
+ sPackageRoot = str(pathlib.Path(__file__).resolve().parents[2])
19
+ sTemplatesPath = pathlib.Path(sPackageRoot) / _sTemplatesDir
20
+ if not sTemplatesPath.is_dir():
21
+ return []
22
+ return sorted(
23
+ d.name for d in sTemplatesPath.iterdir() if d.is_dir()
24
+ )
25
+
26
+
27
+ def fsTemplatePath(sTemplateName):
28
+ """Return the absolute path to a named template directory."""
29
+ sPackageRoot = str(pathlib.Path(__file__).resolve().parents[2])
30
+ return str(pathlib.Path(sPackageRoot) / _sTemplatesDir / sTemplateName)
31
+
32
+
33
+ def fnPrintAvailableTemplates():
34
+ """Print available template names to stdout."""
35
+ listTemplates = flistAvailableTemplates()
36
+ if not listTemplates:
37
+ click.echo("No templates found.")
38
+ return
39
+ click.echo("Available templates:")
40
+ for sName in listTemplates:
41
+ click.echo(f" - {sName}")
42
+
43
+
44
+ def fbConfigExists():
45
+ """Return True if vaibify.yml exists in the current directory."""
46
+ return pathlib.Path(fsConfigPath()).is_file()
47
+
48
+
49
+ def fnCopyTemplate(sTemplateName):
50
+ """Copy template files into the current directory."""
51
+ sSourcePath = fsTemplatePath(sTemplateName)
52
+ if not pathlib.Path(sSourcePath).is_dir():
53
+ click.echo(f"Error: Template '{sTemplateName}' not found.")
54
+ sys.exit(1)
55
+ fnCopyDirectoryContents(sSourcePath, str(pathlib.Path.cwd()))
56
+
57
+
58
+ def fnCopyDirectoryContents(sSourceDir, sDestDir):
59
+ """Copy all files from sSourceDir into sDestDir."""
60
+ sSource = pathlib.Path(sSourceDir)
61
+ for sItem in sSource.iterdir():
62
+ sDest = pathlib.Path(sDestDir) / sItem.name
63
+ if sItem.is_dir():
64
+ shutil.copytree(str(sItem), str(sDest), dirs_exist_ok=True)
65
+ else:
66
+ shutil.copy2(str(sItem), str(sDest))
67
+
68
+
69
+ def fnWriteDefaultConfig(sTemplateName):
70
+ """Write a minimal vaibify.yml using the ProjectConfig defaults."""
71
+ from vaibify.config.projectConfig import (
72
+ ProjectConfig,
73
+ fnSaveToFile,
74
+ )
75
+ sConfigPath = fsConfigPath()
76
+ config = ProjectConfig(sProjectName=sTemplateName)
77
+ fnSaveToFile(config, sConfigPath)
78
+ click.echo(f"Created {sConfigPath}")
79
+
80
+
81
+ def fnRegisterProject():
82
+ """Register the current directory in the global project registry."""
83
+ try:
84
+ fnAddProject(str(pathlib.Path.cwd()))
85
+ except (ValueError, FileNotFoundError):
86
+ pass
87
+
88
+
89
+ @click.command("init")
90
+ @click.option(
91
+ "--template",
92
+ "sTemplateName",
93
+ default=None,
94
+ help="Name of the project template to use.",
95
+ )
96
+ @click.option(
97
+ "--force",
98
+ "bForce",
99
+ is_flag=True,
100
+ default=False,
101
+ help="Overwrite existing vaibify.yml.",
102
+ )
103
+ def init(sTemplateName, bForce):
104
+ """Initialize a new Vaibify project in the current directory."""
105
+ if sTemplateName is None:
106
+ fnPrintAvailableTemplates()
107
+ return
108
+ if fbConfigExists() and not bForce:
109
+ click.echo(
110
+ "Error: vaibify.yml already exists. "
111
+ "Use --force to overwrite."
112
+ )
113
+ sys.exit(1)
114
+ fnCopyTemplate(sTemplateName)
115
+ fnWriteDefaultConfig(sTemplateName)
116
+ fnRegisterProject()
117
+ click.echo(f"Initialized Vaibify project with '{sTemplateName}'.")
@@ -0,0 +1,59 @@
1
+ """CLI subcommand: vaibify ls."""
2
+
3
+ import sys
4
+
5
+ import click
6
+
7
+ from .configLoader import fconfigResolveProject
8
+ from .commandUtilsDocker import (
9
+ fconnectionRequireDocker,
10
+ fsRequireRunningContainer,
11
+ fnPrintJson,
12
+ fbShouldOutputJson,
13
+ )
14
+
15
+
16
+ def _fsNormalizePath(sPath):
17
+ """Prepend /workspace/ if the path is not absolute."""
18
+ if sPath and not sPath.startswith("/"):
19
+ return f"/workspace/{sPath}"
20
+ return sPath
21
+
22
+
23
+ def _flistParseDirectoryListing(sOutput):
24
+ """Parse ls output into a list of filename strings."""
25
+ listFiles = []
26
+ for sLine in sOutput.splitlines():
27
+ sStripped = sLine.strip()
28
+ if sStripped:
29
+ listFiles.append(sStripped)
30
+ return listFiles
31
+
32
+
33
+ @click.command("ls")
34
+ @click.option(
35
+ "--project", "-p", "sProjectName", default=None,
36
+ help="Project name.",
37
+ )
38
+ @click.option(
39
+ "--json", "bJson", is_flag=True, default=False,
40
+ help="Output in JSON format.",
41
+ )
42
+ @click.argument("path", default="/workspace")
43
+ def ls(sProjectName, bJson, path):
44
+ """List files in the container workspace."""
45
+ configProject = fconfigResolveProject(sProjectName)
46
+ connectionDocker = fconnectionRequireDocker()
47
+ sContainerName = fsRequireRunningContainer(configProject)
48
+ sNormalized = _fsNormalizePath(path)
49
+ iExitCode, sOutput = connectionDocker.ftResultExecuteCommand(
50
+ sContainerName, f"ls -1 {sNormalized}"
51
+ )
52
+ if iExitCode != 0:
53
+ click.echo(f"Error: {sOutput.strip()}")
54
+ sys.exit(2)
55
+ if fbShouldOutputJson(bJson):
56
+ listFiles = _flistParseDirectoryListing(sOutput)
57
+ fnPrintJson({"sPath": sNormalized, "listFiles": listFiles})
58
+ else:
59
+ click.echo(sOutput.rstrip())
@@ -0,0 +1,21 @@
1
+ """CLI subcommand group: vaibify publish (stub)."""
2
+
3
+ import click
4
+
5
+
6
+ @click.group("publish")
7
+ def publish():
8
+ """Publish reproducible archives and workflows."""
9
+ pass
10
+
11
+
12
+ @publish.command("archive")
13
+ def publishArchive():
14
+ """Create a reproducible archive of the current project."""
15
+ click.echo("Not yet implemented.")
16
+
17
+
18
+ @publish.command("workflow")
19
+ def publishWorkflow():
20
+ """Publish a workflow definition for the current project."""
21
+ click.echo("Not yet implemented.")