Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions .github/workflows/generate-autocomplete.yml
Original file line number Diff line number Diff line change
@@ -1,44 +1,74 @@
name: Generate Autocomplete Artifact

# artifacts/autocomplete-types.ts is generated from the SDK and stamped with the
# version in pyproject.toml. It is committed to the repo so consumers can fetch it
# at a tagged ref, which means it must be correct *at the commit a tag points at* -
# a fixup pushed afterwards can never reach an already-cut tag.
#
# So `verify` blocks any PR whose committed artifact is stale (including a version
# bump that forgot to regenerate). `generate` stays on as a backstop for direct
# pushes to main; once verify is enforced it should find nothing to do.

on:
push:
branches: [main]
release:
types: [published]
pull_request:
branches: [main, dev]
workflow_dispatch:

jobs:
# Fails the PR if the committed artifact does not match the current SDK, forcing
# the regenerated file into the same commit rather than a follow-up push.
verify:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Verify committed artifact is up to date
run: |
python scripts/generate-autocomplete-artifact.py \
--output-path ./artifacts/autocomplete-types.ts --check

generate:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest

permissions:
contents: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_TOKEN }}
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Upgrade pip
run: python -m pip install --upgrade pip

- name: Generate autocomplete artifacts
run: |
python scripts/generate-autocomplete-artifact.py --output-path ./artifacts/autocomplete-types.ts

- name: Check for changes
id: changes
run: |
git diff --exit-code artifacts/ || echo "changed=true" >> $GITHUB_OUTPUT
continue-on-error: true

- name: Commit artifact to repo
if: steps.changes.outputs.changed == 'true'
run: |
Expand All @@ -47,4 +77,3 @@ jobs:
git add artifacts/autocomplete-types.ts
git commit -m "πŸ€– Auto-generate autocomplete types [skip ci]" || exit 0
git push

8 changes: 8 additions & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ jobs:
with:
python-version: "3.14"

# The tagged commit is what consumers pin to, and tags are immutable - a
# stale artifact here ships a version stamp that disagrees with the release
# and can never be corrected in place. Fail before anything is published.
- name: Verify committed autocomplete artifact matches this release
run: |
python scripts/generate-autocomplete-artifact.py \
--output-path ./artifacts/autocomplete-types.ts --check

- name: Build release distributions
run: |
uv build
Expand Down
4 changes: 2 additions & 2 deletions artifacts/autocomplete-types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// This file is auto-generated by the datamaker-py SDK
// DO NOT EDIT MANUALLY
// Generated from version 0.8.0
// Generated from version 0.8.1

// Monaco Editor CompletionItemKind enum values
// Reference: https://microsoft.github.io/monaco-editor/api/enums/monaco.languages.CompletionItemKind.html
Expand Down Expand Up @@ -49,7 +49,7 @@ export interface DataMakerFieldType {
documentation?: string;
}

export const SDK_VERSION = "0.8.0";
export const SDK_VERSION = "0.8.1";

export const METHOD_SUGGESTIONS: DataMakerMethod[] = [
{
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "datamaker-py"
version = "0.8.0"
version = "0.8.1"
description = "The official Python library for the Automators DataMaker API."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
51 changes: 46 additions & 5 deletions scripts/generate-autocomplete-artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import ast
import sys
from difflib import unified_diff
from pathlib import Path
from typing import Dict, List, Any
import importlib.util
Expand Down Expand Up @@ -307,9 +308,9 @@ def _escape_quotes(self, text: str) -> str:
return text.replace('"', '\\"').replace('\n', ' ').strip()


def get_version() -> str:
def get_version(repo_root: Path = None) -> str:
"""Get the SDK version from pyproject.toml."""
pyproject = Path("pyproject.toml")
pyproject = (repo_root / "pyproject.toml") if repo_root else Path("pyproject.toml")
if pyproject.exists():
with open(pyproject, "r", encoding="utf-8") as f:
for line in f:
Expand All @@ -330,15 +331,27 @@ def main():
default="./artifacts/autocomplete-types.ts",
help="Output path for the generated TypeScript file",
)
parser.add_argument(
"--check",
action="store_true",
help=(
"Verify the committed artifact matches what this script generates, "
"without writing. Exits 1 if it is stale."
),
)
args = parser.parse_args()

# Find the repository root
repo_root = Path(__file__).parent.parent

# Get version
version = get_version()
version = get_version(repo_root)
print(f"πŸ“¦ SDK Version: {version}")

if version == "unknown":
print("❌ Could not read version from pyproject.toml")
return 1

# Analyze SDK
analyzer = SDKAnalyzer(repo_root)
analyzer.analyze()
Expand All @@ -348,8 +361,35 @@ def main():
generator = TypeScriptGenerator(analyzer, version)
ts_code = generator.generate()

# Write output
output_path = Path(args.output_path)

# Verify-only: the committed artifact must already match what we just built.
# Guards the release path, where a stale artifact would be tagged with a
# version stamp that disagrees with pyproject.toml.
if args.check:
if not output_path.exists():
print(f"❌ Missing artifact: {output_path}")
return 1
committed = output_path.read_text(encoding="utf-8")
if committed != ts_code:
print(f"❌ Stale artifact: {output_path}")
print(" It does not match the current SDK. Regenerate and commit:")
print(
" python scripts/generate-autocomplete-artifact.py "
f"--output-path {output_path}"
)
diff = unified_diff(
committed.splitlines(keepends=True),
ts_code.splitlines(keepends=True),
fromfile=f"{output_path} (committed)",
tofile=f"{output_path} (regenerated)",
)
sys.stdout.writelines(diff)
return 1
print(f"βœ… Artifact is up to date: {output_path}")
return 0

# Write output
output_path.parent.mkdir(parents=True, exist_ok=True)

with open(output_path, "w", encoding="utf-8") as f:
Expand All @@ -358,8 +398,9 @@ def main():
print(f"βœ… Generated: {output_path}")
print(f" Methods: {len(analyzer.methods)}")
print(f" Field types: {len(analyzer.field_types)}")
return 0


if __name__ == "__main__":
main()
sys.exit(main())

Loading