robotcode 2.6.2__tar.gz → 2.7.0__tar.gz

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.
@@ -2,6 +2,112 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
4
4
 
5
+ ## [2.7.0](https://github.com/robotcodedev/robotcode/compare/v2.6.2..v2.7.0) - 2026-07-14
6
+
7
+ ### <!-- 0 -->Features
8
+
9
+ - **robot:** Make the analysis cache trustworthy under rapid file changes ([c998399](https://github.com/robotcodedev/robotcode/commit/c998399906355535cd3c2d60aaa3c071a37a410e))
10
+
11
+ Fast consecutive changes to several files could poison the analysis
12
+ cache: the editor and the CLI then reported errors that did not exist,
13
+ like missing keywords or unresolvable variables, until the affected
14
+ files were touched again. Cached results are now bound to exactly the
15
+ file state they were computed from, and unsaved editor content is
16
+ analyzed but never cached, so stale results can no longer stick.
17
+
18
+ - **robotcode:** Run tests through a wrapper ([7aa84f6](https://github.com/robotcodedev/robotcode/commit/7aa84f6c08d17e95d981e67692763408d66c497c))
19
+
20
+ Run the test execution through a command of your choosing, so you can
21
+ prepare the environment a run needs - start the services, servers,
22
+ mocks or session the tests depend on, run the tests inside it, and tear
23
+ it down again afterwards. For anything that is only environment
24
+ variables, keep using robot.toml's [env].
25
+
26
+ Configure it per profile in robot.toml:
27
+
28
+ [profiles.integration]
29
+ wrapper = ["./with-test-services.sh"]
30
+
31
+ set it ad hoc with --wrapper (or the ROBOTCODE_WRAPPER env var), and
32
+ turn a configured wrapper off for one run with --no-wrapper. It applies
33
+ to every command that executes Robot Framework - run, debug, the REPL
34
+ and the notebook kernel - never to discovery, the language server or
35
+ libdoc.
36
+
37
+ Editors pick it up automatically because they run robotcode; the
38
+ VS Code setting robotcode.debug.launchWrapper can override it for the
39
+ IDE alone.
40
+
41
+
42
+
43
+ ### <!-- 1 -->Bug Fixes
44
+
45
+ - **plugin:** Use compact JSON separators for json output format ([e882368](https://github.com/robotcodedev/robotcode/commit/e882368e52326233100dfe6ddbee3e77c84fbc38))
46
+
47
+ The compact flag was compared against OutputFormat.TEXT instead of
48
+ OutputFormat.JSON, so compact JSON output was never actually applied.
49
+
50
+ - **robotcode:** Make the wrapper work on Windows ([7baa0ac](https://github.com/robotcodedev/robotcode/commit/7baa0ac9499c406bb47bbb8a16aaa9152bb1ef6f))
51
+
52
+ The wrapper re-run used os.execvp to replace the process, which only has
53
+ POSIX semantics; on Windows os.exec* spawns a new process and exits the
54
+ caller, so the exit code and stdout/stderr don't propagate. Keep execvp
55
+ on POSIX (clean, no extra process) and on Windows spawn the command,
56
+ wait, and forward its exit code. The subprocess tests, and their wrapper
57
+ scripts, use spawn-and-wait too, so they no longer skip on Windows.
58
+
59
+ Also document how running through a wrapper differs on Linux/macOS vs.
60
+ Windows, and why.
61
+
62
+ - **robotcode:** Make the wrapper work in VS Code ([1180d9c](https://github.com/robotcodedev/robotcode/commit/1180d9cdc13e7dc313bc26c03c84ba663c8d34c7))
63
+
64
+ Running or debugging tests through a wrapper did not work from the
65
+ editor, where robotcode runs from a bundled path instead of being
66
+ installed in the interpreter. Two things are fixed:
67
+
68
+ - The debug launcher no longer applies the wrapper itself; it forwards a
69
+ launch.json wrapper to robotcode as --wrapper and lets robotcode apply
70
+ it (so a launch.json wrapper still overrides the profile's).
71
+
72
+ - When robotcode re-executes itself under the wrapper it re-runs through
73
+ the same entry it was started from (--launcher-script) instead of
74
+ python -m robotcode.cli, which the bundled interpreter cannot import.
75
+ ROBOTCODE_BUNDLED_ROBOTCODE_MAIN is deliberately not used for this: VS
76
+ Code sets it in every terminal, so it would divert a directly started
77
+ robotcode to the bundled copy.
78
+
79
+ - **vscode:** Adapt to vscode-languageclient 10 type changes ([95f305a](https://github.com/robotcodedev/robotcode/commit/95f305ae6f402287f2cb0d2ac5028acc4695a69c))
80
+
81
+ Bumping vscode-languageclient to 10.1.0 broke the typecheck:
82
+ LanguageClient now requires a LogOutputChannel instead of a plain
83
+ OutputChannel, and Diagnostic.message can now be a MarkupContent
84
+ instead of just a string.
85
+
86
+ - **vscode:** Restore -I and -N options when running tests ([c76b113](https://github.com/robotcodedev/robotcode/commit/c76b113936599446b9fdae20c6f9441f9a77be43))
87
+
88
+ Running a single test, a file, or any subset from the Test Explorer
89
+ again passes Robot Framework's parse-include (-I) and top-level suite
90
+ name (-N) options. Since 2.6.0 both were missing, so on Robot Framework
91
+ 6.1+ the run parsed the whole workspace and surfaced parse errors from
92
+ unrelated files.
93
+
94
+ Robot Framework's --parseinclude support is now reported as a
95
+ per-project fact on the discovery result envelope. In the
96
+ `robotcode discover -f json` output this is the new
97
+ `supportsParseInclude` key on the result (replacing the previous
98
+ `needsParseInclude` key on each item).
99
+
100
+
101
+
102
+ ### <!-- 2 -->Documentation
103
+
104
+ - **news:** Add v2.7.0 release notes ([033e12d](https://github.com/robotcodedev/robotcode/commit/033e12d90d8250513c5805113266996bae512680))
105
+
106
+ Announce the test-run wrapper, the trustworthy analysis cache, and the
107
+ restored -I/-N options for Test Explorer subset runs.
108
+
109
+
110
+
5
111
  ## [2.6.2](https://github.com/robotcodedev/robotcode/compare/v2.6.1..v2.6.2) - 2026-06-15
6
112
 
7
113
  ### <!-- 1 -->Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotcode
3
- Version: 2.6.2
3
+ Version: 2.7.0
4
4
  Summary: Command line interface for RobotCode
5
5
  Project-URL: Homepage, https://robotcode.io
6
6
  Project-URL: Donate, https://opencollective.com/robotcode
@@ -33,35 +33,35 @@ Classifier: Topic :: Text Editors :: Integrated Development Environments (IDE)
33
33
  Classifier: Topic :: Utilities
34
34
  Classifier: Typing :: Typed
35
35
  Requires-Python: >=3.10
36
- Requires-Dist: robotcode-core==2.6.2
37
- Requires-Dist: robotcode-plugin==2.6.2
38
- Requires-Dist: robotcode-robot==2.6.2
36
+ Requires-Dist: robotcode-core==2.7.0
37
+ Requires-Dist: robotcode-plugin==2.7.0
38
+ Requires-Dist: robotcode-robot==2.7.0
39
39
  Provides-Extra: all
40
40
  Requires-Dist: docutils; extra == 'all'
41
41
  Requires-Dist: pyyaml>=5.4; extra == 'all'
42
- Requires-Dist: robotcode-analyze==2.6.2; extra == 'all'
43
- Requires-Dist: robotcode-debugger==2.6.2; extra == 'all'
44
- Requires-Dist: robotcode-language-server==2.6.2; extra == 'all'
45
- Requires-Dist: robotcode-repl-server==2.6.2; extra == 'all'
46
- Requires-Dist: robotcode-repl==2.6.2; extra == 'all'
47
- Requires-Dist: robotcode-runner==2.6.2; extra == 'all'
42
+ Requires-Dist: robotcode-analyze==2.7.0; extra == 'all'
43
+ Requires-Dist: robotcode-debugger==2.7.0; extra == 'all'
44
+ Requires-Dist: robotcode-language-server==2.7.0; extra == 'all'
45
+ Requires-Dist: robotcode-repl-server==2.7.0; extra == 'all'
46
+ Requires-Dist: robotcode-repl==2.7.0; extra == 'all'
47
+ Requires-Dist: robotcode-runner==2.7.0; extra == 'all'
48
48
  Requires-Dist: robotframework-robocop>=6.0.0; extra == 'all'
49
49
  Provides-Extra: analyze
50
- Requires-Dist: robotcode-analyze==2.6.2; extra == 'analyze'
50
+ Requires-Dist: robotcode-analyze==2.7.0; extra == 'analyze'
51
51
  Provides-Extra: debugger
52
- Requires-Dist: robotcode-debugger==2.6.2; extra == 'debugger'
52
+ Requires-Dist: robotcode-debugger==2.7.0; extra == 'debugger'
53
53
  Provides-Extra: languageserver
54
- Requires-Dist: robotcode-language-server==2.6.2; extra == 'languageserver'
54
+ Requires-Dist: robotcode-language-server==2.7.0; extra == 'languageserver'
55
55
  Provides-Extra: lint
56
56
  Requires-Dist: robotframework-robocop>=2.0.0; extra == 'lint'
57
57
  Provides-Extra: repl
58
- Requires-Dist: robotcode-repl==2.6.2; extra == 'repl'
58
+ Requires-Dist: robotcode-repl==2.7.0; extra == 'repl'
59
59
  Provides-Extra: replserver
60
- Requires-Dist: robotcode-repl-server==2.6.2; extra == 'replserver'
60
+ Requires-Dist: robotcode-repl-server==2.7.0; extra == 'replserver'
61
61
  Provides-Extra: rest
62
62
  Requires-Dist: docutils; extra == 'rest'
63
63
  Provides-Extra: runner
64
- Requires-Dist: robotcode-runner==2.6.2; extra == 'runner'
64
+ Requires-Dist: robotcode-runner==2.7.0; extra == 'runner'
65
65
  Provides-Extra: yaml
66
66
  Requires-Dist: pyyaml>=5.4; extra == 'yaml'
67
67
  Description-Content-Type: text/markdown
@@ -48,26 +48,26 @@ classifiers = [
48
48
  "Framework :: Robot Framework :: Tool",
49
49
  ]
50
50
  requires-python = ">=3.10"
51
- dependencies = ["robotcode-core==2.6.2", "robotcode-plugin==2.6.2", "robotcode-robot==2.6.2"]
51
+ dependencies = ["robotcode-core==2.7.0", "robotcode-plugin==2.7.0", "robotcode-robot==2.7.0"]
52
52
  dynamic = ["version"]
53
53
 
54
54
  [project.optional-dependencies]
55
- debugger = ["robotcode-debugger==2.6.2"]
56
- languageserver = ["robotcode-language-server==2.6.2"]
57
- runner = ["robotcode-runner==2.6.2"]
58
- analyze = ["robotcode-analyze==2.6.2"]
55
+ debugger = ["robotcode-debugger==2.7.0"]
56
+ languageserver = ["robotcode-language-server==2.7.0"]
57
+ runner = ["robotcode-runner==2.7.0"]
58
+ analyze = ["robotcode-analyze==2.7.0"]
59
59
  yaml = ["PyYAML>=5.4"]
60
60
  lint = ["robotframework-robocop>=2.0.0"]
61
61
  rest = ["docutils"]
62
- repl = ["robotcode-repl==2.6.2"]
63
- replserver = ["robotcode-repl-server==2.6.2"]
62
+ repl = ["robotcode-repl==2.7.0"]
63
+ replserver = ["robotcode-repl-server==2.7.0"]
64
64
  all = [
65
- "robotcode-debugger==2.6.2",
66
- "robotcode-language-server==2.6.2",
67
- "robotcode-runner==2.6.2",
68
- "robotcode-analyze==2.6.2",
69
- "robotcode-repl==2.6.2",
70
- "robotcode-repl-server==2.6.2",
65
+ "robotcode-debugger==2.7.0",
66
+ "robotcode-language-server==2.7.0",
67
+ "robotcode-runner==2.7.0",
68
+ "robotcode-analyze==2.7.0",
69
+ "robotcode-repl==2.7.0",
70
+ "robotcode-repl-server==2.7.0",
71
71
  "PyYAML>=5.4",
72
72
  "robotframework-robocop>=6.0.0",
73
73
  "docutils"
@@ -2,6 +2,9 @@ import json
2
2
  import logging
3
3
  import logging.config
4
4
  import os
5
+ import shlex
6
+ import subprocess
7
+ import sys
5
8
  from pathlib import Path
6
9
  from typing import Any, List, Literal, Optional
7
10
 
@@ -18,6 +21,7 @@ from robotcode.plugin import (
18
21
  from robotcode.plugin._agent_detection import is_running_in_ai_agent
19
22
  from robotcode.plugin.click_helper.aliases import AliasedGroup
20
23
  from robotcode.plugin.click_helper.types import EnumChoice
24
+ from robotcode.plugin.click_helper.wrappable import WRAPPER_APPLIED_ENV, is_wrappable
21
25
  from robotcode.plugin.manager import PluginManager
22
26
 
23
27
  from .__version__ import __version__
@@ -46,6 +50,99 @@ class RobotCodeFormatter(logging.Formatter):
46
50
  super().__init__(*args, defaults=defaults, **kwargs)
47
51
 
48
52
 
53
+ def _maybe_reexec_under_wrapper(
54
+ ctx: click.Context, app: Application, cli_wrapper: Optional[str] = None, no_wrapper: bool = False
55
+ ) -> None:
56
+ """Re-execute this process through a configured ``wrapper`` command.
57
+
58
+ When a ``wrapper`` is configured - either via ``--wrapper`` on the command
59
+ line (``cli_wrapper``) or the selected profile's ``wrapper`` (e.g. to run
60
+ the tests inside a specific X11/Wayland session) - and the invoked command
61
+ is ``@wrappable`` (i.e. it executes Robot Framework), the process
62
+ re-executes itself **once** through that command before Robot Framework
63
+ starts. ``--wrapper`` overrides the profile; ``--no-wrapper`` disables
64
+ wrapping for this run. The guard env var prevents infinite recursion and
65
+ lets an outer layer suppress the re-exec by setting it itself.
66
+ """
67
+ if no_wrapper:
68
+ if cli_wrapper:
69
+ app.warning("Ignoring --wrapper because --no-wrapper was given.")
70
+ return
71
+ if os.environ.get(WRAPPER_APPLIED_ENV):
72
+ return
73
+
74
+ sub_name = ctx.invoked_subcommand
75
+ if not sub_name:
76
+ return
77
+ # The command itself opts in via @wrappable; aliases resolve to the same
78
+ # command object, so this is alias-safe and needs no central name list.
79
+ sub_cmd = ctx.command.get_command(ctx, sub_name) if isinstance(ctx.command, click.Group) else None
80
+ if sub_cmd is None or not is_wrappable(sub_cmd):
81
+ if cli_wrapper:
82
+ app.warning(f"Ignoring --wrapper: '{sub_name}' does not execute Robot Framework.")
83
+ return
84
+
85
+ from robotcode.robot.config.loader import load_robot_config_from_path
86
+ from robotcode.robot.config.utils import get_config_files
87
+
88
+ profile_wrapper = None
89
+ try:
90
+ config_files, _, _ = get_config_files(
91
+ config_files=app.config.config_files,
92
+ root_folder=app.config.root,
93
+ no_vcs=app.config.no_vcs,
94
+ verbose_callback=app.verbose,
95
+ )
96
+ # `evaluated_with_env` also applies the profile's `env` to `os.environ`,
97
+ # so the wrapper command can rely on it.
98
+ profile = (
99
+ load_robot_config_from_path(*config_files, verbose_callback=app.verbose)
100
+ .combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error)
101
+ .evaluated_with_env(verbose_callback=app.verbose, error_callback=app.error)
102
+ )
103
+ profile_wrapper = profile.wrapper
104
+ except Exception as e:
105
+ # Don't fail early on config problems here; the actual command loads the
106
+ # config again and reports errors properly. An explicit --wrapper is
107
+ # still honored below.
108
+ message = str(e)
109
+ app.verbose(lambda: f"Skipping profile wrapper detection: {message}")
110
+
111
+ # `--wrapper` overrides the profile's `wrapper`. The evaluated profile turns
112
+ # StringExpression entries into plain strings.
113
+ if cli_wrapper:
114
+ wrapper = shlex.split(cli_wrapper)
115
+ elif profile_wrapper:
116
+ wrapper = [str(w) for w in profile_wrapper]
117
+ else:
118
+ return
119
+
120
+ # Rebuild the invocation the way this process was actually started, so the
121
+ # re-exec finds robotcode even when it runs from a bundled path (VS Code) and
122
+ # is not installed in the interpreter. `launcher_script` is set only when we
123
+ # were started through a bundled entry (its `__main__` sets it) or an explicit
124
+ # `--launcher-script`; otherwise robotcode is importable directly and
125
+ # `-m robotcode.cli` is correct. We deliberately do NOT consult the
126
+ # ROBOTCODE_BUNDLED_ROBOTCODE_MAIN env var: VS Code sets it in every terminal,
127
+ # so a directly started robotcode would be diverted to the bundled copy.
128
+ # Mirrors the debug launcher's `debugger_script` handling.
129
+ entry = [str(app.config.launcher_script)] if app.config.launcher_script else ["-m", "robotcode.cli"]
130
+ command = [*wrapper, sys.executable, *entry, *sys.argv[1:]]
131
+ app.verbose(lambda: "Running under wrapper: " + " ".join(command))
132
+
133
+ # Run robotcode again under the wrapper; the guard env stops the spawned
134
+ # robotcode from wrapping a second time. On POSIX we replace this process with
135
+ # execvp (no extra process); Windows has no such exec, so there we spawn the
136
+ # command, wait, and forward its exit code.
137
+ os.environ[WRAPPER_APPLIED_ENV] = "1"
138
+ try:
139
+ if sys.platform == "win32":
140
+ sys.exit(subprocess.run(command).returncode)
141
+ os.execvp(command[0], command)
142
+ except OSError as e:
143
+ raise click.ClickException(f"Failed to execute wrapper command {wrapper!r}: {e}") from e
144
+
145
+
49
146
  @click.group(
50
147
  cls=AliasedGroup,
51
148
  context_settings={"auto_envvar_prefix": "ROBOTCODE"},
@@ -129,6 +226,20 @@ class RobotCodeFormatter(logging.Formatter):
129
226
  help="Enables verbose mode.",
130
227
  show_envvar=True,
131
228
  )
229
+ @click.option(
230
+ "--wrapper",
231
+ type=str,
232
+ default=None,
233
+ show_envvar=True,
234
+ help='Command prefix to run the test execution through, e.g. `--wrapper "xvfb-run -a"`. '
235
+ "Split like a shell command. Applies only to commands that execute Robot Framework "
236
+ "(run/debug/repl). Overrides the profile's `wrapper`.",
237
+ )
238
+ @click.option(
239
+ "--no-wrapper",
240
+ is_flag=True,
241
+ help="Disable any configured `wrapper` (profile or --wrapper) for this run.",
242
+ )
132
243
  @click.option("--log", is_flag=True, help="Enables logging.", show_envvar=True)
133
244
  @click.option(
134
245
  "--log-level",
@@ -230,7 +341,9 @@ class RobotCodeFormatter(logging.Formatter):
230
341
  )
231
342
  @click.version_option(version=__version__, prog_name="robotcode")
232
343
  @pass_application
344
+ @click.pass_context
233
345
  def robotcode(
346
+ ctx: click.Context,
234
347
  app: Application,
235
348
  config_files: Optional[List[Path]],
236
349
  profiles: Optional[List[str]],
@@ -249,6 +362,8 @@ def robotcode(
249
362
  log_calls: bool,
250
363
  log_config: Optional[Path],
251
364
  default_path: Optional[List[str]],
365
+ wrapper: Optional[str] = None,
366
+ no_wrapper: bool = False,
252
367
  launcher_script: Optional[str] = None,
253
368
  debugpy: bool = False,
254
369
  debugpy_port: int = 5678,
@@ -295,6 +410,8 @@ def robotcode(
295
410
  app.config.log_level = log_level
296
411
  app.config.log_calls = log_calls
297
412
 
413
+ _maybe_reexec_under_wrapper(ctx, app, wrapper, no_wrapper)
414
+
298
415
  if log_config:
299
416
  if log_calls:
300
417
  LoggingDescriptor.set_call_tracing(True)
@@ -0,0 +1 @@
1
+ __version__ = "2.7.0"
@@ -1 +0,0 @@
1
- __version__ = "2.6.2"
File without changes
File without changes
File without changes
File without changes