opsscript-gate 0.2.0__tar.gz → 0.2.1__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.
Potentially problematic release.
This version of opsscript-gate might be problematic. Click here for more details.
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/CHANGELOG.md +9 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/PKG-INFO +10 -2
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/README.md +9 -1
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/pyproject.toml +1 -1
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/src/opsscript_gate/__init__.py +1 -1
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/src/opsscript_gate/runner.py +17 -3
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/tests/test_runner.py +57 -7
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/.gitattributes +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/.github/ISSUE_TEMPLATE/distro_request.yml +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/.github/assets/social-preview.png +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/.github/pull_request_template.md +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/.github/workflows/demo.yml +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/.github/workflows/release.yml +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/.github/workflows/test.yml +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/.gitignore +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/CONTRIBUTING.md +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/LICENSE +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/ROADMAP.md +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/SECURITY.md +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/action.yml +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/examples/alpine-incompatibility/README.md +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/examples/alpine-incompatibility/apt_dependency.sh +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/examples/basic/README.md +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/examples/basic/clean_setup.sh +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/examples/github-actions/README.md +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/examples/github-actions/workflow.yml +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/examples/interactive-hang/README.md +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/examples/interactive-hang/prompt_confirm.sh +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/src/opsscript_gate/cli.py +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/src/opsscript_gate/models.py +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/src/opsscript_gate/reporter.py +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/tests/fixtures/fail_deps.sh +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/tests/fixtures/fail_hang.sh +0 -0
- {opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/tests/fixtures/pass_basic.sh +0 -0
|
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.1] - 2026-09-19
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Strip complete ANSI CSI and OSC escape sequences from diagnostic text instead of leaving escape-sequence residue.
|
|
12
|
+
- Ensure diagnostic `max_length` includes the trailing ellipsis when truncation occurs.
|
|
13
|
+
- Add regression coverage for ANSI sanitization and truncation edge cases.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
8
17
|
## [0.2.0] - 2026-09-18
|
|
9
18
|
|
|
10
19
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: opsscript-gate
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Run Linux shell scripts across Debian, Ubuntu and Alpine before release to catch runtime compatibility failures
|
|
5
5
|
Project-URL: Homepage, https://github.com/Mresyzz/opsscript-gate
|
|
6
6
|
Project-URL: Repository, https://github.com/Mresyzz/opsscript-gate
|
|
@@ -55,7 +55,7 @@ Add one step to your pull request workflow (`.github/workflows/gate.yml`):
|
|
|
55
55
|
|
|
56
56
|
```yaml
|
|
57
57
|
- name: Verify Shell Script Portability
|
|
58
|
-
uses: Mresyzz/opsscript-gate@v0.2.
|
|
58
|
+
uses: Mresyzz/opsscript-gate@v0.2.1
|
|
59
59
|
with:
|
|
60
60
|
script-path: scripts/setup.sh
|
|
61
61
|
# shell: posix # optional: 'posix' (default), 'shebang', or 'auto'
|
|
@@ -217,6 +217,14 @@ Check out the [examples/](examples/) directory for self-contained, runnable scen
|
|
|
217
217
|
|
|
218
218
|
---
|
|
219
219
|
|
|
220
|
+
## Dogfooding
|
|
221
|
+
|
|
222
|
+
OpsScript Gate is used in [`Mresyzz/linux-dev-bootstrap`](https://github.com/Mresyzz/linux-dev-bootstrap) to validate its `install.sh` script across the default Debian, Ubuntu, and Alpine matrix in GitHub Actions. That workflow pins `Mresyzz/opsscript-gate@v0.2.0` with `shell: auto`.
|
|
223
|
+
|
|
224
|
+
See the downstream workflow: [`.github/workflows/test.yml`](https://github.com/Mresyzz/linux-dev-bootstrap/blob/main/.github/workflows/test.yml).
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
220
228
|
## Development & Testing
|
|
221
229
|
|
|
222
230
|
The test suite uses Docker SDK mocking to ensure fast unit tests without needing a local daemon:
|
|
@@ -27,7 +27,7 @@ Add one step to your pull request workflow (`.github/workflows/gate.yml`):
|
|
|
27
27
|
|
|
28
28
|
```yaml
|
|
29
29
|
- name: Verify Shell Script Portability
|
|
30
|
-
uses: Mresyzz/opsscript-gate@v0.2.
|
|
30
|
+
uses: Mresyzz/opsscript-gate@v0.2.1
|
|
31
31
|
with:
|
|
32
32
|
script-path: scripts/setup.sh
|
|
33
33
|
# shell: posix # optional: 'posix' (default), 'shebang', or 'auto'
|
|
@@ -189,6 +189,14 @@ Check out the [examples/](examples/) directory for self-contained, runnable scen
|
|
|
189
189
|
|
|
190
190
|
---
|
|
191
191
|
|
|
192
|
+
## Dogfooding
|
|
193
|
+
|
|
194
|
+
OpsScript Gate is used in [`Mresyzz/linux-dev-bootstrap`](https://github.com/Mresyzz/linux-dev-bootstrap) to validate its `install.sh` script across the default Debian, Ubuntu, and Alpine matrix in GitHub Actions. That workflow pins `Mresyzz/opsscript-gate@v0.2.0` with `shell: auto`.
|
|
195
|
+
|
|
196
|
+
See the downstream workflow: [`.github/workflows/test.yml`](https://github.com/Mresyzz/linux-dev-bootstrap/blob/main/.github/workflows/test.yml).
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
192
200
|
## Development & Testing
|
|
193
201
|
|
|
194
202
|
The test suite uses Docker SDK mocking to ensure fast unit tests without needing a local daemon:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "opsscript-gate"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Run Linux shell scripts across Debian, Ubuntu and Alpine before release to catch runtime compatibility failures"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import re
|
|
4
5
|
import tempfile
|
|
5
6
|
import time
|
|
6
7
|
from typing import Sequence
|
|
@@ -35,21 +36,34 @@ SUPPORTED_SHEBANG_COMMANDS: dict[str, list[str]] = {
|
|
|
35
36
|
|
|
36
37
|
DEFAULT_POSIX_COMMAND: list[str] = ["/bin/sh", "-c", "/bin/sh /tmp/target_script.sh </dev/null"]
|
|
37
38
|
|
|
39
|
+
# Regular expression matching common ANSI escape sequences:
|
|
40
|
+
# 1. CSI (Control Sequence Introducer): ESC [ ... [@-~]
|
|
41
|
+
# 2. OSC (Operating System Command): ESC ] ... (BEL | ST)
|
|
42
|
+
_ANSI_ESCAPE_RE = re.compile(
|
|
43
|
+
r"\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\))"
|
|
44
|
+
)
|
|
45
|
+
|
|
38
46
|
|
|
39
47
|
def sanitize_diagnostic_text(text: str, max_length: int = 200) -> str:
|
|
40
48
|
"""
|
|
41
49
|
Sanitize raw shebang text for safe display in diagnostics and error messages:
|
|
50
|
+
- Strips complete ANSI escape sequences (CSI, OSC)
|
|
42
51
|
- Removes control characters and embedded CR/LF
|
|
43
|
-
-
|
|
52
|
+
- Collapses multiple consecutive whitespace
|
|
53
|
+
- Ensures maximum final length <= max_length (including ellipsis if truncated)
|
|
44
54
|
"""
|
|
45
|
-
if not text:
|
|
55
|
+
if not text or max_length <= 0:
|
|
46
56
|
return ""
|
|
57
|
+
# Strip complete ANSI escape sequences first
|
|
58
|
+
text = _ANSI_ESCAPE_RE.sub("", text)
|
|
47
59
|
# Filter out control characters (ASCII < 32 and 127)
|
|
48
60
|
sanitized = "".join(ch if (32 <= ord(ch) < 127 or ord(ch) >= 160) else " " for ch in text)
|
|
49
61
|
# Collapse multiple consecutive whitespace
|
|
50
62
|
sanitized = " ".join(sanitized.split())
|
|
51
63
|
if len(sanitized) > max_length:
|
|
52
|
-
|
|
64
|
+
if max_length < 3:
|
|
65
|
+
return sanitized[:max_length]
|
|
66
|
+
return sanitized[: max_length - 3] + "..."
|
|
53
67
|
return sanitized
|
|
54
68
|
|
|
55
69
|
|
|
@@ -368,7 +368,7 @@ def test_cli_version(capsys):
|
|
|
368
368
|
main(["--version"])
|
|
369
369
|
assert excinfo.value.code == 0
|
|
370
370
|
captured = capsys.readouterr()
|
|
371
|
-
assert "0.2.
|
|
371
|
+
assert "0.2.1" in captured.out
|
|
372
372
|
|
|
373
373
|
|
|
374
374
|
def test_cli_format_markdown_and_table(tmp_path, capsys):
|
|
@@ -681,21 +681,71 @@ def test_run_matrix_parses_shebang_once(tmp_path):
|
|
|
681
681
|
def test_sanitize_diagnostic_text():
|
|
682
682
|
# Empty string
|
|
683
683
|
assert sanitize_diagnostic_text("") == ""
|
|
684
|
-
|
|
684
|
+
|
|
685
|
+
# Control characters, embedded CR/LF, ANSI escape, BEL
|
|
685
686
|
raw = "#!/bin/sh\r\n\x00\x1b[31mecho evil\x07"
|
|
686
687
|
cleaned = sanitize_diagnostic_text(raw)
|
|
688
|
+
# ESC byte is absent
|
|
689
|
+
assert "\x1b" not in cleaned
|
|
690
|
+
# ANSI parameter residue such as "[31m" is also absent
|
|
691
|
+
assert "[31m" not in cleaned
|
|
687
692
|
assert "\r" not in cleaned
|
|
688
693
|
assert "\n" not in cleaned
|
|
689
694
|
assert "\x00" not in cleaned
|
|
690
|
-
assert "\x1b" not in cleaned
|
|
691
695
|
assert "\x07" not in cleaned
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
#
|
|
696
|
+
assert cleaned == "#!/bin/sh echo evil"
|
|
697
|
+
|
|
698
|
+
# Extended CSI sequence (e.g. 256-color) is removed as a complete sequence
|
|
699
|
+
extended_csi = "prefix \x1b[38;5;196mcolor\x1b[0m suffix"
|
|
700
|
+
cleaned_csi = sanitize_diagnostic_text(extended_csi)
|
|
701
|
+
assert "\x1b" not in cleaned_csi
|
|
702
|
+
assert "[38;5;196m" not in cleaned_csi
|
|
703
|
+
assert "[0m" not in cleaned_csi
|
|
704
|
+
assert cleaned_csi == "prefix color suffix"
|
|
705
|
+
|
|
706
|
+
# Additional CSI sequences (\x1b[1;31m, \x1b[?25h, etc.)
|
|
707
|
+
csi_variations = "\x1b[1;31mboldred\x1b[0m \x1b[?25hcursor"
|
|
708
|
+
cleaned_variations = sanitize_diagnostic_text(csi_variations)
|
|
709
|
+
assert "[1;31m" not in cleaned_variations
|
|
710
|
+
assert "[?25h" not in cleaned_variations
|
|
711
|
+
assert cleaned_variations == "boldred cursor"
|
|
712
|
+
|
|
713
|
+
# OSC title sequences are removed cleanly
|
|
714
|
+
osc_bel = "\x1b]0;terminal title\x07echo hello"
|
|
715
|
+
cleaned_osc_bel = sanitize_diagnostic_text(osc_bel)
|
|
716
|
+
assert "terminal title" not in cleaned_osc_bel
|
|
717
|
+
assert cleaned_osc_bel == "echo hello"
|
|
718
|
+
|
|
719
|
+
osc_st = "\x1b]0;terminal title\x1b\\echo world"
|
|
720
|
+
cleaned_osc_st = sanitize_diagnostic_text(osc_st)
|
|
721
|
+
assert "terminal title" not in cleaned_osc_st
|
|
722
|
+
assert cleaned_osc_st == "echo world"
|
|
723
|
+
|
|
724
|
+
# Ordinary printable text and Unicode are preserved
|
|
725
|
+
normal_text = "echo 'Hello world! 12345 äöü'"
|
|
726
|
+
cleaned_normal = sanitize_diagnostic_text(normal_text)
|
|
727
|
+
assert cleaned_normal == normal_text
|
|
728
|
+
|
|
729
|
+
# Repeated whitespace is collapsed
|
|
730
|
+
whitespace_text = " a b \t c \n\r d "
|
|
731
|
+
assert sanitize_diagnostic_text(whitespace_text) == "a b c d"
|
|
732
|
+
|
|
733
|
+
# max_length=200 returns len(result) <= 200, ends in "..."
|
|
695
734
|
long_text = "#!" + "a" * 250
|
|
696
735
|
truncated = sanitize_diagnostic_text(long_text, max_length=200)
|
|
697
|
-
assert len(truncated)
|
|
736
|
+
assert len(truncated) <= 200
|
|
737
|
+
assert len(truncated) == 200
|
|
698
738
|
assert truncated.endswith("...")
|
|
739
|
+
assert truncated == "#!" + "a" * 195 + "..."
|
|
740
|
+
|
|
741
|
+
# Small/zero max_length values do not crash and remain defensively sized
|
|
742
|
+
assert sanitize_diagnostic_text("hello world", max_length=0) == ""
|
|
743
|
+
assert sanitize_diagnostic_text("hello world", max_length=-5) == ""
|
|
744
|
+
assert sanitize_diagnostic_text("hello world", max_length=1) == "h"
|
|
745
|
+
assert sanitize_diagnostic_text("hello world", max_length=2) == "he"
|
|
746
|
+
assert sanitize_diagnostic_text("hello world", max_length=3) == "..."
|
|
747
|
+
assert sanitize_diagnostic_text("hello world", max_length=4) == "h..."
|
|
748
|
+
assert len(sanitize_diagnostic_text("hello world", max_length=5)) <= 5
|
|
699
749
|
|
|
700
750
|
|
|
701
751
|
def test_missing_interpreter_compatibility_failure(tmp_path):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{opsscript_gate-0.2.0 → opsscript_gate-0.2.1}/examples/alpine-incompatibility/apt_dependency.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|