Skip to content

Commit c032b24

Browse files
Merge dev into main for 2026.04.05.03 release
2 parents 1a6ea74 + 2ee3f86 commit c032b24

39 files changed

Lines changed: 1868 additions & 310 deletions

.github/workflows/release-main.yml

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,7 @@ jobs:
2828
git config user.name "github-actions[bot]"
2929
git config user.email "github-actions[bot]@users.noreply.github.com"
3030
31-
# Remove today's packages to avoid collision detection in pkg_build.sh
32-
- name: Clean existing packages for today
33-
run: |
34-
TODAY=$(date +"%Y.%m.%d")
35-
rm -f archive/folderview.plus-${TODAY}*.txz
36-
echo "Cleaned packages for $TODAY"
37-
38-
- name: Build stable package
39-
run: |
40-
chmod +x pkg_build.sh
41-
bash pkg_build.sh
42-
43-
- name: Ensure CHANGES entry for release version
44-
run: |
45-
chmod +x scripts/ensure_plg_changes_entry.sh
46-
bash scripts/ensure_plg_changes_entry.sh
47-
48-
- name: Run release validation suite
31+
- name: Prepare and push stable release
4932
env:
5033
FVPLUS_UNRAID_MATRIX: ${{ secrets.FVPLUS_UNRAID_MATRIX }}
5134
FVPLUS_UNRAID_MATRIX_REQUIRED: ${{ secrets.FVPLUS_UNRAID_MATRIX != '' && '1' || '0' }}
@@ -54,6 +37,7 @@ jobs:
5437
FVPLUS_I18N_STRICT: '1'
5538
FVPLUS_DEAD_CODE_STRICT: '1'
5639
FVPLUS_REQUIRE_PERF_BASELINE: '1'
40+
FVPLUS_REQUIRE_EXPLICIT_RELEASE_NOTES: '1'
5741
FVPLUS_MAIN_HISTORY_BASE_REF: ${{ github.event.before }}
5842
FVPLUS_BROWSER_SMOKE_URL: ${{ secrets.FVPLUS_BROWSER_SMOKE_URL }}
5943
FVPLUS_BROWSER_SMOKE_REQUIRED: ${{ secrets.FVPLUS_BROWSER_SMOKE_URL != '' && '1' || '0' }}
@@ -73,29 +57,8 @@ jobs:
7357
FVPLUS_THEME_SMOKE_ARTIFACT_DIR: ${{ github.workspace }}/tmp/browser-smoke-artifacts/theme-matrix
7458
FVPLUS_PLAYWRIGHT_INSTALL_WITH_DEPS: '1'
7559
run: |
76-
chmod +x scripts/run_ci_suite.sh
77-
bash scripts/run_ci_suite.sh --release
78-
79-
- name: Extract stable version
80-
id: version
81-
run: |
82-
VERSION=$(grep -oP '<!ENTITY version "\K[^"]+' folderview.plus.plg)
83-
echo "version=$VERSION" >> $GITHUB_OUTPUT
84-
FILENAME="folderview.plus-${VERSION}.txz"
85-
echo "filename=$FILENAME" >> $GITHUB_OUTPUT
86-
echo "Stable version: $VERSION"
87-
88-
- name: Commit stable release to main
89-
run: |
90-
git add -A
91-
if git diff --cached --quiet; then
92-
echo "No release file changes to commit."
93-
else
94-
git commit -m "Stable release ${{ steps.version.outputs.version }}"
95-
fi
96-
97-
- name: Push main
98-
run: git push origin main
60+
chmod +x scripts/release_prepare.sh
61+
bash scripts/release_prepare.sh --push-main
9962
10063
- name: Upload debug artifacts on failure
10164
if: failure()

.github/workflows/release-on-main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ jobs:
102102
FVPLUS_I18N_STRICT: '1'
103103
FVPLUS_DEAD_CODE_STRICT: '1'
104104
FVPLUS_REQUIRE_PERF_BASELINE: '1'
105+
FVPLUS_REQUIRE_EXPLICIT_RELEASE_NOTES: '1'
105106
FVPLUS_MAIN_HISTORY_BASE_REF: ${{ github.event.before }}
106107
FVPLUS_BROWSER_SMOKE_URL: ${{ secrets.FVPLUS_BROWSER_SMOKE_URL }}
107108
FVPLUS_BROWSER_SMOKE_REQUIRED: ${{ secrets.FVPLUS_BROWSER_SMOKE_URL != '' && '1' || '0' }}

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,11 @@ Recommended migration path:
363363
- `bash scripts/dev_finalize.sh --open-fixture --skip-build`
364364
- Build a package locally:
365365
- `bash pkg_build.sh`
366-
- Prepare a release build with checks:
366+
- Curate stable release notes first:
367+
- `docs/releases/<version>.md`
368+
- Simulate the full `main` release flow safely in a temporary worktree:
369+
- `bash scripts/simulate_main_release.sh`
370+
- Prepare a stable release from `main` with checks:
367371
- `bash scripts/release_prepare.sh`
368372
- Run the automated test suite:
369373
- `node --test tests/*.mjs`

docs/releases/2026.04.05.03.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Features
2+
- Added a compact `Bulk move` workflow directly to the Folder Editor `Members` tab for both Docker and VM folders, so users can move shown or included members into another folder without leaving the editor.
3+
- Bulk move supports the scopes `Shown`, `Included shown`, `Excluded shown`, and `All included`.
4+
- Bulk move reuses the guarded server-side bulk assignment path instead of duplicating a second editor-only mover.
5+
- Each bulk move creates a backup first and exposes an editor-local undo path.
6+
7+
Fixes
8+
- Fixed multi-row Docker preview `Console` and `Logs` actions so they keep their real runtime wiring instead of falling back to a top-of-page anchor jump.
9+
- Fixed Folder Editor startup crashes introduced by the new Members-tab bulk move flow when hydration reached the bulk move helpers before those functions finished initializing.
10+
- Fixed the Folder Editor post-bulk-move lockup so the page no longer gets trapped behind a blocking success or undo modal after the move completes.
11+
- Fixed Members-tab bulk move live refresh so moved Docker containers and VMs disappear from the current folder immediately instead of remaining visible until the folder is submitted or reloaded.
12+
- Fixed the leave-page warning after a successful Members-tab bulk move by syncing the member baseline correctly, while keeping the normal unsaved-changes prompt for regular manual member edits.
13+
14+
UI and UX
15+
- The Members-tab bulk move flow now keeps the editor live after completion and shows a non-blocking inline undo action inside the bulk move summary area.
16+
- Folder Editor bulk move status messaging now updates in place instead of interrupting the editor with follow-up confirmation dialogs.
17+
18+
Platform and Runtime
19+
- Added versioned asset URLs for shipped FolderView Plus JavaScript, CSS, vendor includes, and locale assets so plugin updates no longer depend on users manually clearing browser cache to see new changes.
20+
21+
Release and Quality
22+
- Hardened the main release process so stable cuts now require explicit curated release notes instead of falling back to generic seeded `CHANGES` text.
23+
- Added a local `simulate_main_release.sh` path so the full stable-release flow can be rehearsed before merging and pushing `main`.
24+
- Unified stable packaging through the shared `release_prepare.sh` entrypoint so the local and GitHub release flows use the same preparation path.
25+
- Hardened back-merge handling so release-only artifacts and stable-only metadata do not keep breaking the `main` to `dev` sync path.
26+
- Tightened workflow self-checks, release-note consistency validation, include-order coverage, and related release/package guards around the new release path.

scripts/ensure_plg_changes_entry.sh

Lines changed: 142 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,154 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
55
PLG_FILE="${ROOT_DIR}/folderview.plus.plg"
66
MAX_AUTO_LINES="${FVPLUS_AUTO_CHANGE_LINES:-6}"
77
AUTO_FALLBACK_NOTE='Maintenance: Release metadata and packaging sync.'
8+
CHECK_ONLY=0
9+
VERSION_OVERRIDE="${FVPLUS_TARGET_RELEASE_VERSION:-}"
10+
REQUIRE_EXPLICIT="${FVPLUS_REQUIRE_EXPLICIT_RELEASE_NOTES:-0}"
811
# shellcheck source=scripts/lib.sh
912
source "${ROOT_DIR}/scripts/lib.sh"
1013

14+
print_usage() {
15+
cat <<'EOF'
16+
Usage: ensure_plg_changes_entry.sh [options]
17+
--version VERSION Validate or insert notes for VERSION instead of the manifest version
18+
--check-only Validate note availability/content without modifying folderview.plus.plg
19+
--require-explicit Require curated or explicit non-generic release notes instead of auto-generated notes
20+
-h, --help Show this help
21+
EOF
22+
}
23+
24+
while [[ $# -gt 0 ]]; do
25+
case "${1:-}" in
26+
--version)
27+
VERSION_OVERRIDE="${2:-}"
28+
shift
29+
;;
30+
--check-only)
31+
CHECK_ONLY=1
32+
;;
33+
--require-explicit)
34+
REQUIRE_EXPLICIT=1
35+
;;
36+
-h|--help)
37+
print_usage
38+
exit 0
39+
;;
40+
*)
41+
fvplus::fail "Unknown argument: ${1}"
42+
;;
43+
esac
44+
shift
45+
done
46+
1147
if [[ ! -f "${PLG_FILE}" ]]; then
1248
fvplus::fail "Missing plugin manifest: ${PLG_FILE}"
1349
fi
1450

15-
VERSION="$(fvplus::read_plg_version "${PLG_FILE}")"
51+
VERSION="${VERSION_OVERRIDE:-$(fvplus::read_plg_version "${PLG_FILE}")}"
52+
OVERRIDE_FILE="${ROOT_DIR}/docs/releases/${VERSION}.md"
53+
54+
normalize_changes_block() {
55+
local raw="${1:-}"
56+
printf '%s' "${raw}" | sed -E '/^[[:space:]]*$/d; s/[[:space:]]+/ /g; s/^[[:space:]]+|[[:space:]]+$//g'
57+
}
58+
59+
changes_block_for_version() {
60+
local target_version="${1:-}"
61+
awk -v version="${target_version}" '
62+
BEGIN { capture = 0 }
63+
/^###/ {
64+
if (capture) {
65+
exit
66+
}
67+
if ($0 ~ "^###" version "[[:space:]]*$") {
68+
capture = 1
69+
next
70+
}
71+
}
72+
capture {
73+
print
74+
}
75+
' "${PLG_FILE}" | sed '/^[[:space:]]*$/d'
76+
}
77+
78+
previous_changes_version() {
79+
local target_version="${1:-}"
80+
awk -v version="${target_version}" '
81+
/^###/ {
82+
candidate = $0
83+
sub(/^###/, "", candidate)
84+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", candidate)
85+
if (candidate != version) {
86+
print candidate
87+
exit
88+
}
89+
}
90+
' "${PLG_FILE}"
91+
}
92+
93+
is_metadata_only_changes_line() {
94+
local line="${1:-}"
95+
local lowered=""
96+
lowered="$(printf '%s' "${line}" | tr '[:upper:]' '[:lower:]')"
97+
[[ "${lowered}" == *"release metadata and packaging sync"* ]] && return 0
98+
[[ "${lowered}" == *"automated release metadata update"* ]] && return 0
99+
[[ "${lowered}" == *"metadata update"* ]] && return 0
100+
[[ "${lowered}" == *"packaging sync"* ]] && return 0
101+
[[ "${lowered}" == *"folder editor flows, previews, and bootstrap behavior."* ]] && return 0
102+
return 1
103+
}
104+
105+
block_is_metadata_only() {
106+
local block="${1:-}"
107+
local line=""
108+
local saw_content=0
109+
while IFS= read -r line; do
110+
[[ -z "${line}" ]] && continue
111+
saw_content=1
112+
if ! is_metadata_only_changes_line "${line}"; then
113+
return 1
114+
fi
115+
done <<< "${block}"
116+
[[ "${saw_content}" -eq 1 ]]
117+
}
16118

17-
if grep -q "^###${VERSION}$" "${PLG_FILE}"; then
119+
current_block="$(changes_block_for_version "${VERSION}")"
120+
if [[ -n "${current_block}" ]]; then
121+
if [[ "${REQUIRE_EXPLICIT}" == "1" ]]; then
122+
if block_is_metadata_only "${current_block}"; then
123+
fvplus::fail "CHANGES entry for ${VERSION} contains only generic release-metadata boilerplate. Add explicit release notes or docs/releases/${VERSION}.md."
124+
fi
125+
previous_version="$(previous_changes_version "${VERSION}")"
126+
if [[ -n "${previous_version}" ]]; then
127+
previous_block="$(changes_block_for_version "${previous_version}")"
128+
if [[ -n "${previous_block}" ]] && [[ "$(normalize_changes_block "${current_block}")" == "$(normalize_changes_block "${previous_block}")" ]]; then
129+
fvplus::fail "CHANGES entry for ${VERSION} duplicates the previous release notes block. Add explicit release deltas or docs/releases/${VERSION}.md."
130+
fi
131+
fi
132+
fi
18133
echo "CHANGES entry already present for ${VERSION}"
19134
exit 0
20135
fi
21136

137+
if [[ -f "${OVERRIDE_FILE}" ]]; then
138+
OVERRIDE_NOTES="$(sed '/^[[:space:]]*$/d' "${OVERRIDE_FILE}")"
139+
if [[ -z "${OVERRIDE_NOTES}" ]]; then
140+
fvplus::fail "Curated release note override is empty: docs/releases/${VERSION}.md"
141+
fi
142+
if [[ "${CHECK_ONLY}" == "1" ]]; then
143+
echo "Validated curated release notes for ${VERSION} from docs/releases/${VERSION}.md"
144+
exit 0
145+
fi
146+
AUTO_NOTES="${OVERRIDE_NOTES}"
147+
elif [[ "${REQUIRE_EXPLICIT}" == "1" ]]; then
148+
fvplus::fail "Explicit release notes are required for ${VERSION}. Add docs/releases/${VERSION}.md or a non-generic CHANGES block before packaging."
149+
fi
150+
151+
if [[ "${CHECK_ONLY}" == "1" ]]; then
152+
echo "Validated auto-generated CHANGES plan for ${VERSION}"
153+
exit 0
154+
fi
155+
22156
normalize_subject() {
23157
local raw="${1:-}"
24158
local cleaned
@@ -499,18 +633,13 @@ build_auto_notes() {
499633
printf '%s\n' "${notes[@]}"
500634
}
501635

502-
PREVIOUS_VERSION="$(awk '
503-
/<CHANGES>/ { in_changes = 1; next }
504-
in_changes && /^###/ {
505-
gsub(/^###/, "", $0)
506-
print
507-
exit
508-
}
509-
' "${PLG_FILE}")"
636+
PREVIOUS_VERSION="$(previous_changes_version "${VERSION}")"
510637

511-
AUTO_NOTES="$(build_auto_notes "${PREVIOUS_VERSION}")"
512-
if [[ -z "${AUTO_NOTES}" ]]; then
513-
AUTO_NOTES="- ${AUTO_FALLBACK_NOTE}"
638+
if [[ -z "${AUTO_NOTES:-}" ]]; then
639+
AUTO_NOTES="$(build_auto_notes "${PREVIOUS_VERSION}")"
640+
if [[ -z "${AUTO_NOTES}" ]]; then
641+
AUTO_NOTES="- ${AUTO_FALLBACK_NOTE}"
642+
fi
514643
fi
515644

516645
TMP_FILE="$(mktemp)"

scripts/include_order_guard.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const fs = require('fs');
1818
1919
const pageFile = process.argv[2];
2020
const source = fs.readFileSync(pageFile, 'utf8');
21-
const includes = [...source.matchAll(/folderviewplus(?:\.[a-z-]+)?\.js/g)].map((match) => match[0]);
21+
const includes = [...source.matchAll(/folderviewplus(?:\.[a-z-]+)*\.js/g)].map((match) => match[0]);
2222
2323
const expectedOrder = [
2424
'folderviewplus.fatal-banner.js',
@@ -43,6 +43,7 @@ const expectedOrder = [
4343
'folderviewplus.row-details.js',
4444
'folderviewplus.settings-health.js',
4545
'folderviewplus.settings-workspaces.js',
46+
'folderviewplus.bulk-assignment.shared.js',
4647
'folderviewplus.bulk-assignment.js',
4748
'folderviewplus.runtime-actions.js',
4849
'folderviewplus.wizard-smart-detect.js',

scripts/install_smoke.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ REQUIRED_ARCHIVE_ENTRIES=(
4747
"./usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.folder-editor.js"
4848
"./usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.settings-health.js"
4949
"./usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.settings-workspaces.js"
50+
"./usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.bulk-assignment.shared.js"
5051
"./usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.bulk-assignment.js"
5152
"./usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.runtime-actions.js"
5253
"./usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.wizard.js"
@@ -118,6 +119,7 @@ REQUIRED_FILES=(
118119
"scripts/folderviewplus.folder-editor.js"
119120
"scripts/folderviewplus.settings-health.js"
120121
"scripts/folderviewplus.settings-workspaces.js"
122+
"scripts/folderviewplus.bulk-assignment.shared.js"
121123
"scripts/folderviewplus.bulk-assignment.js"
122124
"scripts/folderviewplus.runtime-actions.js"
123125
"scripts/folderviewplus.wizard.js"

0 commit comments

Comments
 (0)