[release] Bump to version 38.0
- Fix release flow to be easier to trigger - Move imported retroarch lrtl files to processed/ directory on WebDAV - Add listenbrainz support for similar tracks - Consolidate albums in the same musicbrainz_releasegroup_id - Clean up metadata on music tracks - Make artists_m2m field source of artist truth for albums - Fix various artist album problem with Superwolves (track with multiple artists) - Move imported eBird CSV files to processed/ directory on WebDAV - Move imported Board Game CSV files to processed/ directory on WebDAV - Move imported Scale CSV files to processed/ directory on WebDAV - Allow special parameter to re-import already processed GPX files - Move imported GPX files to processed/ directory on WebDAV - Add CSS Grid calendar view for scrobbles - Come up with a possible flow using WebDAV and super-productivity for tasks - Fix PuzzleLogData has no attribute form - Add PuzzleLogData class with with_people and completed - Add weather lookup to the mood check-in flow - Add importing of openScale CSV files to Tasks - Add ability to track Birding sessions via BirdingLocation scrobbles - List only the last 20 scrobbles per category on the home page - Fix display of notes so they look like stickies - Add searching to scrobbles - Fix uniqueness of imdb_id messing up youtube videos - Fix genearting chart records - Save raw scrobble request data to every scrobble log - Clean up follow up notifications for if you're still scrobbling - Fix lookup of music tracks from Musicbrainz - Check opencode about a way to present stats like movies per month - Fix bug in Jellyfin audio track playback - Auto calc duration if no playback time seconds present - Fix bug in video find_or_create - Update admin page to be easier to use - Fix migrations and update repo - Add recipe parsing for food lookups - Videos are scrobbling duplicates again - Fix board games not saving BGG id on lookup - Fix board game lookup with name like Unmatched Game System - Fix raw text webpage title not truncating to 254 chars
This commit is contained in:
18
PROJECT.org
18
PROJECT.org
@ -93,7 +93,7 @@ fetching and simple saving.
|
|||||||
:LOGBOOK:
|
:LOGBOOK:
|
||||||
CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20
|
CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20
|
||||||
:END:
|
:END:
|
||||||
* Backlog [37/53] :vrobbler:project:personal:
|
* Backlog [0/16] :vrobbler:project:personal:
|
||||||
** TODO [#C] Add sentiment parsing for Scrobbles with notes :vrobbler:project:scrobbles:sentiment:
|
** TODO [#C] Add sentiment parsing for Scrobbles with notes :vrobbler:project:scrobbles:sentiment:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ID: 37781d6a-f3b0-48b2-bf98-33c2c791cf85
|
:ID: 37781d6a-f3b0-48b2-bf98-33c2c791cf85
|
||||||
@ -489,6 +489,21 @@ to GMT to save it in the database.
|
|||||||
|
|
||||||
It would be nice to not duplicate comments that exist on a task when it's first scrobbled.
|
It would be nice to not duplicate comments that exist on a task when it's first scrobbled.
|
||||||
|
|
||||||
|
* Version 38.0 [38/38]
|
||||||
|
** DONE [#A] Fix release flow to be easier to trigger :pyproject:release:tooling:
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: e321f8a3-26ea-b316-8493-294b50c0b516
|
||||||
|
:END:
|
||||||
|
|
||||||
|
*** Description
|
||||||
|
|
||||||
|
We should have a command like `just relesae <major|minor>` that can cut new
|
||||||
|
releases on command by updating the PROJECT.org file with a release header for
|
||||||
|
all DONE tasks and creating the appropriate release commits and tags based on
|
||||||
|
either the new release version type.
|
||||||
|
|
||||||
|
It should also update the pyproject file.
|
||||||
|
|
||||||
** DONE [#A] Move imported retroarch lrtl files to processed/ directory on WebDAV :webdav:retroarch:importers:
|
** DONE [#A] Move imported retroarch lrtl files to processed/ directory on WebDAV :webdav:retroarch:importers:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ID: 881b5af6-e96f-4e71-9e66-2d30824e5504
|
:ID: 881b5af6-e96f-4e71-9e66-2d30824e5504
|
||||||
@ -864,6 +879,7 @@ urllib.error.HTTPError: HTTP Error 500: Internal Server Error
|
|||||||
- Note taken on [2025-09-30 Tue 09:33]
|
- Note taken on [2025-09-30 Tue 09:33]
|
||||||
|
|
||||||
This may have already been resolved ... need to just confirm it.
|
This may have already been resolved ... need to just confirm it.
|
||||||
|
|
||||||
* Version 37.0 [4/4]
|
* Version 37.0 [4/4]
|
||||||
** DONE [#A] Tasks from org-mode should properly update notes and leave them out of the body :vrobbler:bug:tasks:
|
** DONE [#A] Tasks from org-mode should properly update notes and leave them out of the body :vrobbler:bug:tasks:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
|
|||||||
3
justfile
3
justfile
@ -14,3 +14,6 @@ celery:
|
|||||||
|
|
||||||
celery-beat:
|
celery-beat:
|
||||||
poetry run celery -A vrobbler beat -l info
|
poetry run celery -A vrobbler beat -l info
|
||||||
|
|
||||||
|
release kind="minor":
|
||||||
|
poetry run python scripts/release.py {{kind}}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "vrobbler"
|
name = "vrobbler"
|
||||||
version = "0.16.1"
|
version = "38.0"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Colin Powell <colin@unbl.ink>"]
|
authors = ["Colin Powell <colin@unbl.ink>"]
|
||||||
|
|
||||||
|
|||||||
217
scripts/release.py
Executable file
217
scripts/release.py
Executable file
@ -0,0 +1,217 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Cut a new release: collect DONE items from Backlog into a new Version section.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
poetry run python scripts/release.py major
|
||||||
|
poetry run python scripts/release.py minor
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PROJECT_FILE = Path("PROJECT.org")
|
||||||
|
PYPROJECT_FILE = Path("pyproject.toml")
|
||||||
|
|
||||||
|
BACKLOG_RE = re.compile(r"^\* Backlog\s+\[(\d+)/(\d+)\](.*)$")
|
||||||
|
VERSION_RE = re.compile(r"^\* Version\s+(\d+\.\d+)\s+\[\d+/\d+\]")
|
||||||
|
DONE_HEADER_RE = re.compile(r"^(\*\* DONE\s+)(.*)$")
|
||||||
|
ITEM_HEADER_RE = re.compile(r"^\*\* ")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_done_line(line):
|
||||||
|
"""Extract a clean title from a ** DONE line, stripping priority and tags."""
|
||||||
|
rest = line[8:].strip() # remove "** DONE "
|
||||||
|
# strip priority marker like [#A]
|
||||||
|
rest = re.sub(r"^\[#[A-C]\]\s+", "", rest, count=1)
|
||||||
|
# strip org-mode tags at end (space-colon-tags)
|
||||||
|
rest = re.sub(r"\s+:\S.*:\s*$", "", rest)
|
||||||
|
return rest
|
||||||
|
|
||||||
|
|
||||||
|
def bump_version(current_major, current_minor, kind):
|
||||||
|
if kind == "major":
|
||||||
|
return current_major + 1, 0
|
||||||
|
elif kind == "minor":
|
||||||
|
return current_major, current_minor + 1
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown bump kind: {kind}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2 or sys.argv[1] not in ("major", "minor"):
|
||||||
|
print(f"Usage: {sys.argv[0]} <major|minor>", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
kind = sys.argv[1]
|
||||||
|
|
||||||
|
lines = PROJECT_FILE.read_text().splitlines(keepends=True)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 1. Identify top-level sections
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
section_starts = []
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.startswith("* ") and not line.startswith("** "):
|
||||||
|
section_starts.append(i)
|
||||||
|
section_starts.append(len(lines))
|
||||||
|
|
||||||
|
backlog_idx = None
|
||||||
|
version_idx = None
|
||||||
|
|
||||||
|
for idx, start in enumerate(section_starts[:-1]):
|
||||||
|
header = lines[start].strip()
|
||||||
|
if header.startswith("* Backlog"):
|
||||||
|
backlog_idx = idx
|
||||||
|
if header.startswith("* Version"):
|
||||||
|
version_idx = idx # last occurrence wins
|
||||||
|
|
||||||
|
if backlog_idx is None:
|
||||||
|
print("ERROR: no Backlog section found", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if version_idx is None:
|
||||||
|
print("ERROR: no Version section found", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
backlog_start = section_starts[backlog_idx]
|
||||||
|
backlog_end = section_starts[backlog_idx + 1]
|
||||||
|
|
||||||
|
# Find the newest Version section (first after Backlog) that matches
|
||||||
|
# our expected format (e.g. "37.0" not "0.11.4").
|
||||||
|
version_start = None
|
||||||
|
for idx in range(backlog_idx + 1, version_idx + 1):
|
||||||
|
header = lines[section_starts[idx]].strip()
|
||||||
|
if VERSION_RE.match(header):
|
||||||
|
version_start = section_starts[idx]
|
||||||
|
break
|
||||||
|
|
||||||
|
if version_start is None:
|
||||||
|
print("ERROR: no parseable Version header found", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
version_header = lines[version_start].strip()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 2. Parse current version from the newest * Version header
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
vm = VERSION_RE.match(version_header)
|
||||||
|
current_version = vm.group(1)
|
||||||
|
major_str, minor_str = current_version.split(".")
|
||||||
|
current_major = int(major_str)
|
||||||
|
current_minor = int(minor_str)
|
||||||
|
new_major, new_minor = bump_version(current_major, current_minor, kind)
|
||||||
|
new_version = f"{new_major}.{new_minor}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 3. Collect ** DONE items from the Backlog section
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
backlog_lines = lines[backlog_start:backlog_end]
|
||||||
|
|
||||||
|
# Split Backlog into items at each ** line (skip the section header)
|
||||||
|
items = [] # list of (start_idx, end_idx, is_done)
|
||||||
|
item_start = None
|
||||||
|
for i in range(1, len(backlog_lines)):
|
||||||
|
if ITEM_HEADER_RE.match(backlog_lines[i]):
|
||||||
|
if item_start is not None:
|
||||||
|
items.append((item_start, i, backlog_lines[item_start].startswith("** DONE")))
|
||||||
|
item_start = i
|
||||||
|
if item_start is not None:
|
||||||
|
items.append((item_start, len(backlog_lines), backlog_lines[item_start].startswith("** DONE")))
|
||||||
|
|
||||||
|
done_items = [(s, e) for s, e, is_done in items if is_done]
|
||||||
|
kept_items = [(s, e) for s, e, is_done in items if not is_done]
|
||||||
|
|
||||||
|
if not done_items:
|
||||||
|
print("No DONE items found in Backlog — nothing to release.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 4. Build the new Version section text
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
version_section_lines = [f"* Version {new_version} [{len(done_items)}/{len(done_items)}]\n"]
|
||||||
|
for s, e in done_items:
|
||||||
|
version_section_lines.extend(backlog_lines[s:e])
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 5. Build updated Backlog section
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
backlog_header_line = backlog_lines[0]
|
||||||
|
bm = BACKLOG_RE.match(backlog_header_line.strip())
|
||||||
|
if not bm:
|
||||||
|
print(f"ERROR: could not parse backlog header: {backlog_header_line!r}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
done_count = int(bm.group(1))
|
||||||
|
total_count = int(bm.group(2))
|
||||||
|
tags = bm.group(3)
|
||||||
|
|
||||||
|
new_done = done_count - len(done_items)
|
||||||
|
new_total = total_count - len(done_items)
|
||||||
|
new_backlog_header = f"* Backlog [{new_done}/{new_total}]{tags}\n"
|
||||||
|
|
||||||
|
backlog_body = []
|
||||||
|
for s, e in kept_items:
|
||||||
|
backlog_body.extend(backlog_lines[s:e])
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 6. Assemble the new file
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
before_backlog = lines[:backlog_start]
|
||||||
|
after_backlog = lines[backlog_end:version_start]
|
||||||
|
|
||||||
|
# Everything from the first Version section onwards
|
||||||
|
from_version = lines[version_start:]
|
||||||
|
|
||||||
|
output = (
|
||||||
|
before_backlog
|
||||||
|
+ [new_backlog_header]
|
||||||
|
+ backlog_body
|
||||||
|
+ version_section_lines
|
||||||
|
+ ["\n"]
|
||||||
|
+ after_backlog
|
||||||
|
+ from_version
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 7. Update pyproject.toml
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
pyproject = PYPROJECT_FILE.read_text()
|
||||||
|
pyproject = re.sub(
|
||||||
|
r'^version = "[\d.]+"',
|
||||||
|
f'version = "{new_version}"',
|
||||||
|
pyproject,
|
||||||
|
count=1,
|
||||||
|
flags=re.MULTILINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 8. Write files
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
PROJECT_FILE.write_text("".join(output))
|
||||||
|
PYPROJECT_FILE.write_text(pyproject)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 9. Build commit body from done item titles
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
commit_lines = []
|
||||||
|
for s, e in done_items:
|
||||||
|
title = parse_done_line(backlog_lines[s])
|
||||||
|
if title:
|
||||||
|
commit_lines.append(f"- {title}")
|
||||||
|
|
||||||
|
commit_body = "\n".join(commit_lines)
|
||||||
|
commit_message = f"[release] Bump to version {new_version}\n\n{commit_body}"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 10. Git commit + tag
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
subprocess.run(["git", "add", str(PROJECT_FILE), str(PYPROJECT_FILE)], check=True)
|
||||||
|
subprocess.run(["git", "commit", "-m", commit_message], check=True)
|
||||||
|
subprocess.run(["git", "tag", new_version], check=True)
|
||||||
|
|
||||||
|
print(f"\nReleased v{new_version} — tag {new_version} created.")
|
||||||
|
print(f"Moved {len(done_items)} DONE item(s) from Backlog to Version section.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user