diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index 8f455fb..8c706ac 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -6,8 +6,14 @@ on: jobs: preview: runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + pull-requests: write steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: '3.x' @@ -16,56 +22,30 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . - - - name: Set preview version - run: | - BASE_VERSION=$(python -c "from socketsecurity import __version__; print(__version__)") - PREVIEW_VERSION="${BASE_VERSION}.dev${{ github.event.pull_request.number }}${{ github.event.pull_request.commits }}" - echo "VERSION=${PREVIEW_VERSION}" >> $GITHUB_ENV + pip install hatchling==1.27.0 hatch==1.14.0 - # Update version in __init__.py - echo "__version__ = \"${PREVIEW_VERSION}\"" > socketsecurity/__init__.py.tmp - cat socketsecurity/__init__.py | grep -v "__version__" >> socketsecurity/__init__.py.tmp - mv socketsecurity/__init__.py.tmp socketsecurity/__init__.py + - name: Inject full dynamic version + run: python .hooks/sync_version.py --dev - # Verify the change - echo "Updated version in __init__.py:" - python -c "from socketsecurity import __version__; print(__version__)" + - name: Clean previous builds + run: rm -rf dist/ build/ *.egg-info - - name: Check if version exists on Test PyPI - id: version_check - env: - VERSION: ${{ env.VERSION }} + - name: Get Hatch version + id: version run: | - if curl -s -f https://test.pypi.org/pypi/socketsecurity/$VERSION/json > /dev/null; then - echo "Version ${VERSION} already exists on Test PyPI" - echo "exists=true" >> $GITHUB_OUTPUT - else - echo "Version ${VERSION} not found on Test PyPI" - echo "exists=false" >> $GITHUB_OUTPUT - fi + VERSION=$(hatch version | cut -d+ -f1) + echo "VERSION=$VERSION" >> $GITHUB_ENV - name: Build package if: steps.version_check.outputs.exists != 'true' run: | - pip install build - python -m build - - - name: Restore original version - if: always() - run: | - BASE_VERSION=$(echo $VERSION | cut -d'.' -f1-3) - echo "__version__ = \"${BASE_VERSION}\"" > socketsecurity/__init__.py.tmp - cat socketsecurity/__init__.py | grep -v "__version__" >> socketsecurity/__init__.py.tmp - mv socketsecurity/__init__.py.tmp socketsecurity/__init__.py + hatch build - name: Publish to Test PyPI if: steps.version_check.outputs.exists != 'true' - uses: pypa/gh-action-pypi-publish@v1.8.11 + uses: pypa/gh-action-pypi-publish@v1.12.4 with: repository-url: https://test.pypi.org/legacy/ - password: ${{ secrets.TEST_PYPI_TOKEN }} verbose: true - name: Comment on PR diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index beb6cc9..0a5d0c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,18 @@ name: Release on: - push: - tags: - - 'v*' + release: + types: [published] jobs: release: runs-on: ubuntu-latest + permissions: + id-token: write + contents: read steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: '3.x' @@ -17,15 +21,15 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . + pip install hatchling==1.27.0 hatch==1.14.0 - name: Get Version id: version run: | - RAW_VERSION=$(python -c "from socketsecurity import __version__; print(__version__)") + RAW_VERSION=$(hatch version) echo "VERSION=$RAW_VERSION" >> $GITHUB_ENV if [ "v$RAW_VERSION" != "${{ github.ref_name }}" ]; then - echo "Error: Git tag (${{ github.ref_name }}) does not match package version (v$RAW_VERSION)" + echo "Error: Git tag (${{ github.ref_name }}) does not match hatch version (v$RAW_VERSION)" exit 1 fi @@ -57,14 +61,12 @@ jobs: - name: Build package if: steps.version_check.outputs.pypi_exists != 'true' run: | - pip install build - python -m build + pip install hatchling + hatch build - name: Publish to PyPI if: steps.version_check.outputs.pypi_exists != 'true' - uses: pypa/gh-action-pypi-publish@v1.8.11 - with: - password: ${{ secrets.PYPI_TOKEN }} + uses: pypa/gh-action-pypi-publish@v1.12.4 - name: Login to Docker Hub uses: docker/login-action@v3 diff --git a/.hooks/sync_version.py b/.hooks/sync_version.py new file mode 100644 index 0000000..f26dd76 --- /dev/null +++ b/.hooks/sync_version.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +import subprocess +import pathlib +import re +import sys +import urllib.request +import json + +INIT_FILE = pathlib.Path("socketsecurity/__init__.py") +PYPROJECT_FILE = pathlib.Path("pyproject.toml") + +VERSION_PATTERN = re.compile(r"__version__\s*=\s*['\"]([^'\"]+)['\"]") +PYPROJECT_PATTERN = re.compile(r'^version\s*=\s*".*"$', re.MULTILINE) +PYPI_API = "https://test.pypi.org/pypi/socketsecurity/json" + +def read_version_from_init(path: pathlib.Path) -> str: + content = path.read_text() + match = VERSION_PATTERN.search(content) + if not match: + print(f"❌ Could not find __version__ in {path}") + sys.exit(1) + return match.group(1) + +def read_version_from_git(path: str) -> str: + try: + output = subprocess.check_output(["git", "show", f"HEAD:{path}"], text=True) + match = VERSION_PATTERN.search(output) + if not match: + return None + return match.group(1) + except subprocess.CalledProcessError: + return None + +def bump_patch_version(version: str) -> str: + if ".dev" in version: + version = version.split(".dev")[0] + parts = version.split(".") + parts[-1] = str(int(parts[-1]) + 1) + return ".".join(parts) + +def fetch_existing_versions() -> set: + try: + with urllib.request.urlopen(PYPI_API) as response: + data = json.load(response) + return set(data.get("releases", {}).keys()) + except Exception as e: + print(f"⚠️ Warning: Failed to fetch existing versions from Test PyPI: {e}") + return set() + +def find_next_available_dev_version(base_version: str) -> str: + existing_versions = fetch_existing_versions() + for i in range(1, 100): + candidate = f"{base_version}.dev{i}" + if candidate not in existing_versions: + return candidate + print("❌ Could not find available .devN slot after 100 attempts.") + sys.exit(1) + +def inject_version(version: str): + print(f"🔁 Updating version to: {version}") + + # Update __init__.py + init_content = INIT_FILE.read_text() + new_init_content = VERSION_PATTERN.sub(f"__version__ = '{version}'", init_content) + INIT_FILE.write_text(new_init_content) + + # Update pyproject.toml + pyproject = PYPROJECT_FILE.read_text() + if PYPROJECT_PATTERN.search(pyproject): + new_pyproject = PYPROJECT_PATTERN.sub(f'version = "{version}"', pyproject) + else: + new_pyproject = re.sub(r"(\[project\])", rf"\1\nversion = \"{version}\"", pyproject) + PYPROJECT_FILE.write_text(new_pyproject) + +def main(): + dev_mode = "--dev" in sys.argv + current_version = read_version_from_init(INIT_FILE) + previous_version = read_version_from_git("socketsecurity/__init__.py") + + print(f"Current: {current_version}, Previous: {previous_version}") + + if current_version == previous_version: + if dev_mode: + base_version = current_version.split(".dev")[0] if ".dev" in current_version else current_version + new_version = find_next_available_dev_version(base_version) + inject_version(new_version) + print("⚠️ Version was unchanged — auto-bumped. Please git add + commit again.") + sys.exit(0) + else: + new_version = bump_patch_version(current_version) + inject_version(new_version) + print("⚠️ Version was unchanged — auto-bumped. Please git add + commit again.") + sys.exit(1) + else: + print("✅ Version already bumped — proceeding.") + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..d201e7f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +repos: + - repo: local + hooks: + - id: sync-version + name: Sync __version__ with hatch version + entry: python .hooks/sync_version.py + language: python + always_run: true + pass_filenames: false \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock index b6df5da..77f078c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "702ad05de9bc9de99a4807c8dde1686f31e0041d7b5f6f6b74861195a52110f5" + "sha256": "7e8ad3d0508bf0c279a648ee7a1873fc16334cf0b711f30b2dc54a1da68fef6c" }, "pipfile-spec": 6, "requires": { @@ -10,7 +10,7 @@ "sources": [ { "name": "pypi", - "url": "https://pypi.org/simple", + "url": "https://pypi.org/socketsecurity", "verify_ssl": true } ] diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..e6826fa --- /dev/null +++ b/docs/README.md @@ -0,0 +1,10 @@ +# 1. Clone the repo and create a virtualenv (Python 3.12+) +python3.12 -m venv .venv +source .venv/bin/activate + +# 2. Install dependencies +pip install --upgrade pip +pip install .[dev] + +# 3. Set up pre-commit hooks +pre-commit install diff --git a/pyproject.toml b/pyproject.toml index 6ea62cf..e720e60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,14 @@ [build-system] -requires = ["setuptools >= 61.0"] -build-backend = "setuptools.build_meta" +requires = [ + "hatchling" +] +build-backend = "hatchling.build" [project] name = "socketsecurity" -dynamic = ["version"] -requires-python = ">= 3.9" +version = "2.0.32" +requires-python = ">= 3.10" +license = {"file" = "LICENSE"} dependencies = [ 'requests', 'mdutils', @@ -13,7 +16,7 @@ dependencies = [ 'GitPython', 'packaging', 'python-dotenv', - 'socket-sdk-python>=2.0.9' + 'socket-sdk-python>=2.0.15' ] readme = "README.md" description = "Socket Security CLI for CI/CD" @@ -43,6 +46,8 @@ dev = [ "ruff>=0.3.0", "twine", # for building "pip-tools>=7.4.0", # for pip-compile + "pre-commit", + "hatch" ] [project.scripts] @@ -51,16 +56,6 @@ socketcli = "socketsecurity.socketcli:cli" [project.urls] Homepage = "https://socket.dev" -[tool.setuptools.packages.find] -include = [ - "socketsecurity*" -] - -[tool.setuptools.dynamic] -version = {attr = "socketsecurity.__version__"} - - - [tool.coverage.run] source = ["socketsecurity"] branch = true @@ -163,4 +158,7 @@ docstring-code-format = false # # This only has an effect when the `docstring-code-format` setting is # enabled. -docstring-code-line-length = "dynamic" \ No newline at end of file +docstring-code-line-length = "dynamic" + +[tool.hatch.build.targets.wheel] +include = ["socketsecurity", "LICENSE"] \ No newline at end of file diff --git a/requirements-dev.lock b/requirements-dev.lock index 137e3e6..099e79b 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -9,7 +9,8 @@ # generate-hashes: false # universal: false --e file:. +hatchling==1.27.0 +hatch==1.14.0 argparse==1.4.0 # via socketsecurity certifi==2024.12.14 @@ -60,7 +61,7 @@ requests==2.32.3 # via socketsecurity smmap==5.0.2 # via gitdb -socket-sdk-python @ file:///Users/erichibbs/code/socket/socket-sdk-python +socket-sdk-python==2.0.15 # via socketsecurity typing-extensions==4.12.2 # via socket-sdk-python diff --git a/requirements.lock b/requirements.lock index 137e3e6..6d0be66 100644 --- a/requirements.lock +++ b/requirements.lock @@ -9,7 +9,6 @@ # generate-hashes: false # universal: false --e file:. argparse==1.4.0 # via socketsecurity certifi==2024.12.14 @@ -60,7 +59,7 @@ requests==2.32.3 # via socketsecurity smmap==5.0.2 # via gitdb -socket-sdk-python @ file:///Users/erichibbs/code/socket/socket-sdk-python +socket-sdk-python==2.0.15 # via socketsecurity typing-extensions==4.12.2 # via socket-sdk-python diff --git a/socketsecurity/__init__.py b/socketsecurity/__init__.py index b8c6665..f77c81f 100644 --- a/socketsecurity/__init__.py +++ b/socketsecurity/__init__.py @@ -1,2 +1,3 @@ __author__ = 'socket.dev' -__version__ = '2.0.14' +__version__ = '2.0.32' + diff --git a/socketsecurity/socketcli.py b/socketsecurity/socketcli.py index 5a75438..612ad1e 100644 --- a/socketsecurity/socketcli.py +++ b/socketsecurity/socketcli.py @@ -71,6 +71,7 @@ def main_code(): ) log.debug("loaded socket_config") client = CliClient(socket_config) + sdk.api.api_url = socket_config.api_url log.debug("loaded client") core = Core(socket_config, sdk) log.debug("loaded core")