@wyxos/zephyr 0.1.0 → 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/README.md CHANGED
@@ -1,76 +1,76 @@
1
- # @wyxos/zephyr
2
-
3
- A streamlined deployment tool for web applications with intelligent Laravel project detection.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install -g @wyxos/zephyr
9
- ```
10
-
11
- Or run directly with npx:
12
-
13
- ```bash
14
- npx @wyxos/zephyr
15
- ```
16
-
17
- ## Usage
18
-
19
- Navigate to your project directory and run:
20
-
21
- ```bash
22
- zephyr
23
- ```
24
-
25
- Follow the interactive prompts to configure your deployment target:
26
- - Server name and IP address
27
- - Project path on the remote server
28
- - Git branch to deploy
29
- - SSH user and private key
30
-
31
- Configuration is saved to `release.json` for future deployments.
32
-
33
- ## Features
34
-
35
- - Automated Git operations (branch switching, commits, pushes)
36
- - SSH-based deployment to remote servers
37
- - Laravel project detection with smart task execution
38
- - Intelligent dependency management (Composer, npm)
39
- - Database migrations when detected
40
- - Frontend asset compilation
41
- - Cache clearing and queue worker management
42
- - SSH key validation and management
43
-
44
- ## Smart Task Execution
45
-
46
- Zephyr analyzes changed files and runs appropriate tasks:
47
-
48
- - **Always**: `git pull origin <branch>`
49
- - **Composer files changed**: `composer update`
50
- - **Migration files added**: `php artisan migrate`
51
- - **package.json changed**: `npm install`
52
- - **Frontend files changed**: `npm run build`
53
- - **PHP files changed**: Clear Laravel caches, restart queues
54
-
55
- ## Configuration
56
-
57
- Deployment targets are stored in `release.json`:
58
-
59
- ```json
60
- [
61
- {
62
- "serverName": "production",
63
- "serverIp": "192.168.1.100",
64
- "projectPath": "~/webapps/myapp",
65
- "branch": "main",
66
- "sshUser": "forge",
67
- "sshKey": "~/.ssh/id_rsa"
68
- }
69
- ]
70
- ```
71
-
72
- ## Requirements
73
-
74
- - Node.js 16+
75
- - Git
1
+ # @wyxos/zephyr
2
+
3
+ A streamlined deployment tool for web applications with intelligent Laravel project detection.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @wyxos/zephyr
9
+ ```
10
+
11
+ Or run directly with npx:
12
+
13
+ ```bash
14
+ npx @wyxos/zephyr
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ Navigate to your project directory and run:
20
+
21
+ ```bash
22
+ zephyr
23
+ ```
24
+
25
+ Follow the interactive prompts to configure your deployment target:
26
+ - Server name and IP address
27
+ - Project path on the remote server
28
+ - Git branch to deploy
29
+ - SSH user and private key
30
+
31
+ Configuration is saved to `release.json` for future deployments.
32
+
33
+ ## Features
34
+
35
+ - Automated Git operations (branch switching, commits, pushes)
36
+ - SSH-based deployment to remote servers
37
+ - Laravel project detection with smart task execution
38
+ - Intelligent dependency management (Composer, npm)
39
+ - Database migrations when detected
40
+ - Frontend asset compilation
41
+ - Cache clearing and queue worker management
42
+ - SSH key validation and management
43
+
44
+ ## Smart Task Execution
45
+
46
+ Zephyr analyzes changed files and runs appropriate tasks:
47
+
48
+ - **Always**: `git pull origin <branch>`
49
+ - **Composer files changed**: `composer update`
50
+ - **Migration files added**: `php artisan migrate`
51
+ - **package.json changed**: `npm install`
52
+ - **Frontend files changed**: `npm run build`
53
+ - **PHP files changed**: Clear Laravel caches, restart queues
54
+
55
+ ## Configuration
56
+
57
+ Deployment targets are stored in `release.json`:
58
+
59
+ ```json
60
+ [
61
+ {
62
+ "serverName": "production",
63
+ "serverIp": "192.168.1.100",
64
+ "projectPath": "~/webapps/myapp",
65
+ "branch": "main",
66
+ "sshUser": "forge",
67
+ "sshKey": "~/.ssh/id_rsa"
68
+ }
69
+ ]
70
+ ```
71
+
72
+ ## Requirements
73
+
74
+ - Node.js 16+
75
+ - Git
76
76
  - SSH access to target servers
package/bin/zephyr.mjs CHANGED
@@ -1,7 +1,7 @@
1
- #!/usr/bin/env node
2
- import { main } from '../src/index.mjs'
3
-
4
- main().catch((error) => {
5
- console.error(error.message)
6
- process.exit(1)
7
- })
1
+ #!/usr/bin/env node
2
+ import { main } from '../src/index.mjs'
3
+
4
+ main().catch((error) => {
5
+ console.error(error.message)
6
+ process.exit(1)
7
+ })
package/package.json CHANGED
@@ -1,47 +1,20 @@
1
- {
2
- "name": "@wyxos/zephyr",
3
- "version": "0.1.0",
4
- "description": "A streamlined deployment tool for web applications with intelligent Laravel project detection",
5
- "type": "module",
6
- "main": "./src/index.mjs",
7
- "bin": {
8
- "zephyr": "./bin/zephyr.mjs"
9
- },
10
- "scripts": {
11
- "test": "vitest"
12
- },
13
- "keywords": [
14
- "deployment",
15
- "laravel",
16
- "ssh",
17
- "automation",
18
- "devops",
19
- "git"
20
- ],
21
- "author": "wyxos",
22
- "license": "MIT",
23
- "repository": {
24
- "type": "git",
25
- "url": "git+https://github.com/wyxos/zephyr.git"
26
- },
27
- "bugs": {
28
- "url": "https://github.com/wyxos/zephyr/issues"
29
- },
30
- "homepage": "https://github.com/wyxos/zephyr#readme",
31
- "engines": {
32
- "node": ">=16.0.0"
33
- },
34
- "files": [
35
- "bin/",
36
- "src/",
37
- "README.md"
38
- ],
39
- "dependencies": {
40
- "chalk": "5.3.0",
41
- "inquirer": "^9.2.12",
42
- "node-ssh": "^13.1.0"
43
- },
44
- "devDependencies": {
45
- "vitest": "^2.1.8"
46
- }
47
- }
1
+ {
2
+ "name": "@wyxos/zephyr",
3
+ "version": "0.1.2",
4
+ "type": "module",
5
+ "scripts": {
6
+ "test": "vitest",
7
+ "release": "node publish.mjs"
8
+ },
9
+ "bin": {
10
+ "zephyr": "bin/zephyr.mjs"
11
+ },
12
+ "dependencies": {
13
+ "chalk": "5.3.0",
14
+ "inquirer": "^9.2.12",
15
+ "node-ssh": "^13.1.0"
16
+ },
17
+ "devDependencies": {
18
+ "vitest": "^2.1.8"
19
+ }
20
+ }
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
+ })