Merge pull request #26 from baskduf/fix/release-blocking-findings #67
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| quality: | |
| name: quality / python ${{ matrix.python-version }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.9", "3.10", "3.11", "3.12"] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| - name: Install test tools | |
| run: python -m pip install --upgrade pip pytest build pyyaml | |
| - name: Compile Python files | |
| run: python -m py_compile skills/artic/scripts/*.py tests/*.py scripts/*.py | |
| - name: Run package tests | |
| run: python -m pytest -q | |
| - name: Smoke scaffold Artic docs | |
| run: python skills/artic/scripts/scaffold_artic_files.py --root /tmp/artic-smoke | |
| - name: Validate scaffolded Artic docs | |
| run: python skills/artic/scripts/validate_artic_outputs.py --root /tmp/artic-smoke | |
| - name: Lint scaffolded DESIGN.md without warnings | |
| run: | | |
| set -euo pipefail | |
| npx -y @google/design.md lint /tmp/artic-smoke/DESIGN.md | tee /tmp/artic-design-lint.json | |
| python - <<'PY' | |
| import json, pathlib | |
| data = json.loads(pathlib.Path('/tmp/artic-design-lint.json').read_text()) | |
| summary = data.get('summary', {}) | |
| assert summary.get('errors', 0) == 0, data | |
| assert summary.get('warnings', 0) == 0, data | |
| PY | |
| - name: Build Python package metadata | |
| run: python -m build --sdist --wheel | |
| - name: Verify package archives are clean | |
| run: | | |
| python scripts/check_release_artifacts.py --require-payload dist/artic-*.tar.gz | |
| python scripts/check_release_artifacts.py dist/artic-*.whl | |
| - name: Verify built package metadata | |
| run: | | |
| python - <<'PY' | |
| import email.parser, pathlib, re, tarfile, zipfile | |
| expected_name = 'artic' | |
| match = re.search(r'^version = "([^"]+)"', pathlib.Path('pyproject.toml').read_text(), re.MULTILINE) | |
| assert match, 'pyproject.toml missing version' | |
| expected_version = match.group(1) | |
| wheels = list(pathlib.Path('dist').glob('*.whl')) | |
| sdists = list(pathlib.Path('dist').glob('*.tar.gz')) | |
| assert wheels, 'missing wheel artifact' | |
| assert sdists, 'missing sdist artifact' | |
| with zipfile.ZipFile(wheels[0]) as zf: | |
| metadata_name = next(n for n in zf.namelist() if n.endswith('.dist-info/METADATA')) | |
| meta = email.parser.Parser().parsestr(zf.read(metadata_name).decode()) | |
| assert meta['Name'] == expected_name, meta['Name'] | |
| assert meta['Version'] == expected_version, meta['Version'] | |
| with tarfile.open(sdists[0]) as tf: | |
| names = tf.getnames() | |
| assert any(name.endswith('README.md') for name in names), 'sdist missing README.md' | |
| assert any(name.endswith('skills/artic/SKILL.md') for name in names), 'sdist missing Artic skill payload' | |
| assert any(name.endswith('plugins/claude-artic/.claude-plugin/plugin.json') for name in names), 'sdist missing Claude plugin manifest' | |
| assert any(name.endswith('plugins/codex-artic/.codex-plugin/plugin.json') for name in names), 'sdist missing Codex plugin manifest' | |
| PY | |
| repository-integrity: | |
| name: repository integrity | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Lint GitHub Actions workflows | |
| run: | | |
| set -euo pipefail | |
| curl -sSL https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz -o /tmp/actionlint.tar.gz | |
| tar -xzf /tmp/actionlint.tar.gz -C /tmp actionlint | |
| /tmp/actionlint | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - name: Install tools | |
| run: python -m pip install --upgrade pip pytest pyyaml | |
| - name: Validate workflow YAML parses | |
| run: | | |
| python - <<'PY' | |
| import pathlib, yaml | |
| for path in pathlib.Path('.github/workflows').glob('*.yml'): | |
| with path.open() as f: | |
| yaml.safe_load(f) | |
| PY | |
| - name: Run integrity tests | |
| run: python -m pytest -q tests/test_artic_package.py | |
| - name: Check for common committed secrets | |
| run: | | |
| python - <<'PY' | |
| import pathlib, re, sys | |
| patterns = [ | |
| re.compile(r'(?i)(api[_-]?key|secret|password|passwd|token)\s*[:=]\s*["\'][^"\']{8,}["\']'), | |
| re.compile(r'gho_[A-Za-z0-9_]{20,}'), | |
| re.compile(r'github_pat_[A-Za-z0-9_]{20,}'), | |
| re.compile(r'sk-[A-Za-z0-9]{20,}'), | |
| ] | |
| skip_parts = {'.git', '.pytest_cache', '__pycache__', 'dist', 'build'} | |
| findings = [] | |
| for path in pathlib.Path('.').rglob('*'): | |
| if not path.is_file() or skip_parts.intersection(path.parts): | |
| continue | |
| try: | |
| text = path.read_text(encoding='utf-8') | |
| except UnicodeDecodeError: | |
| continue | |
| for line_no, line in enumerate(text.splitlines(), 1): | |
| if any(p.search(line) for p in patterns): | |
| findings.append(f'{path}:{line_no}: possible secret pattern') | |
| if findings: | |
| print('\n'.join(findings)) | |
| sys.exit(1) | |
| PY | |
| release-dry-run: | |
| name: release dry-run | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: [quality, repository-integrity] | |
| if: github.event_name != 'pull_request' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - name: Install build tools | |
| run: python -m pip install --upgrade pip build | |
| - name: Build release archives | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python - <<'PY' | |
| import pathlib, re | |
| match = re.search(r'^version = "([^"]+)"', pathlib.Path('pyproject.toml').read_text(), re.MULTILINE) | |
| assert match, 'pyproject.toml missing version' | |
| print(match.group(1)) | |
| PY | |
| ) | |
| RELEASE_TAG="v${VERSION}" | |
| python -m build --sdist --wheel | |
| python scripts/build_skill_archive.py --output "dist/artic-skill-${RELEASE_TAG}.tar.gz" | |
| expected=( | |
| "artic-${VERSION}.tar.gz" | |
| "artic-${VERSION}-py3-none-any.whl" | |
| "artic-skill-${RELEASE_TAG}.tar.gz" | |
| ) | |
| for file in "${expected[@]}"; do | |
| test -s "dist/$file" | |
| done | |
| (cd dist && shasum -a 256 "${expected[@]}" > SHA256SUMS.txt) | |
| python scripts/check_release_artifacts.py --require-payload \ | |
| "dist/artic-${VERSION}.tar.gz" \ | |
| "dist/artic-skill-${RELEASE_TAG}.tar.gz" | |
| python scripts/check_release_artifacts.py "dist/artic-${VERSION}-py3-none-any.whl" | |
| - name: Upload dry-run artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: artic-release-dry-run | |
| path: | | |
| dist/* | |
| if-no-files-found: error |