Adds release script and bumps 0.2.4
This commit is contained in:
12
README.md
12
README.md
@@ -99,6 +99,18 @@ TMDB-backed metadata enrichment requires `TMDB_API_KEY` to be set in the environ
|
||||
|
||||
## Version History
|
||||
|
||||
### 0.2.4
|
||||
|
||||
- lightweight CLI commands now stay import-light via lazy runtime loading
|
||||
- setup/config templating moved to `assets/ffx.json.j2`
|
||||
- aligned two-step local setup wrappers: `ffx setup` and `ffx configure_workstation`
|
||||
- combined `ffprobe` payload reuse in `FileProperties`
|
||||
- configurable crop-detect sampling plus per-process crop result caching
|
||||
- single-query controller accessors and conditional DB schema bootstrap
|
||||
- shared screen bootstrap/controller wiring for large detail screens
|
||||
- configurable default season/episode digit lengths
|
||||
- digit-aware `rename` and padded `unmux` filename markers
|
||||
|
||||
### 0.2.3
|
||||
|
||||
- PyPI packaging
|
||||
|
||||
@@ -9,19 +9,12 @@
|
||||
- The biggest near-term wins are in startup cost, repeated subprocess work, repeated database query patterns, and general repo hygiene.
|
||||
- This list is intentionally optimization-oriented rather than bug-oriented. Some items below also improve correctness or maintainability, but they were selected because they can reduce runtime cost, operator friction, or iteration overhead.
|
||||
- A first modern integration slice now exists under [`tests/integration/subtrack_mapping`](/home/osgw/.local/src/codex/ffx/tests/integration/subtrack_mapping). Remaining test-suite cleanup is now mostly about migrating and shrinking the legacy harness surface under [`tests/legacy`](/home/osgw/.local/src/codex/ffx/tests/legacy).
|
||||
- The CLI root now lazy-loads heavy runtime dependencies so lightweight commands such as `version`, `help`, `setup`, `configure_workstation`, and `upgrade` stay import-light.
|
||||
- Shared CLI defaults for container/output tokens now live outside [`src/ffx/ffx_controller.py`](/home/osgw/.local/src/codex/ffx/src/ffx/ffx_controller.py), and a focused unit test locks in the lazy-import contract.
|
||||
- `FileProperties` now uses one cached `ffprobe -show_format -show_streams -of json` call per source file, and the combined payload was confirmed against the Dragonball asset to satisfy both previous probe call sites fully.
|
||||
- Crop detection now uses configurable sampling windows plus per-process caching keyed by source file and sampling range, and the `cropdetect` CLI command now calls the real `FileProperties.findCropArguments()` path.
|
||||
- Database startup now bootstraps schema only when required tables are actually missing, while version enforcement still runs on ordinary DB-backed context creation.
|
||||
- Helper filename and rich-text utilities now use compiled raw regexes plus translate-based filename filtering, with unit coverage for TMDB suffix rewriting and Rich color stripping.
|
||||
- Process resource limiting now has explicit disabled/default states in the CLI and requirements, and combined CPU-plus-niceness wrapping now executes as `cpulimit -- nice -n ... <command>` instead of a less explicit prefix chain.
|
||||
- FFX logger setup now reuses named handlers, and fallback logger access no longer mutates handlers in ordinary constructors and helpers.
|
||||
- The process wrapper now uses `subprocess.run(...)` with centralized command formatting plus stable timeout and missing-command error mapping.
|
||||
- Active ORM controllers now use single-query accessors instead of paired `count()` plus `first()` lookups.
|
||||
- Pattern matching now uses cached compiled regexes plus explicit duplicate-match errors, and pattern creation flows no longer persist zero-track patterns.
|
||||
- The two-step local setup flow now has aligned CLI wrappers for both phases: `ffx setup` for bundle prep and `ffx configure_workstation` for workstation prep, while the shell scripts remain the bootstrap entrypoints before the bundle exists.
|
||||
- The large detail screens now share one screen-bootstrap helper for context, metadata-filter extraction, and controller wiring, and show-pattern loading now goes through `PatternController` instead of a screen-local session query.
|
||||
|
||||
## Focused Snapshot
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "ffx"
|
||||
description = "FFX recoding and metadata managing tool"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
license = {file = "LICENSE.md"}
|
||||
dependencies = [
|
||||
"requests",
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
- Intended for local execution, not server deployment.
|
||||
- Stores default state in `~/.local/etc/ffx.json`, `~/.local/var/ffx/ffx.db`, and `~/.local/var/log/ffx.log`.
|
||||
- Timeline constraints:
|
||||
- The current implemented scope reflects a compact alpha release stream up to version `0.2.3`.
|
||||
- The current implemented scope reflects a compact alpha release stream up to version `0.2.4`.
|
||||
- Team capacity assumptions:
|
||||
- Maintained as a small codebase where simple patterns and direct controller logic are preferred over framework-heavy abstractions.
|
||||
- Third-party dependencies:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VERSION='0.2.3'
|
||||
VERSION='0.2.4'
|
||||
DATABASE_VERSION = 2
|
||||
|
||||
DEFAULT_QUALITY = 32
|
||||
|
||||
322
tools/merge_dev_into_main.sh
Executable file
322
tools/merge_dev_into_main.sh
Executable file
@@ -0,0 +1,322 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DEV_BRANCH="dev"
|
||||
MAIN_BRANCH="main"
|
||||
ORIGIN_REMOTE="origin"
|
||||
DEFAULT_AGENT_DEVELOPMENT_PATHS=(
|
||||
"AGENTS.md"
|
||||
"SCRATCHPAD.md"
|
||||
"guidance"
|
||||
"requirements"
|
||||
"prompts"
|
||||
"process"
|
||||
)
|
||||
AGENT_DEVELOPMENT_PATHS=("${DEFAULT_AGENT_DEVELOPMENT_PATHS[@]}")
|
||||
|
||||
CURRENT_BRANCH="${DEV_BRANCH}"
|
||||
ASSUME_YES=0
|
||||
DRY_RUN=0
|
||||
SKIP_TESTS=0
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") [--yes] [--dry-run] [--skip-tests] [--help]
|
||||
|
||||
Merge the local ${DEV_BRANCH} branch into ${MAIN_BRANCH}, remove agent-development files
|
||||
from ${MAIN_BRANCH}, create a release merge commit and tag, push to ${ORIGIN_REMOTE}/${MAIN_BRANCH},
|
||||
and switch back to ${DEV_BRANCH}.
|
||||
|
||||
Options:
|
||||
--yes Skip the interactive confirmation prompt.
|
||||
--dry-run Print the validated release plan without changing git state.
|
||||
--skip-tests Skip the default pre-release test gate (./tools/test.sh).
|
||||
--help Show this help text.
|
||||
|
||||
Environment overrides:
|
||||
FFX_RELEASE_CLEAN_PATHS Colon-separated path list to remove from ${MAIN_BRANCH}
|
||||
after merging ${DEV_BRANCH}. Defaults to:
|
||||
${DEFAULT_AGENT_DEVELOPMENT_PATHS[*]}
|
||||
EOF
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf '%s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
local exit_code="$1"
|
||||
|
||||
trap - EXIT
|
||||
|
||||
if git rev-parse -q --verify MERGE_HEAD >/dev/null 2>&1; then
|
||||
printf 'Merge is incomplete; aborting merge on %s...\n' "${CURRENT_BRANCH}" >&2
|
||||
git merge --abort >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
if [ "${CURRENT_BRANCH}" != "${DEV_BRANCH}" ]; then
|
||||
printf 'Switching back to %s...\n' "${DEV_BRANCH}" >&2
|
||||
git switch "${DEV_BRANCH}" >/dev/null 2>&1 || true
|
||||
CURRENT_BRANCH="${DEV_BRANCH}"
|
||||
fi
|
||||
|
||||
exit "${exit_code}"
|
||||
}
|
||||
|
||||
load_cleanup_paths() {
|
||||
if [ -n "${FFX_RELEASE_CLEAN_PATHS:-}" ]; then
|
||||
IFS=':' read -r -a AGENT_DEVELOPMENT_PATHS <<< "${FFX_RELEASE_CLEAN_PATHS}"
|
||||
fi
|
||||
|
||||
if [ "${#AGENT_DEVELOPMENT_PATHS[@]}" -eq 0 ]; then
|
||||
fail "Release cleanup path list is empty."
|
||||
fi
|
||||
}
|
||||
|
||||
require_repo_state() {
|
||||
if ! git rev-parse --show-toplevel >/dev/null 2>&1; then
|
||||
fail "This helper must be run inside a git repository."
|
||||
fi
|
||||
|
||||
if ! git show-ref --verify --quiet "refs/heads/${DEV_BRANCH}"; then
|
||||
fail "Local branch '${DEV_BRANCH}' does not exist."
|
||||
fi
|
||||
|
||||
if ! git show-ref --verify --quiet "refs/heads/${MAIN_BRANCH}"; then
|
||||
fail "Local branch '${MAIN_BRANCH}' does not exist."
|
||||
fi
|
||||
|
||||
if ! git remote get-url "${ORIGIN_REMOTE}" >/dev/null 2>&1; then
|
||||
fail "Remote '${ORIGIN_REMOTE}' is not configured."
|
||||
fi
|
||||
}
|
||||
|
||||
require_dev_checkout() {
|
||||
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||
if [ "${CURRENT_BRANCH}" != "${DEV_BRANCH}" ]; then
|
||||
fail "Current branch is '${CURRENT_BRANCH}', but '${DEV_BRANCH}' is required."
|
||||
fi
|
||||
}
|
||||
|
||||
require_clean_worktree() {
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
fail "Local '${DEV_BRANCH}' branch is dirty. Commit, stash, or clean changes first."
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_remote_state() {
|
||||
printf 'Fetching %s branch and tag state...\n' "${ORIGIN_REMOTE}"
|
||||
git fetch "${ORIGIN_REMOTE}" "${DEV_BRANCH}" "${MAIN_BRANCH}" --tags >/dev/null
|
||||
}
|
||||
|
||||
require_branch_matches_remote() {
|
||||
local branch="$1"
|
||||
local local_sha=""
|
||||
local remote_sha=""
|
||||
|
||||
if ! git show-ref --verify --quiet "refs/remotes/${ORIGIN_REMOTE}/${branch}"; then
|
||||
fail "Remote branch '${ORIGIN_REMOTE}/${branch}' does not exist."
|
||||
fi
|
||||
|
||||
local_sha="$(git rev-parse "refs/heads/${branch}")"
|
||||
remote_sha="$(git rev-parse "refs/remotes/${ORIGIN_REMOTE}/${branch}")"
|
||||
|
||||
if [ "${local_sha}" != "${remote_sha}" ]; then
|
||||
fail "Local branch '${branch}' is not up to date with '${ORIGIN_REMOTE}/${branch}'. Pull, rebase, or push first."
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_release_version() {
|
||||
local version_from_pyproject=""
|
||||
local version_from_constants=""
|
||||
|
||||
version_from_pyproject="$(
|
||||
sed -n 's/^version = "\(.*\)"$/\1/p' pyproject.toml | head -n 1
|
||||
)"
|
||||
version_from_constants="$(
|
||||
sed -n "s/^VERSION='\(.*\)'$/\1/p" src/ffx/constants.py | head -n 1
|
||||
)"
|
||||
|
||||
if [ -z "${version_from_pyproject}" ]; then
|
||||
fail "Could not resolve release version from pyproject.toml."
|
||||
fi
|
||||
|
||||
if [ -z "${version_from_constants}" ]; then
|
||||
fail "Could not resolve release version from src/ffx/constants.py."
|
||||
fi
|
||||
|
||||
if [ "${version_from_pyproject}" != "${version_from_constants}" ]; then
|
||||
fail "Version mismatch: pyproject.toml=${version_from_pyproject}, src/ffx/constants.py=${version_from_constants}."
|
||||
fi
|
||||
|
||||
printf '%s\n' "${version_from_pyproject}"
|
||||
}
|
||||
|
||||
require_release_tag_available() {
|
||||
local release_version="$1"
|
||||
local release_tag="v${release_version}"
|
||||
|
||||
if git rev-parse -q --verify "refs/tags/${release_tag}" >/dev/null 2>&1; then
|
||||
fail "Tag '${release_tag}' already exists."
|
||||
fi
|
||||
|
||||
if git rev-parse -q --verify "refs/tags/${release_version}" >/dev/null 2>&1; then
|
||||
fail "Bare tag '${release_version}' already exists; refusing to create ambiguous release tags."
|
||||
fi
|
||||
}
|
||||
|
||||
run_pre_release_tests() {
|
||||
if [ "${SKIP_TESTS}" -eq 1 ]; then
|
||||
printf 'Skipping pre-release tests.\n'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -x "./tools/test.sh" ]; then
|
||||
fail "Missing executable test runner at ./tools/test.sh."
|
||||
fi
|
||||
|
||||
printf 'Running pre-release tests via ./tools/test.sh...\n'
|
||||
./tools/test.sh
|
||||
}
|
||||
|
||||
print_release_plan() {
|
||||
local release_version="$1"
|
||||
local release_tag="v${release_version}"
|
||||
local release_commit_message="Release ${release_tag}"
|
||||
|
||||
printf 'Dry run only. Planned steps:\n'
|
||||
printf '1. Ensure current branch is %s and the worktree is clean.\n' "${DEV_BRANCH}"
|
||||
printf '2. Fetch %s and verify local %s and %s exactly match %s/%s and %s/%s.\n' \
|
||||
"${ORIGIN_REMOTE}" \
|
||||
"${DEV_BRANCH}" \
|
||||
"${MAIN_BRANCH}" \
|
||||
"${ORIGIN_REMOTE}" \
|
||||
"${DEV_BRANCH}" \
|
||||
"${ORIGIN_REMOTE}" \
|
||||
"${MAIN_BRANCH}"
|
||||
if [ "${SKIP_TESTS}" -eq 1 ]; then
|
||||
printf '3. Skip the pre-release test gate.\n'
|
||||
else
|
||||
printf '3. Run ./tools/test.sh as the pre-release test gate.\n'
|
||||
fi
|
||||
printf '4. Switch to %s and merge %s with --no-ff --no-commit.\n' "${MAIN_BRANCH}" "${DEV_BRANCH}"
|
||||
printf '5. Remove release-cleanup paths from %s:\n' "${MAIN_BRANCH}"
|
||||
local cleanup_path=""
|
||||
for cleanup_path in "${AGENT_DEVELOPMENT_PATHS[@]}"; do
|
||||
printf ' - %s\n' "${cleanup_path}"
|
||||
done
|
||||
printf '6. Create merge commit: %s\n' "${release_commit_message}"
|
||||
printf '7. Create annotated tag: %s\n' "${release_tag}"
|
||||
printf '8. Push %s to %s/%s with --follow-tags.\n' "${MAIN_BRANCH}" "${ORIGIN_REMOTE}" "${MAIN_BRANCH}"
|
||||
printf '9. Switch back to %s.\n' "${DEV_BRANCH}"
|
||||
}
|
||||
|
||||
trap 'cleanup $?' EXIT
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--yes)
|
||||
ASSUME_YES=1
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
;;
|
||||
--skip-tests)
|
||||
SKIP_TESTS=1
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
fail "Unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
load_cleanup_paths
|
||||
require_repo_state
|
||||
require_dev_checkout
|
||||
require_clean_worktree
|
||||
fetch_remote_state
|
||||
require_branch_matches_remote "${DEV_BRANCH}"
|
||||
require_branch_matches_remote "${MAIN_BRANCH}"
|
||||
|
||||
RELEASE_VERSION="$(resolve_release_version)"
|
||||
RELEASE_TAG="v${RELEASE_VERSION}"
|
||||
RELEASE_COMMIT_MESSAGE="Release ${RELEASE_TAG}"
|
||||
require_release_tag_available "${RELEASE_VERSION}"
|
||||
|
||||
printf 'This will merge %s into %s, remove agent-development files on %s,\n' "${DEV_BRANCH}" "${MAIN_BRANCH}" "${MAIN_BRANCH}"
|
||||
printf 'run the pre-release gate%s, create %s, push to %s/%s, and switch back to %s.\n' \
|
||||
"$([ "${SKIP_TESTS}" -eq 1 ] && printf ' (skipped)' || printf '')" \
|
||||
"${RELEASE_TAG}" \
|
||||
"${ORIGIN_REMOTE}" \
|
||||
"${MAIN_BRANCH}" \
|
||||
"${DEV_BRANCH}"
|
||||
|
||||
if [ "${ASSUME_YES}" -ne 1 ]; then
|
||||
printf 'Are you sure? [y/N] '
|
||||
read -r confirmation
|
||||
case "${confirmation}" in
|
||||
y|Y|yes|YES)
|
||||
;;
|
||||
*)
|
||||
fail "Aborted by user."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ "${DRY_RUN}" -eq 1 ]; then
|
||||
print_release_plan "${RELEASE_VERSION}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
run_pre_release_tests
|
||||
require_clean_worktree
|
||||
fetch_remote_state
|
||||
require_branch_matches_remote "${DEV_BRANCH}"
|
||||
require_branch_matches_remote "${MAIN_BRANCH}"
|
||||
require_release_tag_available "${RELEASE_VERSION}"
|
||||
|
||||
git switch "${MAIN_BRANCH}" >/dev/null
|
||||
CURRENT_BRANCH="${MAIN_BRANCH}"
|
||||
|
||||
printf 'Merging %s into %s...\n' "${DEV_BRANCH}" "${MAIN_BRANCH}"
|
||||
if ! git merge --no-ff --no-commit "${DEV_BRANCH}"; then
|
||||
fail "Merge from '${DEV_BRANCH}' into '${MAIN_BRANCH}' failed."
|
||||
fi
|
||||
|
||||
if ! git rev-parse -q --verify MERGE_HEAD >/dev/null 2>&1; then
|
||||
fail "'${MAIN_BRANCH}' is already up to date with '${DEV_BRANCH}'. Nothing to merge."
|
||||
fi
|
||||
|
||||
printf 'Removing agent-development files from %s...\n' "${MAIN_BRANCH}"
|
||||
git rm -r --ignore-unmatch "${AGENT_DEVELOPMENT_PATHS[@]}" >/dev/null
|
||||
|
||||
if git diff --cached --quiet; then
|
||||
fail "No staged changes are present after merging '${DEV_BRANCH}' into '${MAIN_BRANCH}'."
|
||||
fi
|
||||
|
||||
printf 'Creating release merge commit: %s\n' "${RELEASE_COMMIT_MESSAGE}"
|
||||
git commit -m "${RELEASE_COMMIT_MESSAGE}"
|
||||
|
||||
printf 'Creating annotated tag: %s\n' "${RELEASE_TAG}"
|
||||
git tag -a "${RELEASE_TAG}" -m "FFX ${RELEASE_VERSION}"
|
||||
|
||||
printf 'Pushing %s and annotated tags to %s...\n' "${MAIN_BRANCH}" "${ORIGIN_REMOTE}"
|
||||
git push "${ORIGIN_REMOTE}" "${MAIN_BRANCH}" --follow-tags
|
||||
|
||||
printf 'Switching back to %s...\n' "${DEV_BRANCH}"
|
||||
git switch "${DEV_BRANCH}" >/dev/null
|
||||
CURRENT_BRANCH="${DEV_BRANCH}"
|
||||
|
||||
printf 'Release merge complete: %s pushed to %s/%s and tagged as %s.\n' \
|
||||
"${RELEASE_COMMIT_MESSAGE}" \
|
||||
"${ORIGIN_REMOTE}" \
|
||||
"${MAIN_BRANCH}" \
|
||||
"${RELEASE_TAG}"
|
||||
Reference in New Issue
Block a user