@wyxos/zephyr 0.1.1 → 0.1.2
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/package.json +4 -3
- package/publish.mjs +222 -0
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wyxos/zephyr",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"test": "vitest"
|
|
6
|
+
"test": "vitest",
|
|
7
|
+
"release": "node publish.mjs"
|
|
7
8
|
},
|
|
8
9
|
"bin": {
|
|
9
|
-
"zephyr": "
|
|
10
|
+
"zephyr": "bin/zephyr.mjs"
|
|
10
11
|
},
|
|
11
12
|
"dependencies": {
|
|
12
13
|
"chalk": "5.3.0",
|
package/publish.mjs
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { dirname, join } from 'node:path'
|
|
5
|
+
import { readFile } from 'node:fs/promises'
|
|
6
|
+
|
|
7
|
+
const ROOT = dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const PACKAGE_PATH = join(ROOT, 'package.json')
|
|
9
|
+
|
|
10
|
+
const STEP_PREFIX = '→'
|
|
11
|
+
const OK_PREFIX = '✔'
|
|
12
|
+
const WARN_PREFIX = '⚠'
|
|
13
|
+
|
|
14
|
+
function logStep(message) {
|
|
15
|
+
console.log(`${STEP_PREFIX} ${message}`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function logSuccess(message) {
|
|
19
|
+
console.log(`${OK_PREFIX} ${message}`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function logWarning(message) {
|
|
23
|
+
console.warn(`${WARN_PREFIX} ${message}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runCommand(command, args, { cwd = ROOT, capture = false } = {}) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const spawnOptions = {
|
|
29
|
+
cwd,
|
|
30
|
+
stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'inherit'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const child = spawn(command, args, spawnOptions)
|
|
34
|
+
let stdout = ''
|
|
35
|
+
let stderr = ''
|
|
36
|
+
|
|
37
|
+
if (capture) {
|
|
38
|
+
child.stdout.on('data', (chunk) => {
|
|
39
|
+
stdout += chunk
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
child.stderr.on('data', (chunk) => {
|
|
43
|
+
stderr += chunk
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
child.on('error', reject)
|
|
48
|
+
child.on('close', (code) => {
|
|
49
|
+
if (code === 0) {
|
|
50
|
+
resolve(capture ? { stdout: stdout.trim(), stderr: stderr.trim() } : undefined)
|
|
51
|
+
} else {
|
|
52
|
+
const error = new Error(`Command failed (${code}): ${command} ${args.join(' ')}`)
|
|
53
|
+
if (capture) {
|
|
54
|
+
error.stdout = stdout
|
|
55
|
+
error.stderr = stderr
|
|
56
|
+
}
|
|
57
|
+
error.exitCode = code
|
|
58
|
+
reject(error)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function readPackage() {
|
|
65
|
+
const raw = await readFile(PACKAGE_PATH, 'utf8')
|
|
66
|
+
return JSON.parse(raw)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function ensureCleanWorkingTree() {
|
|
70
|
+
const { stdout } = await runCommand('git', ['status', '--porcelain'], { capture: true })
|
|
71
|
+
|
|
72
|
+
if (stdout.length > 0) {
|
|
73
|
+
throw new Error('Working tree has uncommitted changes. Commit or stash them before releasing.')
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function getCurrentBranch() {
|
|
78
|
+
const { stdout } = await runCommand('git', ['branch', '--show-current'], { capture: true })
|
|
79
|
+
return stdout || null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function getUpstreamRef() {
|
|
83
|
+
try {
|
|
84
|
+
const { stdout } = await runCommand('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], {
|
|
85
|
+
capture: true
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return stdout || null
|
|
89
|
+
} catch {
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function ensureUpToDateWithUpstream(branch, upstreamRef) {
|
|
95
|
+
if (!upstreamRef) {
|
|
96
|
+
logWarning(`Branch ${branch} has no upstream configured; skipping ahead/behind checks.`)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const aheadResult = await runCommand('git', ['rev-list', '--count', `${upstreamRef}..HEAD`], {
|
|
101
|
+
capture: true
|
|
102
|
+
})
|
|
103
|
+
const behindResult = await runCommand('git', ['rev-list', '--count', `HEAD..${upstreamRef}`], {
|
|
104
|
+
capture: true
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const ahead = Number.parseInt(aheadResult.stdout || '0', 10)
|
|
108
|
+
const behind = Number.parseInt(behindResult.stdout || '0', 10)
|
|
109
|
+
|
|
110
|
+
if (Number.isFinite(behind) && behind > 0) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Branch ${branch} is behind ${upstreamRef} by ${behind} commit${behind === 1 ? '' : 's'}. Pull or rebase first.`
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (Number.isFinite(ahead) && ahead > 0) {
|
|
117
|
+
logWarning(`Branch ${branch} is ahead of ${upstreamRef} by ${ahead} commit${ahead === 1 ? '' : 's'}.`)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseArgs() {
|
|
122
|
+
const args = process.argv.slice(2)
|
|
123
|
+
const positionals = args.filter((arg) => !arg.startsWith('--'))
|
|
124
|
+
const flags = new Set(args.filter((arg) => arg.startsWith('--')))
|
|
125
|
+
|
|
126
|
+
const releaseType = positionals[0] ?? 'patch'
|
|
127
|
+
const skipTests = flags.has('--skip-tests')
|
|
128
|
+
|
|
129
|
+
const allowedTypes = new Set([
|
|
130
|
+
'major',
|
|
131
|
+
'minor',
|
|
132
|
+
'patch',
|
|
133
|
+
'premajor',
|
|
134
|
+
'preminor',
|
|
135
|
+
'prepatch',
|
|
136
|
+
'prerelease'
|
|
137
|
+
])
|
|
138
|
+
|
|
139
|
+
if (!allowedTypes.has(releaseType)) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Invalid release type "${releaseType}". Use one of: ${Array.from(allowedTypes).join(', ')}.`
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { releaseType, skipTests }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function runTests(skipTests) {
|
|
149
|
+
if (skipTests) {
|
|
150
|
+
logWarning('Skipping tests because --skip-tests flag was provided.')
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
logStep('Running test suite (vitest run)...')
|
|
155
|
+
await runCommand('npx', ['vitest', 'run'])
|
|
156
|
+
logSuccess('Tests passed.')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function ensureNpmAuth() {
|
|
160
|
+
logStep('Confirming npm authentication...')
|
|
161
|
+
await runCommand('npm', ['whoami'])
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function bumpVersion(releaseType) {
|
|
165
|
+
logStep(`Bumping package version with "npm version ${releaseType}"...`)
|
|
166
|
+
await runCommand('npm', ['version', releaseType, '--message', 'chore: release %s'])
|
|
167
|
+
const pkg = await readPackage()
|
|
168
|
+
logSuccess(`Version updated to ${pkg.version}.`)
|
|
169
|
+
return pkg
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function pushChanges() {
|
|
173
|
+
logStep('Pushing commits and tags to origin...')
|
|
174
|
+
await runCommand('git', ['push', '--follow-tags'])
|
|
175
|
+
logSuccess('Git push completed.')
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function publishPackage(pkg) {
|
|
179
|
+
const publishArgs = ['publish']
|
|
180
|
+
|
|
181
|
+
if (pkg.name.startsWith('@')) {
|
|
182
|
+
publishArgs.push('--access', 'public')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
logStep(`Publishing ${pkg.name}@${pkg.version} to npm...`)
|
|
186
|
+
await runCommand('npm', publishArgs)
|
|
187
|
+
logSuccess('npm publish completed.')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function main() {
|
|
191
|
+
const { releaseType, skipTests } = parseArgs()
|
|
192
|
+
|
|
193
|
+
logStep('Reading package metadata...')
|
|
194
|
+
const pkg = await readPackage()
|
|
195
|
+
|
|
196
|
+
logStep('Checking working tree status...')
|
|
197
|
+
await ensureCleanWorkingTree()
|
|
198
|
+
|
|
199
|
+
const branch = await getCurrentBranch()
|
|
200
|
+
if (!branch) {
|
|
201
|
+
throw new Error('Unable to determine current branch.')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
logStep(`Current branch: ${branch}`)
|
|
205
|
+
const upstreamRef = await getUpstreamRef()
|
|
206
|
+
await ensureUpToDateWithUpstream(branch, upstreamRef)
|
|
207
|
+
|
|
208
|
+
await runTests(skipTests)
|
|
209
|
+
await ensureNpmAuth()
|
|
210
|
+
|
|
211
|
+
const updatedPkg = await bumpVersion(releaseType)
|
|
212
|
+
await pushChanges()
|
|
213
|
+
await publishPackage(updatedPkg)
|
|
214
|
+
|
|
215
|
+
logSuccess(`Release workflow completed for ${updatedPkg.name}@${updatedPkg.version}.`)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
main().catch((error) => {
|
|
219
|
+
console.error('\nRelease failed:')
|
|
220
|
+
console.error(error.message)
|
|
221
|
+
process.exit(1)
|
|
222
|
+
})
|