@dream-encode/secret-santa-hat-build-tools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## [NEXT_VERSION] - [UNRELEASED]
4
+ * BUG: Example fix description.
5
+
6
+ ## [0.1.0] - 2026-07-16
7
+ * FEA: Initial release. Ships the `ssh-release` command, a JavaScript-tailored port of the `gcrq` (`git_create_release_quiet`) bash flow: branch and changelog guards, version bump across `package.json` and `public/manifest.json`, `[NEXT_VERSION]` placeholder replacement, changelog finalize and re-seed, release branch and tag, GitHub release with a compare link, merge to the default branch, and a service-worker version reset. Includes a postinstall that offers to wire a `release` script into the consuming project.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Baumwald
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Secret Santa Hat Build Tools
2
+
3
+ Release tooling for the Secret Santa Hat JavaScript repositories. Ships the
4
+ `ssh-release` command, a JS-tailored port of the `gcrq`
5
+ (`git_create_release_quiet`) bash flow, so every repo releases the same way
6
+ without depending on a personal shell profile.
7
+
8
+ ## What it does
9
+
10
+ Running `ssh-release` from a repo on the `development` (or `hotfix`) branch:
11
+
12
+ 1. Verifies the branch and that `CHANGELOG.md` has a
13
+ `## [NEXT_VERSION] - [UNRELEASED]` section at the top.
14
+ 2. Bumps the version (interactive menu, or `patch`/`minor`/`major`/`hotfix`):
15
+ - `package.json`
16
+ - `public/manifest.json` (if present)
17
+ - any file containing the `[NEXT_VERSION]` placeholder
18
+ 3. Commits the bump, then rewrites the changelog `NEXT_VERSION` header to the
19
+ new version and date and commits that.
20
+ 4. Pushes, creates a `release/<version>` branch, and tags `v<version>`.
21
+ 5. Creates a GitHub release from the changelog notes and appends a
22
+ **Full Changelog** compare link.
23
+ 6. Merges the release branch into the default branch (`main`/`master`) and
24
+ pushes it.
25
+ 7. Re-seeds a fresh `NEXT_VERSION` changelog section and resets
26
+ `public/sw.js` `APP_VERSION` back to `[NEXT_VERSION]`, then returns to the
27
+ working branch.
28
+
29
+ JSON edits are done with `node` (always present in a Node project), so `jq` is
30
+ not required.
31
+
32
+ ## Requirements
33
+
34
+ - **git**
35
+ - **gh** (GitHub CLI), authenticated: `gh auth login`
36
+ - **bash** — Git Bash on Windows; already present on macOS/Linux
37
+ - **node** — provided by the project itself
38
+
39
+ Check everything with:
40
+
41
+ ```bash
42
+ ssh-release --check-tools
43
+ ```
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ # yarn (classic)
49
+ yarn add --dev @dream-encode/secret-santa-hat-build-tools
50
+
51
+ # npm
52
+ npm install --save-dev @dream-encode/secret-santa-hat-build-tools
53
+ ```
54
+
55
+ After install, wire the `release` script into the project:
56
+
57
+ ```bash
58
+ npx @dream-encode/secret-santa-hat-build-tools setup # interactive
59
+ npx @dream-encode/secret-santa-hat-build-tools setup --force # no prompts
60
+ ```
61
+
62
+ This sets `"release": "ssh-release"`. Any existing custom release script is
63
+ backed up as `release-backup`. An existing `prerelease` script is left alone.
64
+
65
+ ## `yarn release` = prerelease + release
66
+
67
+ yarn (classic) and npm run a `prerelease` script automatically before
68
+ `release`. Keep each repo's pre-flight checks in its own `prerelease` script,
69
+ and `yarn release` runs them and then the release flow:
70
+
71
+ ```jsonc
72
+ {
73
+ "scripts": {
74
+ // repo-specific pre-flight (tests, lint, build, browserslist refresh, ...)
75
+ "prerelease": "yarn maintenance && yarn test:unit",
76
+ // the shared release flow
77
+ "release": "ssh-release"
78
+ }
79
+ }
80
+ ```
81
+
82
+ ```bash
83
+ yarn release # prerelease, then interactive release
84
+ yarn release --dry-run
85
+ ```
86
+
87
+ ## Usage
88
+
89
+ ```bash
90
+ ssh-release # interactive version selection
91
+ ssh-release patch # 1.0.0 -> 1.0.1
92
+ ssh-release minor # 1.0.0 -> 1.1.0
93
+ ssh-release major # 1.0.0 -> 2.0.0
94
+ ssh-release hotfix # 1.0.0 -> 1.0.0.1
95
+ ssh-release --dry-run minor # preview without making changes
96
+ ssh-release --check-tools # verify git + gh
97
+ ssh-release --help
98
+ ```
99
+
100
+ Because `release` passes arguments through, `yarn release patch` and
101
+ `yarn release --dry-run` work too.
102
+
103
+ ## Configuration
104
+
105
+ Optional. Copy `bin/config/release.conf.example` to `bin/config/release.conf`
106
+ inside the installed package to override defaults:
107
+
108
+ - `SSH_RELEASE_MAIN_BRANCH` — the branch releases merge into (auto-detected as
109
+ `main`, then `master`, when unset).
110
+
111
+ ## Notes
112
+
113
+ - Releases are allowed only from `development` or `hotfix`.
114
+ - The version bump commit runs `git add .`, so the working tree should hold
115
+ only what you intend to ship.
116
+ - The re-seeded `NEXT_VERSION` changelog section and the `sw.js` reset are left
117
+ uncommitted on the working branch, ready for the next cycle.
@@ -0,0 +1,8 @@
1
+ # secret-santa-hat-build-tools release configuration (optional).
2
+ #
3
+ # Copy this file to release.conf in the same directory to override defaults.
4
+ # It is sourced by release.sh before the release runs.
5
+
6
+ # Branch that releases are merged into. Auto-detected (main, then master) when
7
+ # unset.
8
+ # SSH_RELEASE_MAIN_BRANCH="main"
@@ -0,0 +1,284 @@
1
+ #!/bin/bash
2
+
3
+ # General helpers for secret-santa-hat-build-tools.
4
+ #
5
+ # JSON reads/writes go through node rather than jq: node is guaranteed present
6
+ # in a Node project and preserves the 2-space formatting these repos use.
7
+
8
+ # Detect the package manager for the current project.
9
+ function get_package_manager_for_project() {
10
+ if [ -f yarn.lock ]; then
11
+ echo "yarn"
12
+ else
13
+ echo "npm"
14
+ fi
15
+ }
16
+
17
+ # Read a version field from a JSON file. Usage: get_json_version <file>
18
+ function get_json_version() {
19
+ local file="$1"
20
+
21
+ node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version || ''))" "$file" 2>/dev/null
22
+ }
23
+
24
+ # Read the project version from package.json.
25
+ function get_package_json_version() {
26
+ get_json_version "package.json"
27
+ }
28
+
29
+ # Write a version field to a JSON file, preserving 2-space formatting and a
30
+ # trailing newline. Usage: set_json_version <file> <version>
31
+ function set_json_version() {
32
+ local file="$1"
33
+ local version="$2"
34
+
35
+ node -e "const fs=require('fs');const f=process.argv[1];const v=process.argv[2];const j=JSON.parse(fs.readFileSync(f,'utf8'));j.version=v;fs.writeFileSync(f, JSON.stringify(j,null,2)+'\n');" "$file" "$version"
36
+ }
37
+
38
+ # Calculate a new version from a bump type. Supports X.X.X and X.X.X.X.
39
+ function calculate_new_version() {
40
+ local current_version="$1"
41
+ local bump_type="$2"
42
+
43
+ IFS='.' read -ra VERSION_PARTS <<< "$current_version"
44
+
45
+ local major=${VERSION_PARTS[0]}
46
+ local minor=${VERSION_PARTS[1]}
47
+ local patch=${VERSION_PARTS[2]}
48
+ local hotfix=${VERSION_PARTS[3]:-}
49
+
50
+ case $bump_type in
51
+ "patch")
52
+ patch=$((patch + 1))
53
+ hotfix=""
54
+ ;;
55
+ "minor")
56
+ minor=$((minor + 1))
57
+ patch=0
58
+ hotfix=""
59
+ ;;
60
+ "major")
61
+ major=$((major + 1))
62
+ minor=0
63
+ patch=0
64
+ hotfix=""
65
+ ;;
66
+ "hotfix")
67
+ if [ -z "$hotfix" ]; then
68
+ hotfix=1
69
+ else
70
+ hotfix=$((hotfix + 1))
71
+ fi
72
+ ;;
73
+ *)
74
+ echo "Invalid bump type: $bump_type. Use patch, minor, major, or hotfix." >&2
75
+
76
+ return 1
77
+ ;;
78
+ esac
79
+
80
+ if [ -n "$hotfix" ]; then
81
+ echo "$major.$minor.$patch.$hotfix"
82
+ else
83
+ echo "$major.$minor.$patch"
84
+ fi
85
+ }
86
+
87
+ # Interactive menu selection with fzf/whiptail/numbered fallbacks.
88
+ function interactive_menu_select() {
89
+ local prompt="$1"
90
+ shift
91
+ local options=("$@")
92
+
93
+ if command -v fzf >/dev/null 2>&1; then
94
+ local result
95
+ result=$(printf '%s\n' "${options[@]}" | fzf --height=12 --layout=reverse --border --prompt="Select: " --header="$prompt")
96
+
97
+ if [ $? -eq 0 ] && [ -n "$result" ]; then
98
+ echo "$result"
99
+
100
+ return 0
101
+ fi
102
+
103
+ return 1
104
+ fi
105
+
106
+ if command -v whiptail >/dev/null 2>&1; then
107
+ local menu_items=()
108
+ local i
109
+ for i in "${!options[@]}"; do
110
+ menu_items+=("$((i+1))" "${options[i]}")
111
+ done
112
+
113
+ local choice
114
+ choice=$(whiptail --title "Version Bump" --menu "$prompt" 18 90 8 "${menu_items[@]}" 3>&1 1>&2 2>&3)
115
+
116
+ if [ $? -eq 0 ] && [ -n "$choice" ]; then
117
+ echo "${options[$((choice-1))]}"
118
+
119
+ return 0
120
+ fi
121
+
122
+ return 1
123
+ fi
124
+
125
+ echo "$prompt" >&2
126
+ echo "" >&2
127
+
128
+ local i
129
+ for i in "${!options[@]}"; do
130
+ echo " $((i+1))) ${options[i]}" >&2
131
+ done
132
+ echo "" >&2
133
+
134
+ while true; do
135
+ read -r -p "Enter your choice (1-${#options[@]}): " choice
136
+ if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "${#options[@]}" ]; then
137
+ echo "${options[$((choice-1))]}"
138
+
139
+ return 0
140
+ fi
141
+
142
+ echo "Invalid choice. Please enter a number between 1 and ${#options[@]}." >&2
143
+ done
144
+ }
145
+
146
+ # Changelog helpers.
147
+ function changelog_exists() {
148
+ [ -f "CHANGELOG.md" ]
149
+ }
150
+
151
+ # True when CHANGELOG.md has a NEXT_VERSION / UNRELEASED section at the top.
152
+ function changelog_check_next_version() {
153
+ if changelog_exists && grep -q "## \[NEXT_VERSION\] - \[UNRELEASED\]" CHANGELOG.md; then
154
+ return 0
155
+ fi
156
+
157
+ return 1
158
+ }
159
+
160
+ # Extract the bullet notes for a version from CHANGELOG.md, as GitHub release
161
+ # body. Supports X.X.X and X.X.X.X.
162
+ function extract_version_updates_from_changelog() {
163
+ local RELEASE_VERSION="$1"
164
+ local CHANGELOG_FILE="CHANGELOG.md"
165
+
166
+ if [ ! -f "$CHANGELOG_FILE" ]; then
167
+ return 1
168
+ fi
169
+
170
+ awk -v ver="$RELEASE_VERSION" '
171
+ $0 ~ "^## \\[" ver "\\]" { print "## Changelog"; version_found=1; next }
172
+ /^## \[/ && version_found { exit }
173
+ version_found && /^\*/' "$CHANGELOG_FILE"
174
+ }
175
+
176
+ # Replace the NEXT_VERSION section header with the released version and date,
177
+ # then commit that changelog change.
178
+ function changelog_update_current_version() {
179
+ local CURRENT_VERSION="${1:-$(get_package_json_version)}"
180
+
181
+ if changelog_exists && grep -q "## \[NEXT_VERSION\]" CHANGELOG.md; then
182
+ local CURRENT_DATE
183
+ CURRENT_DATE=$(date +%Y-%m-%d)
184
+
185
+ sed_inplace "s/^## \[NEXT_VERSION\] - \[UNRELEASED\]$/## [$CURRENT_VERSION] - $CURRENT_DATE/" CHANGELOG.md
186
+
187
+ git add CHANGELOG.md >/dev/null 2>&1
188
+ git commit -m "Update changelog for v$CURRENT_VERSION" >/dev/null 2>&1
189
+ fi
190
+ }
191
+
192
+ # Re-seed a fresh NEXT_VERSION section above the just-released version.
193
+ function changelog_add_next_version_template() {
194
+ local CURRENT_VERSION=""
195
+ local QUIET_MODE="false"
196
+
197
+ while [[ $# -gt 0 ]]; do
198
+ case $1 in
199
+ --quiet)
200
+ QUIET_MODE="true"
201
+ shift
202
+ ;;
203
+ --version)
204
+ CURRENT_VERSION="$2"
205
+ shift 2
206
+ ;;
207
+ *)
208
+ echo "❌ Error: unknown argument '$1' for changelog_add_next_version_template" >&2
209
+
210
+ return 1
211
+ ;;
212
+ esac
213
+ done
214
+
215
+ if [ -z "$CURRENT_VERSION" ]; then
216
+ CURRENT_VERSION=$(get_package_json_version)
217
+ fi
218
+
219
+ if changelog_exists; then
220
+ sed_inplace "s/^## \[$CURRENT_VERSION\]/## [NEXT_VERSION] - [UNRELEASED]\n* BUG: Example fix description.\n\n## [$CURRENT_VERSION]/" CHANGELOG.md
221
+
222
+ if [ "$QUIET_MODE" != "true" ]; then
223
+ echo "✅ Template changelog entry added to CHANGELOG.md"
224
+ fi
225
+ fi
226
+ }
227
+
228
+ # Replace [NEXT_VERSION] placeholders across project files (skipping the
229
+ # changelog, shell scripts, binaries, and dependency directories).
230
+ function replace_next_version_placeholders() {
231
+ local new_version="$1"
232
+
233
+ if ! command -v grep >/dev/null 2>&1; then
234
+ return 0
235
+ fi
236
+
237
+ local files
238
+ files=$(grep -r -l "\[NEXT_VERSION\]" . \
239
+ --exclude-dir=node_modules \
240
+ --exclude-dir=vendor \
241
+ --exclude-dir=.git \
242
+ --exclude-dir=dist \
243
+ --exclude-dir=build \
244
+ --exclude="*.zip" \
245
+ --exclude="*.tar.gz" \
246
+ --exclude="*.jpg" \
247
+ --exclude="*.jpeg" \
248
+ --exclude="*.png" \
249
+ --exclude="*.gif" \
250
+ --exclude="*.ico" \
251
+ --exclude="*.svg" \
252
+ --exclude="*.webp" \
253
+ --exclude="*.pdf" \
254
+ --exclude="*.woff" \
255
+ --exclude="*.woff2" \
256
+ --exclude="*.ttf" \
257
+ --exclude="*.eot" \
258
+ --exclude="*.mp4" \
259
+ --exclude="*.mp3" \
260
+ --exclude="*.wav" \
261
+ --exclude="*.lock" \
262
+ --exclude="*.sh" \
263
+ --exclude="CHANGELOG.md" \
264
+ 2>/dev/null || true)
265
+
266
+ if [ -n "$files" ]; then
267
+ echo "$files" | while IFS= read -r file; do
268
+ if [ -f "$file" ]; then
269
+ sed_inplace "s/\[NEXT_VERSION\]/$new_version/g" "$file"
270
+ fi
271
+ done
272
+
273
+ echo " - Replaced [NEXT_VERSION] with $new_version in files."
274
+ fi
275
+ }
276
+
277
+ # Reset the service worker APP_VERSION back to the [NEXT_VERSION] template.
278
+ function update_service_worker_next_version_template() {
279
+ if [ -f "public/sw.js" ]; then
280
+ sed_inplace "s/const APP_VERSION *= *'[^']*'/const APP_VERSION = '[NEXT_VERSION]'/g" public/sw.js
281
+
282
+ echo " - Updated service worker version template."
283
+ fi
284
+ }
@@ -0,0 +1,97 @@
1
+ #!/bin/bash
2
+
3
+ # Git and GitHub helpers for secret-santa-hat-build-tools.
4
+
5
+ # True when the current directory is inside a git repository.
6
+ function is_git_repo() {
7
+ git rev-parse --is-inside-work-tree >/dev/null 2>&1
8
+ }
9
+
10
+ # Print the current branch name.
11
+ function get_current_branch() {
12
+ git branch --show-current
13
+ }
14
+
15
+ # Determine the default branch to merge releases into. Honours an override from
16
+ # release.conf (SSH_RELEASE_MAIN_BRANCH), otherwise prefers main, then master.
17
+ function get_default_branch() {
18
+ if [ -n "${SSH_RELEASE_MAIN_BRANCH:-}" ]; then
19
+ echo "$SSH_RELEASE_MAIN_BRANCH"
20
+
21
+ return 0
22
+ fi
23
+
24
+ if git show-ref --verify --quiet refs/heads/main; then
25
+ echo "main"
26
+ elif git show-ref --verify --quiet refs/heads/master; then
27
+ echo "master"
28
+ else
29
+ echo "main"
30
+ fi
31
+ }
32
+
33
+ # Print owner/name for the current repo via the GitHub CLI.
34
+ function get_github_repo_info() {
35
+ gh repo view --json nameWithOwner -q '.nameWithOwner' 2>/dev/null
36
+ }
37
+
38
+ # Create a GitHub release for the given version, using changelog notes.
39
+ function github_create_release() {
40
+ local VERSION="$1"
41
+
42
+ if changelog_exists; then
43
+ local RELEASE_NOTES
44
+ RELEASE_NOTES=$(extract_version_updates_from_changelog "$VERSION")
45
+
46
+ if ! gh release create "v$VERSION" -n "$RELEASE_NOTES" -t "v$VERSION"; then
47
+ echo "Error: failed to create GitHub release v$VERSION" >&2
48
+
49
+ return 1
50
+ fi
51
+ else
52
+ if ! gh release create "v$VERSION" -t "v$VERSION" --generate-notes; then
53
+ echo "Error: failed to create GitHub release v$VERSION" >&2
54
+
55
+ return 1
56
+ fi
57
+ fi
58
+ }
59
+
60
+ # Generate a GitHub compare URL between two tags.
61
+ function github_generate_tags_compare_link() {
62
+ local prev_tag="$1"
63
+ local current_tag="$2"
64
+ local repo
65
+ repo=$(get_github_repo_info)
66
+
67
+ echo "https://github.com/${repo}/compare/${prev_tag}...${current_tag}"
68
+ }
69
+
70
+ # Append a "Full Changelog" compare link to an existing GitHub release.
71
+ function github_release_add_compare_link() {
72
+ local current_version="$1"
73
+ local current_tag="v${current_version}"
74
+ local prev_tag
75
+ prev_tag=$(git tag -l --sort=-version:refname "v*" | sed -n '2p')
76
+
77
+ if [ -z "$prev_tag" ]; then
78
+ return 0
79
+ fi
80
+
81
+ local compare_url
82
+ compare_url=$(github_generate_tags_compare_link "$prev_tag" "$current_tag")
83
+
84
+ local display="${prev_tag}...${current_tag}"
85
+ local compare_line="**Full Changelog:** [\`${display}\`](${compare_url})"
86
+
87
+ local existing_notes
88
+ existing_notes=$(gh release view "$current_tag" --json body -q '.body' 2>/dev/null)
89
+
90
+ local updated_notes="${existing_notes}"$'\n\n'"${compare_line}"
91
+
92
+ if ! gh release edit "$current_tag" --notes "$updated_notes"; then
93
+ echo "Warning: failed to add compare link to release $current_tag" >&2
94
+
95
+ return 1
96
+ fi
97
+ }
@@ -0,0 +1,43 @@
1
+ #!/bin/bash
2
+
3
+ # Cross-platform helpers for secret-santa-hat-build-tools.
4
+
5
+ # Detect the current platform.
6
+ function get_platform() {
7
+ case "$OSTYPE" in
8
+ msys*|cygwin*|mingw*)
9
+ echo "windows"
10
+ ;;
11
+ darwin*)
12
+ echo "macos"
13
+ ;;
14
+ linux*)
15
+ echo "linux"
16
+ ;;
17
+ *)
18
+ echo "unknown"
19
+ ;;
20
+ esac
21
+ }
22
+
23
+ # File existence check.
24
+ function file_exists() {
25
+ [ -f "$1" ]
26
+ }
27
+
28
+ # Command existence check.
29
+ function command_exists() {
30
+ command -v "$1" >/dev/null 2>&1
31
+ }
32
+
33
+ # Cross-platform sed in-place editing.
34
+ function sed_inplace() {
35
+ local pattern="$1"
36
+ local file="$2"
37
+
38
+ if [[ "$(get_platform)" == "macos" ]]; then
39
+ sed -i '' "$pattern" "$file"
40
+ else
41
+ sed -i "$pattern" "$file"
42
+ fi
43
+ }