its-magic 0.1.2-10
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/.cursor/agents/curator.mdc +21 -0
- package/.cursor/agents/dev.mdc +20 -0
- package/.cursor/agents/po.mdc +19 -0
- package/.cursor/agents/qa.mdc +19 -0
- package/.cursor/agents/release.mdc +19 -0
- package/.cursor/agents/tech-lead.mdc +21 -0
- package/.cursor/commands/architecture.md +29 -0
- package/.cursor/commands/auto.md +27 -0
- package/.cursor/commands/discovery.md +27 -0
- package/.cursor/commands/execute.md +32 -0
- package/.cursor/commands/intake.md +28 -0
- package/.cursor/commands/map-codebase.md +25 -0
- package/.cursor/commands/milestone-complete.md +24 -0
- package/.cursor/commands/milestone-start.md +26 -0
- package/.cursor/commands/pause.md +25 -0
- package/.cursor/commands/phase-context.md +25 -0
- package/.cursor/commands/plan-verify.md +26 -0
- package/.cursor/commands/qa.md +28 -0
- package/.cursor/commands/quick.md +24 -0
- package/.cursor/commands/refresh-context.md +26 -0
- package/.cursor/commands/release.md +29 -0
- package/.cursor/commands/research.md +28 -0
- package/.cursor/commands/resume.md +26 -0
- package/.cursor/commands/sprint-plan.md +30 -0
- package/.cursor/commands/verify-work.md +25 -0
- package/.cursor/hooks/README.md +13 -0
- package/.cursor/hooks/hook.py +197 -0
- package/.cursor/hooks.json +26 -0
- package/.cursor/plans/cursor-gsd-team-kit_8cfee9b8.plan.md +57 -0
- package/.cursor/remote.json +18 -0
- package/.cursor/rules/core.mdc +18 -0
- package/.cursor/rules/escalation.mdc +11 -0
- package/.cursor/rules/handoffs.mdc +10 -0
- package/.cursor/rules/quality.mdc +15 -0
- package/.cursor/scratchpad.md +34 -0
- package/.cursor/skills/its-magic/SKILL.md +39 -0
- package/.cursor/skills/its-magic/templates/acceptance.json +10 -0
- package/.cursor/skills/its-magic/templates/acceptance.md +7 -0
- package/.cursor/skills/its-magic/templates/architecture.json +11 -0
- package/.cursor/skills/its-magic/templates/architecture.md +14 -0
- package/.cursor/skills/its-magic/templates/decision.json +14 -0
- package/.cursor/skills/its-magic/templates/decision.md +19 -0
- package/.cursor/skills/its-magic/templates/handoff.json +6 -0
- package/.cursor/skills/its-magic/templates/handoff.md +12 -0
- package/.cursor/skills/its-magic/templates/milestone.json +7 -0
- package/.cursor/skills/its-magic/templates/phase-context.json +6 -0
- package/.cursor/skills/its-magic/templates/plan-verify.json +11 -0
- package/.cursor/skills/its-magic/templates/sprint.json +6 -0
- package/.cursor/skills/its-magic/templates/sprint.md +11 -0
- package/.cursor/skills/its-magic/templates/story.json +9 -0
- package/.cursor/skills/its-magic/templates/story.md +15 -0
- package/.cursor/skills/its-magic/templates/uat.json +15 -0
- package/.github/workflows/ci.yml +49 -0
- package/.github/workflows/deploy.yml +56 -0
- package/README.md +755 -0
- package/bin/its-magic.js +86 -0
- package/decisions/DEC-0001.md +21 -0
- package/decisions/DEC-0002.md +21 -0
- package/docs/engineering/architecture.md +354 -0
- package/docs/engineering/codebase-map.md +14 -0
- package/docs/engineering/context/phase-template.json +6 -0
- package/docs/engineering/decisions.md +6 -0
- package/docs/engineering/dependencies.json +5 -0
- package/docs/engineering/research.md +11 -0
- package/docs/engineering/runbook.md +32 -0
- package/docs/engineering/state.md +33 -0
- package/docs/product/acceptance.md +6 -0
- package/docs/product/backlog.md +7 -0
- package/docs/product/vision.md +46 -0
- package/handoffs/dev_to_qa.md +8 -0
- package/handoffs/po_to_tl.md +8 -0
- package/handoffs/qa_to_dev.md +6 -0
- package/handoffs/release_notes.md +14 -0
- package/handoffs/resume_brief.md +8 -0
- package/handoffs/tl_to_dev.md +7 -0
- package/installer.ps1 +189 -0
- package/installer.py +195 -0
- package/installer.sh +201 -0
- package/milestones/M0001/milestone.json +7 -0
- package/milestones/M0001/phases.json +9 -0
- package/milestones/M0001/progress.md +3 -0
- package/milestones/M0001/summary.md +3 -0
- package/package.json +38 -0
- package/scripts/generate-release-notes.ps1 +74 -0
- package/scripts/generate-release-notes.sh +63 -0
- package/scripts/release-all.ps1 +423 -0
- package/scripts/release-all.sh +226 -0
- package/sprints/S0001/plan-verify.json +5 -0
- package/sprints/S0001/progress.md +4 -0
- package/sprints/S0001/qa-findings.md +113 -0
- package/sprints/S0001/sprint.md +70 -0
- package/sprints/S0001/summary.md +46 -0
- package/sprints/S0001/tasks.md +35 -0
- package/sprints/S0001/uat.json +8 -0
- package/sprints/S0001/uat.md +8 -0
- package/sprints/quick/Q0001/summary.md +3 -0
- package/sprints/quick/Q0001/task.json +6 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Param(
|
|
2
|
+
[string]$RepoRoot,
|
|
3
|
+
[string]$Version = "v0.0.0",
|
|
4
|
+
[string]$Sprint = "S0001"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
$ErrorActionPreference = "Stop"
|
|
8
|
+
|
|
9
|
+
function Resolve-RepoRoot {
|
|
10
|
+
if ($RepoRoot) { return (Resolve-Path $RepoRoot).Path }
|
|
11
|
+
return (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function Read-IfExists($Path) {
|
|
15
|
+
if (Test-Path $Path -PathType Leaf) {
|
|
16
|
+
return Get-Content -Path $Path -Raw
|
|
17
|
+
}
|
|
18
|
+
return ""
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
$root = Resolve-RepoRoot
|
|
22
|
+
$summaryPath = Join-Path $root "sprints\$Sprint\summary.md"
|
|
23
|
+
$qaPath = Join-Path $root "sprints\$Sprint\qa-findings.md"
|
|
24
|
+
$runbookPath = Join-Path $root "docs\engineering\runbook.md"
|
|
25
|
+
$outPath = Join-Path $root "handoffs\release_notes.md"
|
|
26
|
+
|
|
27
|
+
$summary = Read-IfExists $summaryPath
|
|
28
|
+
$qa = Read-IfExists $qaPath
|
|
29
|
+
$runbook = Read-IfExists $runbookPath
|
|
30
|
+
|
|
31
|
+
$gitChanges = ""
|
|
32
|
+
if (Get-Command git -ErrorAction SilentlyContinue) {
|
|
33
|
+
try {
|
|
34
|
+
$isRepo = git -C $root rev-parse --is-inside-work-tree 2>$null
|
|
35
|
+
if ($isRepo -eq "true") {
|
|
36
|
+
$gitChanges = git -C $root log -n 20 --pretty=format:"- %s"
|
|
37
|
+
}
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
$timestamp = (Get-Date).ToString("yyyy-MM-dd")
|
|
42
|
+
|
|
43
|
+
@"
|
|
44
|
+
# Release Notes — $Version
|
|
45
|
+
|
|
46
|
+
**Sprint:** $Sprint
|
|
47
|
+
**Date:** $timestamp
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Summary
|
|
52
|
+
|
|
53
|
+
${summary.Trim()}
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Changes (last 20 commits)
|
|
58
|
+
|
|
59
|
+
${gitChanges.Trim()}
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## QA Findings (from sprint)
|
|
64
|
+
|
|
65
|
+
${qa.Trim()}
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Runbook Notes
|
|
70
|
+
|
|
71
|
+
${runbook.Trim()}
|
|
72
|
+
"@ | Set-Content -Path $outPath
|
|
73
|
+
|
|
74
|
+
Write-Host "Release notes written to: $outPath"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
ROOT="${1:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
5
|
+
VERSION="${2:-v0.0.0}"
|
|
6
|
+
SPRINT="${3:-S0001}"
|
|
7
|
+
|
|
8
|
+
SUMMARY_PATH="$ROOT/sprints/$SPRINT/summary.md"
|
|
9
|
+
QA_PATH="$ROOT/sprints/$SPRINT/qa-findings.md"
|
|
10
|
+
RUNBOOK_PATH="$ROOT/docs/engineering/runbook.md"
|
|
11
|
+
OUT_PATH="$ROOT/handoffs/release_notes.md"
|
|
12
|
+
|
|
13
|
+
read_if_exists() {
|
|
14
|
+
if [ -f "$1" ]; then
|
|
15
|
+
cat "$1"
|
|
16
|
+
fi
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
summary="$(read_if_exists "$SUMMARY_PATH")"
|
|
20
|
+
qa="$(read_if_exists "$QA_PATH")"
|
|
21
|
+
runbook="$(read_if_exists "$RUNBOOK_PATH")"
|
|
22
|
+
|
|
23
|
+
git_changes=""
|
|
24
|
+
if command -v git >/dev/null 2>&1; then
|
|
25
|
+
if git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
26
|
+
git_changes="$(git -C "$ROOT" log -n 20 --pretty=format:"- %s")"
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
timestamp="$(date +"%Y-%m-%d")"
|
|
31
|
+
|
|
32
|
+
cat > "$OUT_PATH" <<EOF
|
|
33
|
+
# Release Notes — $VERSION
|
|
34
|
+
|
|
35
|
+
**Sprint:** $SPRINT
|
|
36
|
+
**Date:** $timestamp
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Summary
|
|
41
|
+
|
|
42
|
+
$summary
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Changes (last 20 commits)
|
|
47
|
+
|
|
48
|
+
$git_changes
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## QA Findings (from sprint)
|
|
53
|
+
|
|
54
|
+
$qa
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Runbook Notes
|
|
59
|
+
|
|
60
|
+
$runbook
|
|
61
|
+
EOF
|
|
62
|
+
|
|
63
|
+
echo "Release notes written to: $OUT_PATH"
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Unified release: npm + Chocolatey + Homebrew (all three at once).
|
|
4
|
+
.DESCRIPTION
|
|
5
|
+
1. Bumps version in package.json (patch|minor|major|explicit)
|
|
6
|
+
2. Publishes to npm
|
|
7
|
+
3. Creates a GitHub release with a source zip (for Homebrew/Chocolatey URLs)
|
|
8
|
+
4. Updates Chocolatey nuspec + pushes to chocolatey.org
|
|
9
|
+
5. Updates Homebrew formula with new URL + sha256
|
|
10
|
+
.PARAMETER Bump
|
|
11
|
+
Version bump type: patch, minor, major, or an explicit semver (e.g. 1.2.3).
|
|
12
|
+
Default: patch
|
|
13
|
+
.PARAMETER NpmTag
|
|
14
|
+
npm dist-tag (e.g. latest, beta, rc). Default: latest
|
|
15
|
+
.PARAMETER SkipNpm
|
|
16
|
+
Skip npm publish.
|
|
17
|
+
.PARAMETER SkipChoco
|
|
18
|
+
Skip Chocolatey push.
|
|
19
|
+
.PARAMETER SkipBrew
|
|
20
|
+
Skip Homebrew formula update.
|
|
21
|
+
.PARAMETER DryRun
|
|
22
|
+
Print what would happen without executing.
|
|
23
|
+
#>
|
|
24
|
+
param(
|
|
25
|
+
[string]$Bump = "patch",
|
|
26
|
+
[string]$NpmTag = "latest",
|
|
27
|
+
[switch]$SkipNpm,
|
|
28
|
+
[switch]$SkipChoco,
|
|
29
|
+
[switch]$SkipBrew,
|
|
30
|
+
[switch]$DryRun,
|
|
31
|
+
[switch]$SkipBrewPush,
|
|
32
|
+
[string]$BrewTapRepo = "",
|
|
33
|
+
[string]$BrewTapBranch = "main",
|
|
34
|
+
[string]$BrewTapDir = "",
|
|
35
|
+
[bool]$CreateBrewTapIfMissing = $true
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
$ErrorActionPreference = 'Stop'
|
|
39
|
+
$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
|
|
40
|
+
if (-not (Test-Path (Join-Path $repoRoot 'package.json'))) {
|
|
41
|
+
$repoRoot = Split-Path -Parent $PSScriptRoot
|
|
42
|
+
}
|
|
43
|
+
Push-Location $repoRoot
|
|
44
|
+
|
|
45
|
+
# ── Helpers ──────────────────────────────────────────────────────────
|
|
46
|
+
function Log($msg) { Write-Host "[release] $msg" -ForegroundColor Cyan }
|
|
47
|
+
function Warn($msg){ Write-Host "[release] $msg" -ForegroundColor Yellow }
|
|
48
|
+
function Err($msg) { Write-Host "[release] $msg" -ForegroundColor Red; Pop-Location; exit 1 }
|
|
49
|
+
|
|
50
|
+
# Quote char for building regex patterns (avoids PS 5.1 parser issues)
|
|
51
|
+
$DQ = [char]34
|
|
52
|
+
$chocoFailed = $false
|
|
53
|
+
$brewTapFailed = $false
|
|
54
|
+
$brewTapPushed = $false
|
|
55
|
+
|
|
56
|
+
# ── 1. Version bump ─────────────────────────────────────────────────
|
|
57
|
+
Log "Bumping version ($Bump) ..."
|
|
58
|
+
if ($DryRun) {
|
|
59
|
+
Log "(dry-run) would run: npm version $Bump --no-git-tag-version"
|
|
60
|
+
$newVersion = "0.0.0-dryrun"
|
|
61
|
+
} else {
|
|
62
|
+
npm version $Bump --no-git-tag-version | Out-Null
|
|
63
|
+
$pkg = Get-Content package.json -Raw | ConvertFrom-Json
|
|
64
|
+
$newVersion = $pkg.version
|
|
65
|
+
}
|
|
66
|
+
Log "New version: $newVersion"
|
|
67
|
+
|
|
68
|
+
# ── 2. npm publish ───────────────────────────────────────────────────
|
|
69
|
+
if (-not $SkipNpm) {
|
|
70
|
+
Log "Publishing to npm (tag=$NpmTag) ..."
|
|
71
|
+
if ($DryRun) {
|
|
72
|
+
Log "(dry-run) would run: npm publish --tag $NpmTag"
|
|
73
|
+
} else {
|
|
74
|
+
npm publish --tag $NpmTag
|
|
75
|
+
if ($LASTEXITCODE -ne 0) { Err "npm publish failed" }
|
|
76
|
+
Log "npm publish OK"
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
Warn "Skipping npm"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# ── 3. GitHub release (needed by Homebrew + Chocolatey) ──────────────
|
|
83
|
+
$ghAvailable = $null -ne (Get-Command gh -ErrorAction SilentlyContinue)
|
|
84
|
+
$tagName = "v$newVersion"
|
|
85
|
+
$zipUrl = ""
|
|
86
|
+
$tarUrl = ""
|
|
87
|
+
|
|
88
|
+
$isPrerelease = $newVersion -match '-'
|
|
89
|
+
$chocoVersion = $newVersion
|
|
90
|
+
if ($newVersion -match '^(\d+\.\d+\.\d+)-(\d+)$') {
|
|
91
|
+
# Old Chocolatey/NuGet rejects numeric-only prerelease labels (e.g. 0.1.1-1).
|
|
92
|
+
# Convert to a compatible label for nuspec only.
|
|
93
|
+
$chocoVersion = "$($Matches[1])-beta$($Matches[2])"
|
|
94
|
+
}
|
|
95
|
+
if ($chocoVersion -ne $newVersion) {
|
|
96
|
+
Log "Chocolatey version normalized: $newVersion -> $chocoVersion"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if ($ghAvailable) {
|
|
100
|
+
Log "Creating GitHub release $tagName ..."
|
|
101
|
+
if ($DryRun) {
|
|
102
|
+
Log "(dry-run) would run: gh release create $tagName --generate-notes"
|
|
103
|
+
$zipUrl = "https://github.com/USER/its-magic/archive/refs/tags/$tagName.zip"
|
|
104
|
+
$tarUrl = "https://github.com/USER/its-magic/archive/refs/tags/$tagName.tar.gz"
|
|
105
|
+
} else {
|
|
106
|
+
$ghArgs = @("release", "create", $tagName, "--generate-notes", "--title", $tagName)
|
|
107
|
+
if ($isPrerelease) { $ghArgs += "--prerelease" }
|
|
108
|
+
& gh @ghArgs
|
|
109
|
+
if ($LASTEXITCODE -ne 0) { Warn "gh release create failed - continuing anyway" }
|
|
110
|
+
# Derive archive URL from current remote
|
|
111
|
+
$remoteUrl = git remote get-url origin 2>$null
|
|
112
|
+
if ($remoteUrl -match 'github\.com[:/](.+?)(\.git)?$') {
|
|
113
|
+
$repoSlug = $Matches[1]
|
|
114
|
+
$zipUrl = "https://github.com/$repoSlug/archive/refs/tags/$tagName.zip"
|
|
115
|
+
$tarUrl = "https://github.com/$repoSlug/archive/refs/tags/$tagName.tar.gz"
|
|
116
|
+
}
|
|
117
|
+
Log "GitHub release created: $tagName"
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
Warn "gh CLI not found - skipping GitHub release"
|
|
121
|
+
$zipUrl = "https://github.com/USER/its-magic/archive/refs/tags/$tagName.zip"
|
|
122
|
+
$tarUrl = "https://github.com/USER/its-magic/archive/refs/tags/$tagName.tar.gz"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# ── 4. Chocolatey ───────────────────────────────────────────────────
|
|
126
|
+
if (-not $SkipChoco) {
|
|
127
|
+
$chocoDir = Join-Path $repoRoot 'packaging\chocolatey'
|
|
128
|
+
$nuspec = Join-Path $chocoDir 'its-magic.nuspec'
|
|
129
|
+
$chocoInstall = Join-Path $chocoDir 'tools\chocolateyInstall.ps1'
|
|
130
|
+
|
|
131
|
+
if (Test-Path $nuspec) {
|
|
132
|
+
Log "Updating Chocolatey nuspec to $chocoVersion ..."
|
|
133
|
+
if (-not $DryRun) {
|
|
134
|
+
# Update version in nuspec
|
|
135
|
+
$xml = [xml](Get-Content $nuspec -Raw)
|
|
136
|
+
$xml.package.metadata.version = $chocoVersion
|
|
137
|
+
$xml.Save($nuspec)
|
|
138
|
+
|
|
139
|
+
# Update URL in install script
|
|
140
|
+
$installContent = Get-Content $chocoInstall -Raw
|
|
141
|
+
if ($zipUrl) {
|
|
142
|
+
$installContent = $installContent -replace "\`$url\s*=\s*'[^']+'", "`$url = '$zipUrl'"
|
|
143
|
+
}
|
|
144
|
+
Set-Content -Path $chocoInstall -Value $installContent
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# Compute checksum if we have the zip
|
|
148
|
+
if ($zipUrl -and -not $DryRun) {
|
|
149
|
+
Log "Downloading zip for checksum ..."
|
|
150
|
+
$tmpZip = Join-Path $env:TEMP "its-magic-$tagName.zip"
|
|
151
|
+
try {
|
|
152
|
+
Invoke-WebRequest -Uri $zipUrl -OutFile $tmpZip -UseBasicParsing
|
|
153
|
+
$sha = (Get-FileHash -Path $tmpZip -Algorithm SHA256).Hash.ToLower()
|
|
154
|
+
$installContent = Get-Content $chocoInstall -Raw
|
|
155
|
+
$installContent = $installContent -replace "\`$checksum\s*=\s*'[^']+'", "`$checksum = '$sha'"
|
|
156
|
+
Set-Content -Path $chocoInstall -Value $installContent
|
|
157
|
+
Remove-Item $tmpZip -Force
|
|
158
|
+
Log "Checksum: $sha"
|
|
159
|
+
} catch {
|
|
160
|
+
Warn "Could not download zip for checksum - set PLACEHOLDER manually"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
$chocoAvailable = $null -ne (Get-Command choco -ErrorAction SilentlyContinue)
|
|
165
|
+
if ($chocoAvailable) {
|
|
166
|
+
Log "Packing + pushing Chocolatey package ..."
|
|
167
|
+
if ($DryRun) {
|
|
168
|
+
Log "(dry-run) would run: choco pack + choco push"
|
|
169
|
+
} else {
|
|
170
|
+
Push-Location $chocoDir
|
|
171
|
+
choco pack
|
|
172
|
+
if ($LASTEXITCODE -ne 0) {
|
|
173
|
+
$chocoFailed = $true
|
|
174
|
+
Warn "choco pack failed"
|
|
175
|
+
} else {
|
|
176
|
+
$nupkg = Get-ChildItem "*.nupkg" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
|
177
|
+
if ($nupkg) {
|
|
178
|
+
choco push $nupkg.Name --source https://push.chocolatey.org/
|
|
179
|
+
if ($LASTEXITCODE -ne 0) {
|
|
180
|
+
$chocoFailed = $true
|
|
181
|
+
Warn "choco push failed"
|
|
182
|
+
}
|
|
183
|
+
else { Log "Chocolatey push OK" }
|
|
184
|
+
} else {
|
|
185
|
+
$chocoFailed = $true
|
|
186
|
+
Warn "No .nupkg created by choco pack"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
Pop-Location
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
$chocoFailed = $true
|
|
193
|
+
Warn "choco not found - nuspec updated but not pushed. Run choco pack and choco push manually."
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
$chocoFailed = $true
|
|
197
|
+
Warn "nuspec not found at $nuspec - skipping Chocolatey"
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
Warn "Skipping Chocolatey"
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# ── 5. Homebrew ─────────────────────────────────────────────────────
|
|
204
|
+
if (-not $SkipBrew) {
|
|
205
|
+
# Detect pre-release: anything with a hyphen
|
|
206
|
+
if ($isPrerelease) {
|
|
207
|
+
$formulaPath = Join-Path $repoRoot 'packaging\homebrew\its-magic-beta.rb'
|
|
208
|
+
Log "Pre-release detected - using beta formula"
|
|
209
|
+
} else {
|
|
210
|
+
$formulaPath = Join-Path $repoRoot 'packaging\homebrew\its-magic.rb'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (Test-Path $formulaPath) {
|
|
214
|
+
$formulaName = Split-Path -Leaf $formulaPath
|
|
215
|
+
Log "Updating Homebrew formula ($formulaName) to $newVersion ..."
|
|
216
|
+
if (-not $DryRun) {
|
|
217
|
+
$formula = Get-Content $formulaPath -Raw
|
|
218
|
+
|
|
219
|
+
# Update version in URL
|
|
220
|
+
if ($tarUrl) {
|
|
221
|
+
$urlPattern = 'url ' + $DQ + 'https://github\.com/[^' + $DQ + ']+\.tar\.gz' + $DQ
|
|
222
|
+
$urlReplace = 'url ' + $DQ + $tarUrl + $DQ
|
|
223
|
+
$formula = $formula -replace $urlPattern, $urlReplace
|
|
224
|
+
} else {
|
|
225
|
+
$formula = $formula -replace 'vVERSION', $tagName
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Update explicit version line for beta formula
|
|
229
|
+
if ($isPrerelease) {
|
|
230
|
+
$verPattern = 'version ' + $DQ + '[^' + $DQ + ']*' + $DQ
|
|
231
|
+
$verReplace = 'version ' + $DQ + $newVersion + $DQ
|
|
232
|
+
$formula = $formula -replace $verPattern, $verReplace
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# Compute tar.gz sha256 if possible
|
|
236
|
+
if ($tarUrl) {
|
|
237
|
+
$tmpTar = Join-Path $env:TEMP "its-magic-$tagName.tar.gz"
|
|
238
|
+
try {
|
|
239
|
+
Invoke-WebRequest -Uri $tarUrl -OutFile $tmpTar -UseBasicParsing
|
|
240
|
+
$sha = (Get-FileHash -Path $tmpTar -Algorithm SHA256).Hash.ToLower()
|
|
241
|
+
$shaPattern = 'sha256 ' + $DQ + '[^' + $DQ + ']*' + $DQ
|
|
242
|
+
$shaReplace = 'sha256 ' + $DQ + $sha + $DQ
|
|
243
|
+
$formula = $formula -replace $shaPattern, $shaReplace
|
|
244
|
+
Remove-Item $tmpTar -Force
|
|
245
|
+
Log "Homebrew sha256: $sha"
|
|
246
|
+
} catch {
|
|
247
|
+
Warn "Could not download tar.gz - set sha256 manually in the formula"
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
Set-Content -Path $formulaPath -Value $formula
|
|
252
|
+
Log "Homebrew formula updated: $formulaName"
|
|
253
|
+
if ($isPrerelease) {
|
|
254
|
+
Log "Users install beta with: brew install USER/tap/its-magic-beta"
|
|
255
|
+
} else {
|
|
256
|
+
Log "Users install stable with: brew install USER/tap/its-magic"
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
$formulaName = Split-Path -Leaf $formulaPath
|
|
260
|
+
Log "(dry-run) would update $formulaName"
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
Warn "Formula not found at $formulaPath - skipping Homebrew"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Optional: publish formulas to Homebrew tap repository
|
|
267
|
+
if (-not $SkipBrewPush) {
|
|
268
|
+
if ($DryRun) {
|
|
269
|
+
Log "(dry-run) would publish formulas to Homebrew tap"
|
|
270
|
+
} else {
|
|
271
|
+
$gitAvailable = $null -ne (Get-Command git -ErrorAction SilentlyContinue)
|
|
272
|
+
$ghAvailableForTap = $null -ne (Get-Command gh -ErrorAction SilentlyContinue)
|
|
273
|
+
if (-not $gitAvailable) {
|
|
274
|
+
$brewTapFailed = $true
|
|
275
|
+
Warn "git not found - cannot push Homebrew formulas to tap"
|
|
276
|
+
} else {
|
|
277
|
+
if (-not $BrewTapRepo) {
|
|
278
|
+
$originUrl = git remote get-url origin 2>$null
|
|
279
|
+
if ($originUrl -match 'github\.com[:/](.+?)/(.+?)(\.git)?$') {
|
|
280
|
+
$owner = $Matches[1]
|
|
281
|
+
$BrewTapRepo = "$owner/homebrew-tap"
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (-not $BrewTapRepo) {
|
|
286
|
+
$brewTapFailed = $true
|
|
287
|
+
Warn "Could not infer Homebrew tap repo. Pass -BrewTapRepo owner/homebrew-tap"
|
|
288
|
+
} else {
|
|
289
|
+
if (-not $BrewTapDir) {
|
|
290
|
+
$safeTap = ($BrewTapRepo -replace '[^a-zA-Z0-9_-]', '-')
|
|
291
|
+
$BrewTapDir = Join-Path $env:TEMP "its-magic-$safeTap"
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
Log "Publishing formulas to tap $BrewTapRepo (branch: $BrewTapBranch) ..."
|
|
295
|
+
|
|
296
|
+
# Use a child scope with relaxed error handling for git commands
|
|
297
|
+
# that write to stderr even on success (e.g. empty repo warnings).
|
|
298
|
+
$tapReady = $true
|
|
299
|
+
$prevEAP = $ErrorActionPreference
|
|
300
|
+
$ErrorActionPreference = 'Continue'
|
|
301
|
+
|
|
302
|
+
# ── Step A: Ensure we have a local clone ──
|
|
303
|
+
$tapGitDir = Join-Path $BrewTapDir ".git"
|
|
304
|
+
|
|
305
|
+
# Clean up non-git directory leftovers
|
|
306
|
+
if ((Test-Path $BrewTapDir) -and -not (Test-Path $tapGitDir)) {
|
|
307
|
+
Warn "Tap directory exists but is not a git repo. Removing: $BrewTapDir"
|
|
308
|
+
Remove-Item -Recurse -Force $BrewTapDir
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (Test-Path $tapGitDir) {
|
|
312
|
+
# Already cloned - fetch latest
|
|
313
|
+
git -C $BrewTapDir fetch origin 2>&1 | Out-Null
|
|
314
|
+
} else {
|
|
315
|
+
# Clone (try gh first, then git)
|
|
316
|
+
$cloneOk = $false
|
|
317
|
+
if ($ghAvailableForTap) {
|
|
318
|
+
& gh repo clone $BrewTapRepo $BrewTapDir 2>&1 | Out-Null
|
|
319
|
+
if ($LASTEXITCODE -eq 0) { $cloneOk = $true }
|
|
320
|
+
}
|
|
321
|
+
if (-not $cloneOk) {
|
|
322
|
+
git clone "https://github.com/$BrewTapRepo.git" $BrewTapDir 2>&1 | Out-Null
|
|
323
|
+
if ($LASTEXITCODE -eq 0) { $cloneOk = $true }
|
|
324
|
+
}
|
|
325
|
+
if (-not $cloneOk -and $CreateBrewTapIfMissing -and $ghAvailableForTap) {
|
|
326
|
+
Warn "Tap repo not found. Creating $BrewTapRepo on GitHub ..."
|
|
327
|
+
& gh repo create $BrewTapRepo --public --description "Homebrew tap for its-magic" 2>&1 | Out-Null
|
|
328
|
+
if ($LASTEXITCODE -eq 0) {
|
|
329
|
+
Log "Tap repo created: https://github.com/$BrewTapRepo"
|
|
330
|
+
if (Test-Path $BrewTapDir) { Remove-Item -Recurse -Force $BrewTapDir }
|
|
331
|
+
& gh repo clone $BrewTapRepo $BrewTapDir 2>&1 | Out-Null
|
|
332
|
+
if ($LASTEXITCODE -eq 0) { $cloneOk = $true }
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (-not $cloneOk) {
|
|
336
|
+
$tapReady = $false; $brewTapFailed = $true
|
|
337
|
+
Warn "Failed to clone/create tap repo $BrewTapRepo"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
# ── Step B: Ensure branch exists ──
|
|
342
|
+
if ($tapReady) {
|
|
343
|
+
# Check if repo has any commits at all
|
|
344
|
+
$hasCommits = $false
|
|
345
|
+
git -C $BrewTapDir log --oneline -1 2>&1 | Out-Null
|
|
346
|
+
if ($LASTEXITCODE -eq 0) { $hasCommits = $true }
|
|
347
|
+
|
|
348
|
+
if ($hasCommits) {
|
|
349
|
+
git -C $BrewTapDir checkout $BrewTapBranch 2>&1 | Out-Null
|
|
350
|
+
if ($LASTEXITCODE -ne 0) {
|
|
351
|
+
git -C $BrewTapDir checkout -b $BrewTapBranch 2>&1 | Out-Null
|
|
352
|
+
}
|
|
353
|
+
# Try to pull if remote branch exists
|
|
354
|
+
git -C $BrewTapDir pull origin $BrewTapBranch 2>&1 | Out-Null
|
|
355
|
+
} else {
|
|
356
|
+
Log "Empty tap repo detected - creating initial branch $BrewTapBranch"
|
|
357
|
+
git -C $BrewTapDir checkout --orphan $BrewTapBranch 2>&1 | Out-Null
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# ── Step C: Copy formulas, commit, push ──
|
|
362
|
+
if ($tapReady) {
|
|
363
|
+
$tapFormulaDir = Join-Path $BrewTapDir "Formula"
|
|
364
|
+
if (-not (Test-Path $tapFormulaDir)) {
|
|
365
|
+
New-Item -ItemType Directory -Path $tapFormulaDir -Force | Out-Null
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
Copy-Item (Join-Path $repoRoot "packaging\homebrew\its-magic.rb") (Join-Path $tapFormulaDir "its-magic.rb") -Force
|
|
369
|
+
Copy-Item (Join-Path $repoRoot "packaging\homebrew\its-magic-beta.rb") (Join-Path $tapFormulaDir "its-magic-beta.rb") -Force
|
|
370
|
+
|
|
371
|
+
git -C $BrewTapDir add Formula/its-magic.rb Formula/its-magic-beta.rb
|
|
372
|
+
|
|
373
|
+
$tapChanges = git -C $BrewTapDir status --porcelain
|
|
374
|
+
if ($tapChanges) {
|
|
375
|
+
git -C $BrewTapDir commit -m "chore: update its-magic formulas for $newVersion" 2>&1 | Out-Null
|
|
376
|
+
if ($LASTEXITCODE -ne 0) {
|
|
377
|
+
$brewTapFailed = $true
|
|
378
|
+
Warn "Failed to commit Homebrew tap changes"
|
|
379
|
+
} else {
|
|
380
|
+
git -C $BrewTapDir push -u origin $BrewTapBranch 2>&1 | Out-Null
|
|
381
|
+
if ($LASTEXITCODE -ne 0) {
|
|
382
|
+
$brewTapFailed = $true
|
|
383
|
+
Warn "Failed to push Homebrew tap changes"
|
|
384
|
+
} else {
|
|
385
|
+
$brewTapPushed = $true
|
|
386
|
+
Log "Homebrew tap updated: https://github.com/$BrewTapRepo"
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
Log "No Homebrew formula changes to push"
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
$ErrorActionPreference = $prevEAP
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
Warn "Skipping Homebrew tap push"
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
Warn "Skipping Homebrew"
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
# ── Done ─────────────────────────────────────────────────────────────
|
|
406
|
+
Pop-Location
|
|
407
|
+
$npmStatus = "OK"
|
|
408
|
+
if ($SkipNpm) { $npmStatus = "SKIPPED" }
|
|
409
|
+
$chocoStatus = "OK"
|
|
410
|
+
if ($SkipChoco) { $chocoStatus = "SKIPPED" }
|
|
411
|
+
elseif ($DryRun) { $chocoStatus = "DRY-RUN" }
|
|
412
|
+
elseif ($chocoFailed) { $chocoStatus = "FAILED - check log" }
|
|
413
|
+
$brewStatus = "OK - formula updated"
|
|
414
|
+
if ($SkipBrew) { $brewStatus = "SKIPPED" }
|
|
415
|
+
elseif ($brewTapFailed) { $brewStatus = "FORMULA OK - TAP PUSH FAILED" }
|
|
416
|
+
elseif ($brewTapPushed) { $brewStatus = "OK - formula updated + tap pushed" }
|
|
417
|
+
|
|
418
|
+
Log "=========================================="
|
|
419
|
+
Log "Release $newVersion complete!"
|
|
420
|
+
Log " npm: $npmStatus"
|
|
421
|
+
Log " Chocolatey: $chocoStatus"
|
|
422
|
+
Log " Homebrew: $brewStatus"
|
|
423
|
+
Log "=========================================="
|