+1
-1
docs/deployment/database-migrations.md
+1
-1
docs/deployment/database-migrations.md
+1
-1
justfile
+1
-1
justfile
+72
scripts/release
+72
scripts/release
···
···
1
+
#!/usr/bin/env -S uv run --script --quiet
2
+
# /// script
3
+
# requires-python = ">=3.12"
4
+
# dependencies = []
5
+
# ///
6
+
"""create a github release using timestamp-based versioning
7
+
8
+
version format: YYYY.MMDD.HHMMSS (e.g., 2025.1106.134523)
9
+
10
+
usage:
11
+
just release
12
+
./scripts/release
13
+
"""
14
+
15
+
import subprocess
16
+
import sys
17
+
from datetime import UTC, datetime
18
+
19
+
20
+
def main() -> int:
21
+
"""create release with timestamp version."""
22
+
# ensure we're on main branch
23
+
result = subprocess.run(
24
+
["git", "branch", "--show-current"],
25
+
capture_output=True,
26
+
text=True,
27
+
check=True,
28
+
)
29
+
current_branch = result.stdout.strip()
30
+
31
+
if current_branch != "main":
32
+
print(f"❌ must be on main branch (currently on {current_branch})")
33
+
return 1
34
+
35
+
# ensure working directory is clean
36
+
result = subprocess.run(
37
+
["git", "status", "--porcelain"],
38
+
capture_output=True,
39
+
text=True,
40
+
check=True,
41
+
)
42
+
if result.stdout.strip():
43
+
print("❌ working directory has uncommitted changes")
44
+
return 1
45
+
46
+
version = datetime.now(UTC).strftime("%Y.%m%d.%H%M%S")
47
+
48
+
print(f"🚀 creating release {version}")
49
+
print("📝 release notes will be auto-generated by gh cli")
50
+
51
+
# create release - gh will auto-generate notes from commits
52
+
subprocess.run(
53
+
[
54
+
"gh",
55
+
"release",
56
+
"create",
57
+
version,
58
+
"--title",
59
+
version,
60
+
"--generate-notes",
61
+
],
62
+
check=True,
63
+
)
64
+
65
+
print(f"✅ release {version} created")
66
+
print("🚢 production deployment starting automatically...")
67
+
68
+
return 0
69
+
70
+
71
+
if __name__ == "__main__":
72
+
sys.exit(main())
-125
scripts/release.py
-125
scripts/release.py
···
1
-
#!/usr/bin/env python3
2
-
"""create a github release using timestamp-based versioning (nebula strategy).
3
-
4
-
version format: YYYY.MMDD.HHMMSS (e.g., 2025.1106.134523)
5
-
"""
6
-
7
-
import subprocess
8
-
import sys
9
-
from datetime import UTC, datetime
10
-
11
-
12
-
def get_timestamp_version() -> str:
13
-
"""generate version string using nebula's timestamp strategy."""
14
-
now = datetime.now(UTC)
15
-
# format: YYYY.MMDD.HHMMSS
16
-
return now.strftime("%Y.%m%d.%H%M%S")
17
-
18
-
19
-
def get_recent_commits(since_tag: str | None = None) -> list[str]:
20
-
"""get commit messages since last tag or all recent commits."""
21
-
if since_tag:
22
-
cmd = ["git", "log", f"{since_tag}..HEAD", "--oneline"]
23
-
else:
24
-
cmd = ["git", "log", "--oneline", "-20"]
25
-
26
-
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
27
-
return [line for line in result.stdout.strip().split("\n") if line]
28
-
29
-
30
-
def get_latest_tag() -> str | None:
31
-
"""get the most recent git tag."""
32
-
result = subprocess.run(
33
-
["git", "describe", "--tags", "--abbrev=0"],
34
-
capture_output=True,
35
-
text=True,
36
-
)
37
-
return result.stdout.strip() if result.returncode == 0 else None
38
-
39
-
40
-
def create_release(version: str, notes: str) -> None:
41
-
"""create github release using gh cli."""
42
-
cmd = [
43
-
"gh",
44
-
"release",
45
-
"create",
46
-
version,
47
-
"--title",
48
-
version,
49
-
"--notes",
50
-
notes,
51
-
]
52
-
53
-
subprocess.run(cmd, check=True)
54
-
print(f"✓ created release {version}")
55
-
print("✓ deployment to production will start automatically")
56
-
57
-
58
-
def main() -> int:
59
-
"""main entry point."""
60
-
# ensure we're on main branch
61
-
result = subprocess.run(
62
-
["git", "branch", "--show-current"],
63
-
capture_output=True,
64
-
text=True,
65
-
check=True,
66
-
)
67
-
current_branch = result.stdout.strip()
68
-
69
-
if current_branch != "main":
70
-
print(f"error: must be on main branch (currently on {current_branch})")
71
-
return 1
72
-
73
-
# ensure working directory is clean
74
-
result = subprocess.run(
75
-
["git", "status", "--porcelain"],
76
-
capture_output=True,
77
-
text=True,
78
-
check=True,
79
-
)
80
-
if result.stdout.strip():
81
-
print("error: working directory has uncommitted changes")
82
-
return 1
83
-
84
-
# generate version
85
-
version = get_timestamp_version()
86
-
print(f"version: {version}")
87
-
88
-
# get commits since last tag
89
-
latest_tag = get_latest_tag()
90
-
commits = get_recent_commits(latest_tag)
91
-
92
-
if not commits:
93
-
print("warning: no commits since last release")
94
-
95
-
# generate release notes
96
-
notes_lines = []
97
-
if latest_tag:
98
-
notes_lines.append(f"changes since {latest_tag}:\n")
99
-
else:
100
-
notes_lines.append("initial production release\n")
101
-
102
-
for commit in commits:
103
-
notes_lines.append(f"- {commit}")
104
-
105
-
notes = "\n".join(notes_lines)
106
-
107
-
# show preview
108
-
print("\nrelease notes:")
109
-
print(notes)
110
-
print()
111
-
112
-
# confirm
113
-
response = input(f"create release {version}? [y/N] ")
114
-
if response.lower() != "y":
115
-
print("cancelled")
116
-
return 0
117
-
118
-
# create release
119
-
create_release(version, notes)
120
-
121
-
return 0
122
-
123
-
124
-
if __name__ == "__main__":
125
-
sys.exit(main())
···
+1
-1
src/backend/utilities/database.py
+1
-1
src/backend/utilities/database.py