galbe 0.15.2 → 0.15.4

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.
@@ -511,7 +511,12 @@ ${fetchApiSignature}
511
511
  ...(bodyData !== undefined ? { body: bodyData } : {}),
512
512
  })
513
513
 
514
- if (${interceptorRef}) req = await ${interceptorRef}(req, command, cliArgs, cliOptions)
514
+ if (${interceptorRef}) {
515
+ try { req = await ${interceptorRef}(req, command, cliArgs, cliOptions) } catch (e: any) {
516
+ console.error(_ansi(true, '31', \`error: \${e?.message ?? e}\`))
517
+ process.exit(1)
518
+ }
519
+ }
515
520
 
516
521
  let _res: Response | undefined
517
522
  let _resClone: Response | undefined
@@ -522,7 +527,10 @@ ${fetchApiSignature}
522
527
  const elapsed = (Bun.nanoseconds() - t0) / 1_000_000
523
528
 
524
529
  if (${formatterRef}) {
525
- Bun.write(Bun.stdout, await ${formatterRef}(_res, command, cliArgs, cliOptions))
530
+ try { Bun.write(Bun.stdout, await ${formatterRef}(_res, command, cliArgs, cliOptions)) } catch (e: any) {
531
+ console.error(_ansi(true, '31', \`error: \${e?.message ?? e}\`))
532
+ process.exit(1)
533
+ }
526
534
  } else {
527
535
  if (fmt.has('s')) Bun.write(Bun.stdout, \`\${_res.status}\\n\`)
528
536
  if (fmt.has('h')) Bun.write(Bun.stdout, _fmtObject(_headersToObj(_res.headers), fmt.has('p')) + '\\n')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "galbe",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
4
4
  "description": "Fast, lightweight and highly customizable JavaScript web framework based on Bun",
5
5
  "author": "Pierre Caillaud M (https://github.com/pierre-cm)",
6
6
  "type": "module",
@@ -35,12 +35,11 @@
35
35
  "scripts": {
36
36
  "test": "bun test",
37
37
  "typecheck": "tsc --noEmit --emitDeclarationOnly false",
38
- "release": "release-it"
38
+ "release": "bun run scripts/release.ts"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/bun": "latest",
42
- "openapi-types": "^12.1.3",
43
- "release-it": "^17.1.1"
42
+ "openapi-types": "^12.1.3"
44
43
  },
45
44
  "peerDependencies": {
46
45
  "typescript": "^5.0.0"
@@ -52,17 +51,5 @@
52
51
  "acorn-walk": "^8.3.0",
53
52
  "chokidar": "^3.6.0",
54
53
  "commander": "^11.1.0"
55
- },
56
- "release-it": {
57
- "git": {
58
- "pushRepo": "git@github.com:pierre-cm/galbe.git"
59
- },
60
- "github": {
61
- "requireBranch": "main",
62
- "release": "true"
63
- },
64
- "npm": {
65
- "publish": false
66
- }
67
54
  }
68
55
  }
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { $ } from 'bun'
4
+ import { existsSync } from 'fs'
5
+
6
+ const PKG_PATH = new URL('../package.json', import.meta.url).pathname
7
+ const CHANGELOG_PATH = new URL('../CHANGELOG.md', import.meta.url).pathname
8
+ const REMOTE = 'git@github.com:pierre-cm/galbe.git'
9
+
10
+ type BumpType = 'major' | 'minor' | 'patch'
11
+ type CommitKind = 'breaking' | 'feat' | 'fix'
12
+
13
+ interface Commit {
14
+ kind: CommitKind
15
+ scope?: string
16
+ message: string
17
+ }
18
+
19
+ // --- arg parsing ---
20
+
21
+ const argv = process.argv.slice(2)
22
+ const isCI = argv.includes('--ci')
23
+ const scopeIdx = argv.indexOf('--scope')
24
+ const scopeArg = scopeIdx !== -1 ? argv[scopeIdx + 1] : null
25
+
26
+ if (scopeArg && !['patch', 'minor', 'major'].includes(scopeArg)) {
27
+ console.error(`error: --scope must be patch, minor, or major`)
28
+ process.exit(1)
29
+ }
30
+
31
+ // --- helpers ---
32
+
33
+ function classify(subject: string): Commit | null {
34
+ if (/^(Release \d|Merge branch)/.test(subject)) return null
35
+
36
+ if (subject.includes('BREAKING CHANGE') || /^[^:]+!:/.test(subject))
37
+ return { kind: 'breaking', message: subject.replace(/^[^:]+!?:\s*/, '') }
38
+
39
+ const feat = subject.match(/^feat(?:\(([^)]+)\))?:\s*(.+)/)
40
+ if (feat) return { kind: 'feat', scope: feat[1], message: feat[2] }
41
+
42
+ const fix = subject.match(/^fix(?:\(([^)]+)\))?:\s*(.+)/)
43
+ if (fix) return { kind: 'fix', scope: fix[1], message: fix[2] }
44
+
45
+ return null
46
+ }
47
+
48
+ function bump(version: string, type: BumpType): string {
49
+ const [maj, min, pat] = version.split('.').map(Number)
50
+ if (type === 'major') return `${maj + 1}.0.0`
51
+ if (type === 'minor') return `${maj}.${min + 1}.0`
52
+ return `${maj}.${min}.${pat + 1}`
53
+ }
54
+
55
+ function suggest(commits: Commit[], preRelease: boolean): BumpType {
56
+ if (!preRelease && commits.some(c => c.kind === 'breaking')) return 'major'
57
+ if (commits.some(c => c.kind === 'breaking') || commits.some(c => c.kind === 'feat')) return 'minor'
58
+ return 'patch'
59
+ }
60
+
61
+ function formatEntry(version: string, commits: Commit[], date: string): string {
62
+ const sections: string[] = []
63
+ const fmt = (c: Commit) => `- ${c.scope ? `**${c.scope}**: ` : ''}${c.message}`
64
+
65
+ const breaking = commits.filter(c => c.kind === 'breaking')
66
+ const feats = commits.filter(c => c.kind === 'feat')
67
+ const fixes = commits.filter(c => c.kind === 'fix')
68
+
69
+ if (breaking.length) sections.push(`### Breaking Changes\n${breaking.map(fmt).join('\n')}`)
70
+ if (feats.length) sections.push(`### Features\n${feats.map(fmt).join('\n')}`)
71
+ if (fixes.length) sections.push(`### Fixes\n${fixes.map(fmt).join('\n')}`)
72
+
73
+ return `## ${version} — ${date}\n\n${sections.join('\n\n')}`
74
+ }
75
+
76
+ function prependChangelog(entry: string, existing: string): string {
77
+ if (!existing) return `# Changelog\n\n${entry}\n`
78
+ if (existing.startsWith('# Changelog')) {
79
+ const body = existing.replace(/^# Changelog\s*/, '')
80
+ return `# Changelog\n\n${entry}\n\n${body}`
81
+ }
82
+ return `# Changelog\n\n${entry}\n\n${existing}`
83
+ }
84
+
85
+ // --- main ---
86
+
87
+ async function main() {
88
+ if (!isCI) {
89
+ const branch = (await $`git rev-parse --abbrev-ref HEAD`.quiet().text()).trim()
90
+ if (branch !== 'main') {
91
+ console.error(`error: must be on main branch (currently on '${branch}')`)
92
+ process.exit(1)
93
+ }
94
+
95
+ const dirty = (await $`git status --porcelain`.quiet().text()).trim()
96
+ if (dirty) {
97
+ console.error('error: working tree has uncommitted changes')
98
+ process.exit(1)
99
+ }
100
+ }
101
+
102
+ const lastTag = await $`git describe --tags --abbrev=0`
103
+ .quiet()
104
+ .text()
105
+ .catch(() => '')
106
+ .then(t => t.trim())
107
+
108
+ const logRange = lastTag ? `${lastTag}..HEAD` : 'HEAD'
109
+ const rawLog = (await $`git log --pretty=format:%s ${logRange}`.quiet().text()).trim()
110
+
111
+ const commits = rawLog ? (rawLog.split('\n').map(classify).filter(Boolean) as Commit[]) : []
112
+
113
+ const pkg = await Bun.file(PKG_PATH).json()
114
+ const current: string = pkg.version
115
+ const preRelease = current.startsWith('0.')
116
+
117
+ console.log(`\ncurrent: ${current}${lastTag ? ` (since tag ${lastTag})` : ''}`)
118
+
119
+ if (!commits.length && !scopeArg) {
120
+ console.log('no feat/fix commits since last release — nothing to do.')
121
+ process.exit(0)
122
+ }
123
+
124
+ let next: string
125
+
126
+ if (scopeArg) {
127
+ // CI / non-interactive: scope is given directly
128
+ next = bump(current, scopeArg as BumpType)
129
+ console.log(`scope: ${scopeArg} → ${next}`)
130
+ } else {
131
+ // Interactive
132
+ const breaking = commits.filter(c => c.kind === 'breaking')
133
+ const feats = commits.filter(c => c.kind === 'feat')
134
+ const fixes = commits.filter(c => c.kind === 'fix')
135
+
136
+ if (breaking.length) { console.log('\n breaking:'); breaking.forEach(c => console.log(` - ${c.message}`)) }
137
+ if (feats.length) { console.log('\n features:'); feats.forEach(c => console.log(` - ${c.scope ? `[${c.scope}] ` : ''}${c.message}`)) }
138
+ if (fixes.length) { console.log('\n fixes:'); fixes.forEach(c => console.log(` - ${c.scope ? `[${c.scope}] ` : ''}${c.message}`)) }
139
+
140
+ const recommended = suggest(commits, preRelease)
141
+ const versions = { patch: bump(current, 'patch'), minor: bump(current, 'minor'), major: bump(current, 'major') }
142
+ const defaultChoice = recommended === 'patch' ? '1' : recommended === 'minor' ? '2' : '3'
143
+
144
+ console.log(`\n 1) patch → ${versions.patch}${recommended === 'patch' ? ' ←' : ''}`)
145
+ console.log(` 2) minor → ${versions.minor}${recommended === 'minor' ? ' ←' : ''}`)
146
+ console.log(` 3) major → ${versions.major}${recommended === 'major' ? ' ←' : ''}`)
147
+ console.log(` 4) custom`)
148
+ if (preRelease) console.log(`\n note: ${current} is pre-release (0.x) — breaking changes capped to minor`)
149
+
150
+ const choice = prompt(`\nbump type [${defaultChoice}]:`)?.trim() || defaultChoice
151
+
152
+ if (choice === '1') next = versions.patch
153
+ else if (choice === '2') next = versions.minor
154
+ else if (choice === '3') next = versions.major
155
+ else if (choice === '4') {
156
+ next = prompt('version:')?.trim() ?? ''
157
+ if (!/^\d+\.\d+\.\d+$/.test(next)) {
158
+ console.error('error: invalid semver format')
159
+ process.exit(1)
160
+ }
161
+ } else {
162
+ console.error('error: invalid choice')
163
+ process.exit(1)
164
+ }
165
+
166
+ const ok = prompt(`\nrelease ${next}? (y/N):`)?.trim().toLowerCase()
167
+ if (ok !== 'y') { console.log('aborted.'); process.exit(0) }
168
+ }
169
+
170
+ console.log()
171
+
172
+ const date = new Date().toISOString().split('T')[0]
173
+ const entry = formatEntry(next, commits, date)
174
+
175
+ const existing = existsSync(CHANGELOG_PATH) ? await Bun.file(CHANGELOG_PATH).text() : ''
176
+ await Bun.write(CHANGELOG_PATH, prependChangelog(entry, existing))
177
+ console.log('updated CHANGELOG.md')
178
+
179
+ pkg.version = next
180
+ await Bun.write(PKG_PATH, JSON.stringify(pkg, null, 2) + '\n')
181
+ console.log(`bumped package.json → ${next}`)
182
+
183
+ await $`git add package.json CHANGELOG.md`
184
+ await $`git commit -m "Release ${next}"`
185
+ await $`git tag ${next}`
186
+ await $`git push ${REMOTE} main --tags`
187
+ console.log(`pushed commit and tag ${next}`)
188
+
189
+ await $`gh release create ${next} --title "Release ${next}" --notes ${entry}`
190
+ console.log(`\ncreated GitHub release ${next}`)
191
+ }
192
+
193
+ main().catch(e => {
194
+ console.error(e.message ?? e)
195
+ process.exit(1)
196
+ })