marsh-lib 0.3.0__tar.gz → 0.3.2__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.
Files changed (82) hide show
  1. marsh_lib-0.3.2/.circleci/config.yml +125 -0
  2. marsh_lib-0.3.2/.github/workflows/docker-integration.yml +39 -0
  3. marsh_lib-0.3.2/.github/workflows/release-rehearsal.yml +179 -0
  4. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/.github/workflows/release.yml +8 -5
  5. marsh_lib-0.3.2/.github/workflows/ssh-integration.yml +59 -0
  6. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/.github/workflows/test.yml +25 -3
  7. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/PKG-INFO +3 -1
  8. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/README.md +2 -0
  9. marsh_lib-0.3.2/docs/api-migration.md +218 -0
  10. marsh_lib-0.3.2/docs/v0.3.2-test-portability.md +73 -0
  11. marsh_lib-0.3.2/docs/workflow-guide.md +347 -0
  12. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/pyproject.toml +1 -1
  13. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/configuration.py +13 -10
  14. marsh_lib-0.3.2/src/marsh/core/dag_bridge.py +33 -0
  15. marsh_lib-0.3.2/src/marsh/core/diagnostics.py +15 -0
  16. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/runtime.py +29 -9
  17. marsh_lib-0.3.2/src/marsh/core/validation.py +61 -0
  18. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/docker/docker_command_grammar.py +1 -1
  19. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/docker/docker_executor.py +10 -2
  20. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/modifier_functions/case_conversion.py +1 -1
  21. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/modifier_functions/readers.py +2 -2
  22. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/processor_functions/printers.py +3 -2
  23. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/processor_functions/redirections.py +1 -1
  24. marsh_lib-0.3.2/src/marsh/providers/__init__.py +5 -0
  25. marsh_lib-0.3.2/src/marsh/runtime/__init__.py +5 -0
  26. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/ssh/ssh_connector.py +1 -0
  27. marsh_lib-0.3.2/src/marsh/workflow/__init__.py +8 -0
  28. marsh_lib-0.3.0/.circleci/config.yml +0 -73
  29. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  30. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  31. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/.github/ISSUE_TEMPLATE/other.md +0 -0
  32. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/.github/pull_request_template.md +0 -0
  33. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/.gitignore +0 -0
  34. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/LICENSE +0 -0
  35. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/Taskfile.yml +0 -0
  36. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/docs/P0_REPOSITORY_AUDIT.md +0 -0
  37. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/docs/P6_UX_COMPOSITION_EVALUATION.md +0 -0
  38. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/docs/architecture.md +0 -0
  39. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/run_act.sh +0 -0
  40. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/run_pytest.sh +0 -0
  41. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/run_tests_with_sysbox.sh +0 -0
  42. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/__init__.py +0 -0
  43. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/bash/__init__.py +0 -0
  44. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/bash/bash_factory.py +0 -0
  45. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/bash/bash_grammar.py +0 -0
  46. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/bash/bash_runner_decorators.py +0 -0
  47. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/bash/bash_script.py +0 -0
  48. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/constants.py +0 -0
  49. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/__init__.py +0 -0
  50. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/authenticator.py +0 -0
  51. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/cache.py +0 -0
  52. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/cmd_run_decorator.py +0 -0
  53. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/cmd_runner_spec.py +0 -0
  54. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/command_grammar.py +0 -0
  55. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/connector.py +0 -0
  56. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/conveyor.py +0 -0
  57. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/domain.py +0 -0
  58. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/executor.py +0 -0
  59. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/expression.py +0 -0
  60. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/observability.py +0 -0
  61. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/policies.py +0 -0
  62. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/providers.py +0 -0
  63. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/script.py +0 -0
  64. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/core/serialization.py +0 -0
  65. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/dag/__init__.py +0 -0
  66. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/dag/dag.py +0 -0
  67. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/dag/node.py +0 -0
  68. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/dag/startable.py +0 -0
  69. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/docker/__init__.py +0 -0
  70. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/exceptions.py +0 -0
  71. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/logger.py +0 -0
  72. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/modifier_functions/__init__.py +0 -0
  73. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/powershell/__init__.py +0 -0
  74. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/processor_functions/__init__.py +0 -0
  75. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/processor_functions/raisers.py +0 -0
  76. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/signals.py +0 -0
  77. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/ssh/__init__.py +0 -0
  78. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/ssh/ssh_command_grammar.py +0 -0
  79. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/ssh/ssh_factory.py +0 -0
  80. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/utils/__init__.py +0 -0
  81. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/src/marsh/utils/output_streams.py +0 -0
  82. {marsh_lib-0.3.0 → marsh_lib-0.3.2}/uv.lock +0 -0
@@ -0,0 +1,125 @@
1
+ version: 2.1
2
+
3
+ jobs:
4
+ test:
5
+ parameters:
6
+ python-version:
7
+ type: string
8
+ docker:
9
+ - image: cimg/python:<< parameters.python-version >>
10
+ resource_class: medium
11
+ steps:
12
+ - checkout
13
+ - run:
14
+ name: Install uv
15
+ command: |
16
+ python -m pip install --upgrade pip
17
+ python -m pip install uv
18
+ - run:
19
+ name: Install dependencies
20
+ command: uv sync --all-groups
21
+ - run:
22
+ name: Run baseline tests
23
+ command: |
24
+ mkdir -p test-results
25
+ uv run pytest -vv --disable-warnings --tb=short \
26
+ --junitxml=test-results/junit.xml \
27
+ --ignore=tests/ssh/test_ssh_connector.py \
28
+ --ignore=tests/ssh/test_ssh_factory.py \
29
+ --ignore=tests/docker/
30
+ - store_test_results:
31
+ path: test-results
32
+
33
+ lint:
34
+ docker:
35
+ - image: cimg/python:3.10
36
+ resource_class: small
37
+ steps:
38
+ - checkout
39
+ - run:
40
+ name: Install uv
41
+ command: |
42
+ python -m pip install --upgrade pip
43
+ python -m pip install uv
44
+ - run:
45
+ name: Install dependencies
46
+ command: uv sync --all-groups
47
+ - run:
48
+ name: Run pflake8
49
+ command: uv run pflake8 src/marsh --select=E9,F --ignore=F401,F403,F405,F541,F811,F841
50
+
51
+ build:
52
+ docker:
53
+ - image: cimg/python:3.10
54
+ resource_class: small
55
+ steps:
56
+ - checkout
57
+ - run:
58
+ name: Install uv
59
+ command: |
60
+ python -m pip install --upgrade pip
61
+ python -m pip install uv
62
+ - run:
63
+ name: Install dependencies
64
+ command: uv sync --all-groups
65
+ - run:
66
+ name: Build wheel
67
+ command: uv build --wheel --out-dir dist -v
68
+
69
+ integration:
70
+ machine:
71
+ image: ubuntu-2404:current
72
+ docker_layer_caching: false
73
+ resource_class: medium
74
+ steps:
75
+ - checkout
76
+ - run:
77
+ name: Install uv
78
+ command: |
79
+ set -euxo pipefail
80
+ python3 -m pip install --upgrade pip
81
+ python3 -m pip install --user uv==0.12.12
82
+ USER_BIN="$(python3 -m site --user-base)/bin"
83
+ echo "export PATH=\"$USER_BIN:\$PATH\"" >> "$BASH_ENV"
84
+ export PATH="$USER_BIN:$PATH"
85
+ "$USER_BIN/uv" --version
86
+ command -v uv
87
+ - run:
88
+ name: Install dependencies
89
+ command: uv sync --all-groups
90
+ - run:
91
+ name: Validate dependency and platform contract
92
+ command: |
93
+ uv run python - \<<'PY'
94
+ import sys
95
+ from pathlib import Path
96
+ import tomllib
97
+
98
+ assert sys.version_info[:2] == (3, 12), sys.version
99
+ project = tomllib.loads(Path("pyproject.toml").read_text())["project"]
100
+ assert "optional-dependencies" not in project, "Unexpected optional dependency surface"
101
+ assert project["requires-python"] == ">=3.10,<3.13"
102
+ print("Python 3.12 integration environment validated; optional dependency surface is N/A.")
103
+ PY
104
+ - run:
105
+ name: Pull Docker integration test images
106
+ command: |
107
+ docker pull bash:latest
108
+ docker pull ubuntu:24.04
109
+ - run:
110
+ name: Run Docker integration tests
111
+ command: uv run pytest -vv --disable-warnings --tb=short tests/docker
112
+ - run:
113
+ name: Run SSH integration tests
114
+ command: uv run pytest -vv --disable-warnings --tb=short tests/ssh
115
+
116
+ workflows:
117
+ baseline:
118
+ jobs:
119
+ - test:
120
+ matrix:
121
+ parameters:
122
+ python-version: ["3.10", "3.11", "3.12"]
123
+ - lint
124
+ - build
125
+ - integration
@@ -0,0 +1,39 @@
1
+ name: Docker Integration Smoke
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - develop
7
+ paths:
8
+ - "src/marsh/docker/**"
9
+ - "tests/docker/**"
10
+ - "pyproject.toml"
11
+ - "uv.lock"
12
+ - ".github/workflows/docker-integration.yml"
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ docker-integration:
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: 10
21
+ steps:
22
+ - name: Checkout
23
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
27
+ with:
28
+ version: "0.12.12"
29
+ enable-cache: false
30
+
31
+ - name: Install dependencies
32
+ run: uv sync --all-groups
33
+
34
+ - name: Pull integration image
35
+ run: docker pull bash:latest
36
+
37
+ - name: Run Docker smoke tests
38
+ run: |
39
+ uv run pytest -vv --disable-warnings --tb=short tests/docker/test_docker_container.py::test_docker_container_resource_lifecycle tests/docker/test_docker_command_executor.py::test_basic_command_without_piping
@@ -0,0 +1,179 @@
1
+ name: Release Rehearsal
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - chore/p7-release-rehearsal-and-matrix
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ rehearsal:
13
+ name: Build, publish, verify, and exercise recovery against TestPyPI
14
+ runs-on: ubuntu-latest
15
+ environment:
16
+ name: testpypi
17
+ permissions:
18
+ contents: read
19
+ id-token: write
20
+ steps:
21
+ - name: Checkout exact rehearsal revision
22
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ with:
24
+ fetch-depth: 1
25
+ persist-credentials: false
26
+
27
+ - name: Install uv
28
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
29
+ with:
30
+ version: "0.12.12"
31
+ enable-cache: false
32
+
33
+ - name: Build wheel and source distribution once
34
+ run: uv build --no-sources --out-dir dist
35
+
36
+ - name: Validate exact rehearsal artifact set
37
+ env:
38
+ RELEASE_VERSION: "0.3.0"
39
+ run: |
40
+ python - <<'PY'
41
+ import os
42
+ from pathlib import Path
43
+ import tomllib
44
+
45
+ version = os.environ["RELEASE_VERSION"]
46
+ project = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]
47
+ if project != version:
48
+ raise SystemExit(f"Package version {project!r} does not match rehearsal version {version!r}")
49
+
50
+ files = sorted(
51
+ p.name for p in Path("dist").iterdir()
52
+ if p.is_file() and not p.name.startswith(".")
53
+ )
54
+ wheels = [name for name in files if name.endswith(".whl")]
55
+ sdists = [name for name in files if name.endswith(".tar.gz")]
56
+ if len(wheels) != 1 or len(sdists) != 1 or len(files) != 2:
57
+ raise SystemExit(f"Expected exactly one wheel and one sdist, found: {files}")
58
+
59
+ if not wheels[0].startswith(f"marsh_lib-{version}-"):
60
+ raise SystemExit(f"Unexpected wheel filename: {wheels[0]}")
61
+ if sdists[0] != f"marsh_lib-{version}.tar.gz":
62
+ raise SystemExit(f"Unexpected sdist filename: {sdists[0]}")
63
+ print("\n".join(files))
64
+ PY
65
+
66
+ - name: Smoke test wheel
67
+ run: uv run --isolated --no-project --with dist/*.whl python -c "import marsh; print(marsh.__file__)"
68
+
69
+ - name: Smoke test source distribution
70
+ run: uv run --isolated --no-project --with dist/*.tar.gz python -c "import marsh; print(marsh.__file__)"
71
+
72
+ - name: Record exact distribution hashes
73
+ run: |
74
+ sha256sum dist/*.whl dist/*.tar.gz | tee dist/SHA256SUMS
75
+
76
+ - name: Exercise verifier failure and recovery path
77
+ run: |
78
+ set -euo pipefail
79
+ mkdir -p recovery-test
80
+ cp dist/*.whl recovery-test/
81
+ python - <<'PY'
82
+ from pathlib import Path
83
+ wheel = next(Path("recovery-test").glob("*.whl"))
84
+ wheel.write_bytes(wheel.read_bytes() + b"intentional-recovery-test")
85
+ PY
86
+
87
+ set +e
88
+ python - <<'PY'
89
+ import hashlib
90
+ from pathlib import Path
91
+
92
+ expected = {
93
+ p.name: hashlib.sha256(p.read_bytes()).hexdigest()
94
+ for p in Path("dist").glob("*.whl")
95
+ }
96
+ tampered = next(Path("recovery-test").glob("*.whl"))
97
+ if hashlib.sha256(tampered.read_bytes()).hexdigest() != expected[tampered.name]:
98
+ raise SystemExit(1)
99
+ PY
100
+ verifier_status=$?
101
+ set -e
102
+
103
+ if [ "$verifier_status" -eq 0 ]; then
104
+ echo "ERROR: tampered artifact was not detected"
105
+ exit 1
106
+ fi
107
+ echo "Recovery drill: tampered artifact correctly rejected"
108
+
109
+ rm -rf recovery-test
110
+ python - <<'PY'
111
+ import hashlib
112
+ from pathlib import Path
113
+
114
+ for p in list(Path("dist").glob("*.whl")) + list(Path("dist").glob("*.tar.gz")):
115
+ recorded = next(
116
+ line.split()[0]
117
+ for line in Path("dist/SHA256SUMS").read_text().splitlines()
118
+ if Path(line.split(maxsplit=1)[1]).name == p.name
119
+ )
120
+ actual = hashlib.sha256(p.read_bytes()).hexdigest()
121
+ assert recorded == actual, p.name
122
+ print("Recovery drill: original immutable artifacts revalidated successfully")
123
+ PY
124
+
125
+ - name: Publish exact artifacts to TestPyPI
126
+ run: uv publish --publish-url https://test.pypi.org/legacy/
127
+
128
+ - name: Verify TestPyPI hashes
129
+ env:
130
+ RELEASE_VERSION: "0.3.0"
131
+ run: |
132
+ python - <<'PY'
133
+ import hashlib
134
+ import json
135
+ import os
136
+ from pathlib import Path
137
+ import urllib.request
138
+
139
+ version = os.environ["RELEASE_VERSION"]
140
+ url = f"https://test.pypi.org/pypi/marsh-lib/{version}/json"
141
+ with urllib.request.urlopen(url, timeout=30) as response:
142
+ payload = json.load(response)
143
+
144
+ expected = {
145
+ p.name: hashlib.sha256(p.read_bytes()).hexdigest()
146
+ for pattern in ("*.whl", "*.tar.gz")
147
+ for p in Path("dist").glob(pattern)
148
+ if p.is_file()
149
+ }
150
+ published = {
151
+ item["filename"]: item["digests"]["sha256"]
152
+ for item in payload["urls"]
153
+ }
154
+ missing = sorted(set(expected) - set(published))
155
+ unexpected = sorted(set(published) - set(expected))
156
+ mismatched = sorted(
157
+ name for name, digest in expected.items()
158
+ if published.get(name) != digest
159
+ )
160
+ if missing or unexpected or mismatched:
161
+ raise SystemExit(
162
+ "TestPyPI verification failed; "
163
+ f"missing={missing}, unexpected={unexpected}, mismatched={mismatched}"
164
+ )
165
+ print(f"Verified {len(expected)} TestPyPI distribution hashes for marsh-lib {version}")
166
+ PY
167
+
168
+ - name: Install rehearsal package from TestPyPI
169
+ run: |
170
+ uv run --isolated --no-project --with "marsh-lib==0.3.0" \
171
+ --index-url https://test.pypi.org/simple/ \
172
+ --extra-index-url https://pypi.org/simple/ \
173
+ python -c "import marsh; print(marsh.__file__)"
174
+
175
+ - name: Rehearsal summary
176
+ run: |
177
+ echo "Non-production release rehearsal completed."
178
+ echo "Production PyPI was not contacted."
179
+ echo "The exact artifact set was built once, published to TestPyPI, hash-verified, installed, and recovery verification was exercised."
@@ -193,8 +193,9 @@ jobs:
193
193
 
194
194
  expected = {
195
195
  path.name: hashlib.sha256(path.read_bytes()).hexdigest()
196
- for path in Path("dist").glob("*")
197
- if path.is_file() and not path.name.endswith(".attestation")
196
+ for pattern in ("*.whl", "*.tar.gz")
197
+ for path in Path("dist").glob(pattern)
198
+ if path.is_file()
198
199
  }
199
200
  published = {
200
201
  item["filename"]: item["digests"]["sha256"]
@@ -202,13 +203,15 @@ jobs:
202
203
  }
203
204
 
204
205
  missing = sorted(set(expected) - set(published))
206
+ unexpected = sorted(set(published) - set(expected))
205
207
  mismatched = sorted(
206
208
  name for name, digest in expected.items()
207
209
  if published.get(name) != digest
208
210
  )
209
- if missing or mismatched:
211
+ if missing or unexpected or mismatched:
210
212
  raise SystemExit(
211
- f"PyPI verification failed; missing={missing}, mismatched={mismatched}"
213
+ "PyPI verification failed; "
214
+ f"missing={missing}, unexpected={unexpected}, mismatched={mismatched}"
212
215
  )
213
- print(f"Verified {len(expected)} published artifact hashes for marsh-lib {version}")
216
+ print(f"Verified {len(expected)} published distribution hashes for marsh-lib {version}")
214
217
  PY
@@ -0,0 +1,59 @@
1
+ name: SSH Integration Smoke
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - develop
7
+ paths:
8
+ - "src/marsh/ssh/**"
9
+ - "tests/ssh/**"
10
+ - "pyproject.toml"
11
+ - "uv.lock"
12
+ - ".github/workflows/ssh-integration.yml"
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ ssh-integration:
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: 10
21
+ steps:
22
+ - name: Checkout
23
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
27
+ with:
28
+ version: "0.12.12"
29
+ enable-cache: false
30
+
31
+ - name: Install dependencies
32
+ run: uv sync --all-groups
33
+
34
+ - name: Configure local SSH server
35
+ run: |
36
+ sudo apt-get update -qq
37
+ sudo apt-get install -y openssh-server
38
+ sudo useradd --create-home --shell /bin/bash marsh-ci
39
+ sudo install -d -m 700 -o marsh-ci -g marsh-ci /home/marsh-ci/.ssh
40
+ echo "marsh-ci:marsh-password" | sudo chpasswd
41
+ sudo mkdir -p /run/sshd
42
+ cat > "$RUNNER_TEMP/sshd_config" <<EOF
43
+ Port 2222
44
+ ListenAddress 127.0.0.1
45
+ HostKey /etc/ssh/ssh_host_ed25519_key
46
+ PubkeyAuthentication yes
47
+ PasswordAuthentication yes
48
+ PermitRootLogin no
49
+ UsePAM no
50
+ PidFile $RUNNER_TEMP/sshd.pid
51
+ AllowUsers marsh-ci
52
+ EOF
53
+ sudo /usr/sbin/sshd -f "$RUNNER_TEMP/sshd_config"
54
+
55
+ - name: Run SSH smoke test
56
+ env:
57
+ MARSH_SSH_TARGET: marsh-ci@127.0.0.1:2222
58
+ MARSH_SSH_PASSWORD: marsh-password
59
+ run: uv run pytest -s -vv --disable-warnings --tb=short tests/ssh/test_local_sshd_smoke.py
@@ -4,7 +4,7 @@ on:
4
4
  push:
5
5
  branches: ["*"]
6
6
  pull_request:
7
- branches: ["main"]
7
+ branches: ["develop", "main"]
8
8
  workflow_dispatch:
9
9
 
10
10
  permissions:
@@ -16,7 +16,7 @@ jobs:
16
16
  runs-on: ubuntu-latest
17
17
  steps:
18
18
  - name: Checkout code
19
- uses: actions/checkout@v2
19
+ uses: actions/checkout@v4
20
20
 
21
21
  - name: Install uv
22
22
  uses: astral-sh/setup-uv@v10.2.0
@@ -27,7 +27,6 @@ jobs:
27
27
  run: |
28
28
  uv sync --all-groups
29
29
 
30
- # TODO: Setup Docker for testing docker components (and possible ssh components).
31
30
  - name: Run pytest
32
31
  run: |
33
32
  cd src
@@ -45,3 +44,26 @@ jobs:
45
44
  with:
46
45
  coverageFile: coverage.xml
47
46
  token: ${{ secrets.GITHUB_TOKEN }}
47
+
48
+ windows_portability:
49
+ name: Windows portability (Python 3.12)
50
+ runs-on: windows-latest
51
+ steps:
52
+ - name: Checkout code
53
+ uses: actions/checkout@v4
54
+
55
+ - name: Install uv
56
+ uses: astral-sh/setup-uv@v10.2.0
57
+ with:
58
+ python-version: '3.12'
59
+
60
+ - name: Install dependencies
61
+ run: uv sync --all-groups
62
+
63
+ - name: Run portable pytest suite
64
+ run: >
65
+ uv run pytest -vv --disable-warnings --tb=short
66
+ --ignore=tests/bash
67
+ --ignore=tests/docker
68
+ --ignore=tests/ssh
69
+ tests/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: marsh-lib
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Lightweight, extensible Python library for building, managing, and executing command workflows.
5
5
  Project-URL: Repository, https://github.com/CedricAnover/marsh
6
6
  Author-email: Cedric Anover <cedric.anover@hotmail.com>
@@ -609,6 +609,8 @@ remain important compatibility surfaces.
609
609
 
610
610
  New functionality should prefer the canonical workflow contracts and adapt existing mechanisms into them rather than introducing a separate execution model.
611
611
 
612
+ For migration examples and legacy/deprecation guidance, see [`docs/api-migration.md`](docs/api-migration.md).
613
+
612
614
  ## Contributing
613
615
 
614
616
  Before making architectural or public-interface changes:
@@ -582,6 +582,8 @@ remain important compatibility surfaces.
582
582
 
583
583
  New functionality should prefer the canonical workflow contracts and adapt existing mechanisms into them rather than introducing a separate execution model.
584
584
 
585
+ For migration examples and legacy/deprecation guidance, see [`docs/api-migration.md`](docs/api-migration.md).
586
+
585
587
  ## Contributing
586
588
 
587
589
  Before making architectural or public-interface changes: