Repository: pypa/hatch

Issue:
Option to run all commands in script array even if one fails
[poethepoet](https://github.com/nat-n/poethepoet), for example, allows running all scripts even if any fail.  The result code will be 0 only if all succeeded.  This is nice so that you can always see all errors when running multiple linting commands, for example.  It might be nice if hatch eventually had some more advanced functionality like poethepoet for running scripts, but right now this is the only feature I actually care about.

Comments:

Patch:
diff --git a/docs/config/environment.md b/docs/config/environment.md
index 1727977a..c9b1115d 100644
--- a/docs/config/environment.md
+++ b/docs/config/environment.md
@@ -361,12 +361,36 @@ Scripts can also be defined as an array of strings.
     ]
     ```
 
+You can ignore the exit code of commands that start with `-` (a hyphen). For example, the script `error` defined by the following configuration would halt after the second command with `3` as the exit code:
+
+=== ":octicons-file-code-16: pyproject.toml"
+
+    ```toml
+    [tool.hatch.envs.test.scripts]
+    error = [
+      "- exit 1",
+      "exit 3",
+      "exit 0",
+    ]
+    ```
+
+=== ":octicons-file-code-16: hatch.toml"
+
+    ```toml
+    [envs.test.scripts]
+    error = [
+      "- exit 1",
+      "exit 3",
+      "exit 0",
+    ]
+    ```
+
 !!! tip
-    Scripts [inherit](#inheritance) from parent environments just like options.
+    Individual scripts [inherit](#inheritance) from parent environments just like options.
 
 ## Commands
 
-All commands are able to use any defined [scripts](#scripts).
+All commands are able to use any defined [scripts](#scripts). Also like scripts, the exit code of commands that start with a hyphen will be ignored.
 
 ### Pre-install
 
diff --git a/docs/history.md b/docs/history.md
index 427320ec..accdadb2 100644
--- a/docs/history.md
+++ b/docs/history.md
@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 
 - Add context formatting functionality i.e. the ability to insert values into configuration like environment variables and command line arguments
 - Add `--show-headers` option to the `env run` command to always display headers, even for single environments
+- Similar to `make`, ignore the exit code of executed commands that start with `-` (a hyphen)
 - Update project metadata to reflect the adoption by PyPA
 
 ### [1.0.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.0.0) - 2022-04-28 ### {: #hatch-v1.0.0 }
diff --git a/src/hatch/cli/application.py b/src/hatch/cli/application.py
index ac4e8dda..6601608b 100644
--- a/src/hatch/cli/application.py
+++ b/src/hatch/cli/application.py
@@ -93,6 +93,10 @@ class Application(Terminal):
     def run_shell_commands(self, environment, commands: list[str], show_code_on_error=True):
         with environment.command_context():
             for command in environment.resolve_commands(commands):
+                if command.startswith('-'):
+                    environment.run_shell_command(command[1:].strip())
+                    continue
+
                 process = environment.run_shell_command(command)
                 if process.returncode:
                     if show_code_on_error:


Test patch:
diff --git a/tests/cli/run/test_run.py b/tests/cli/run/test_run.py
index 3a18db1f..2738117c 100644
--- a/tests/cli/run/test_run.py
+++ b/tests/cli/run/test_run.py
@@ -399,6 +399,73 @@ def test_error(hatch, helpers, temp_dir, config_file):
     assert not output_file.is_file()
 
 
+def test_ignore_error(hatch, helpers, temp_dir, config_file):
+    config_file.model.template.plugins['default']['tests'] = False
+    config_file.save()
+
+    project_name = 'My App'
+
+    with temp_dir.as_cwd():
+        result = hatch('new', project_name)
+
+    assert result.exit_code == 0, result.output
+
+    project_path = temp_dir / 'my-app'
+    data_path = temp_dir / 'data'
+    data_path.mkdir()
+
+    project = Project(project_path)
+    helpers.update_project_environment(
+        project,
+        'default',
+        {
+            'skip-install': True,
+            'scripts': {
+                'error': [
+                    '- python -c "import sys;sys.exit(3)"',
+                    'python -c "import pathlib,sys;pathlib.Path(\'test.txt\').write_text(sys.executable)"',
+                ],
+            },
+            **project.config.envs['default'],
+        },
+    )
+
+    with project_path.as_cwd(env_vars={ConfigEnvVars.DATA: str(data_path)}):
+        result = hatch('run', 'error')
+
+    assert result.exit_code == 0
+    assert result.output == helpers.dedent(
+        """
+        Creating environment: default
+        """
+    )
+    output_file = project_path / 'test.txt'
+    assert output_file.is_file()
+
+    env_data_path = data_path / 'env' / 'virtual'
+    assert env_data_path.is_dir()
+
+    storage_dirs = list(env_data_path.iterdir())
+    assert len(storage_dirs) == 1
+
+    storage_path = storage_dirs[0]
+
+    project_part = f'{project_path.name}-'
+    assert storage_path.name.startswith(project_part)
+
+    hash_part = storage_path.name[len(project_part) :]
+    assert len(hash_part) == 8
+
+    env_dirs = list(storage_path.iterdir())
+    assert len(env_dirs) == 1
+
+    env_path = env_dirs[0]
+
+    assert env_path.name == project_path.name
+
+    assert str(env_path) in str(output_file.read_text())
+
+
 def test_matrix_no_environments(hatch, helpers, temp_dir, config_file):
     config_file.model.template.plugins['default']['tests'] = False
     config_file.save()