@wyxos/zephyr 0.1.3 → 0.1.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.
- package/.github/copilot-instructions.md +35 -0
- package/package.json +1 -1
- package/publish.mjs +26 -0
- package/src/index.mjs +27 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Copilot Instructions
|
|
2
|
+
|
|
3
|
+
## Project Snapshot
|
|
4
|
+
- Command-line deployment tool (`bin/zephyr.mjs`) that delegates to `src/index.mjs` for all logic.
|
|
5
|
+
- Node.js ESM project; keep imports as `import … from` and avoid CommonJS helpers.
|
|
6
|
+
- Primary responsibilities: gather deployment config via prompts, ensure local git state, SSH into remote servers, run per-change maintenance tasks.
|
|
7
|
+
|
|
8
|
+
## Configuration Model
|
|
9
|
+
- Global servers live at `~/.config/zephyr/servers.json` (array of `{ serverName, serverIp }`).
|
|
10
|
+
- Per-project apps live at `.zephyr/config.json` (apps array with `{ serverName, projectPath, branch, sshUser, sshKey }`).
|
|
11
|
+
- `main()` now sequences: ensure `.zephyr/` ignored, load servers, pick/create one, load project config, pick/create app, ensure SSH details, run deployment.
|
|
12
|
+
- When adding config logic, reuse helpers: `selectServer`, `promptServerDetails`, `selectApp`, `promptAppDetails`, `ensureProjectConfig`.
|
|
13
|
+
|
|
14
|
+
## Deployment Flow Highlights
|
|
15
|
+
- Always call `ensureLocalRepositoryState(branch)` before SSH. It:
|
|
16
|
+
- Verifies current branch, fast-forwards with `git pull --ff-only`, warns if ahead, commits + pushes uncommitted changes when needed.
|
|
17
|
+
- Prompts for commit message if dirty and pushes to `origin/<branch>`.
|
|
18
|
+
- Remote execution happens via `runRemoteTasks(config)`; keep all SSH commands funneled through `executeRemote(label, command, options)` to inherit logging and error handling.
|
|
19
|
+
- Laravel detection toggles extra tasks—Composer, migrations, npm install/build, cache clears, queue restarts—based on changed files from `git diff HEAD..origin/<branch>`.
|
|
20
|
+
|
|
21
|
+
## Release Workflow
|
|
22
|
+
- Automated publishing script at `publish.mjs` (`npm run release`):
|
|
23
|
+
- Checks clean working tree, fetches & fast-forwards branch, runs `npx vitest run`, bumps version via `npm version <type>`, pushes with tags, publishes (adds `--access public` for scoped packages).
|
|
24
|
+
- `npm pkg fix` may adjust `package.json`; commit results before running the release.
|
|
25
|
+
|
|
26
|
+
## Testing & Tooling
|
|
27
|
+
- Test suite: `npm test` (Vitest). Mocks for fs, child_process, inquirer, node-ssh are set up—extend them for new behaviors rather than shelling out.
|
|
28
|
+
- Avoid long-running watchers in scripts; tests spawn Vitest in watch mode by default, so kill (`pkill -f vitest`) after scripted runs when necessary.
|
|
29
|
+
|
|
30
|
+
## Conventions & Style
|
|
31
|
+
- Logging helpers (`logProcessing`, `logSuccess`, `logWarning`, `logError`) centralize colored output—use them instead of `console.log` in new deployment logic.
|
|
32
|
+
- Use async/await with `runCommand` / `runCommandCapture` for local shell ops; never `exec` directly.
|
|
33
|
+
- Keep new prompts routed through `runPrompt`; it supports injection for tests.
|
|
34
|
+
- Default to ASCII in files; comments only where logic is non-obvious.
|
|
35
|
+
- Update Vitest cases in `tests/index.test.js` when altering prompts, config structure, or deployment steps; tests expect deterministic logging text.
|
package/package.json
CHANGED
package/publish.mjs
CHANGED
|
@@ -97,6 +97,18 @@ async function ensureUpToDateWithUpstream(branch, upstreamRef) {
|
|
|
97
97
|
return
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
const [remoteName, ...branchParts] = upstreamRef.split('/')
|
|
101
|
+
const remoteBranch = branchParts.join('/')
|
|
102
|
+
|
|
103
|
+
if (remoteName && remoteBranch) {
|
|
104
|
+
logStep(`Fetching latest updates from ${remoteName}/${remoteBranch}...`)
|
|
105
|
+
try {
|
|
106
|
+
await runCommand('git', ['fetch', remoteName, remoteBranch])
|
|
107
|
+
} catch (error) {
|
|
108
|
+
throw new Error(`Failed to fetch ${upstreamRef}: ${error.message}`)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
const aheadResult = await runCommand('git', ['rev-list', '--count', `${upstreamRef}..HEAD`], {
|
|
101
113
|
capture: true
|
|
102
114
|
})
|
|
@@ -108,6 +120,20 @@ async function ensureUpToDateWithUpstream(branch, upstreamRef) {
|
|
|
108
120
|
const behind = Number.parseInt(behindResult.stdout || '0', 10)
|
|
109
121
|
|
|
110
122
|
if (Number.isFinite(behind) && behind > 0) {
|
|
123
|
+
if (remoteName && remoteBranch) {
|
|
124
|
+
logStep(`Fast-forwarding ${branch} with ${upstreamRef}...`)
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await runCommand('git', ['pull', '--ff-only', remoteName, remoteBranch])
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Unable to fast-forward ${branch} with ${upstreamRef}. Resolve conflicts manually, then rerun the release.\n${error.message}`
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return ensureUpToDateWithUpstream(branch, upstreamRef)
|
|
135
|
+
}
|
|
136
|
+
|
|
111
137
|
throw new Error(
|
|
112
138
|
`Branch ${branch} is behind ${upstreamRef} by ${behind} commit${behind === 1 ? '' : 's'}. Pull or rebase first.`
|
|
113
139
|
)
|
package/src/index.mjs
CHANGED
|
@@ -197,6 +197,33 @@ async function ensureLocalRepositoryState(targetBranch, rootDir = process.cwd())
|
|
|
197
197
|
const initialStatus = await getGitStatus(rootDir)
|
|
198
198
|
const hasPendingChanges = initialStatus.length > 0
|
|
199
199
|
|
|
200
|
+
const statusReport = await runCommandCapture('git', ['status', '--short', '--branch'], {
|
|
201
|
+
cwd: rootDir
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const lines = statusReport.split(/\r?\n/)
|
|
205
|
+
const branchLine = lines[0] || ''
|
|
206
|
+
const aheadMatch = branchLine.match(/ahead (\d+)/)
|
|
207
|
+
const behindMatch = branchLine.match(/behind (\d+)/)
|
|
208
|
+
const aheadCount = aheadMatch ? parseInt(aheadMatch[1], 10) : 0
|
|
209
|
+
const behindCount = behindMatch ? parseInt(behindMatch[1], 10) : 0
|
|
210
|
+
|
|
211
|
+
if (aheadCount > 0) {
|
|
212
|
+
logWarning(`Local branch ${currentBranch} is ahead of upstream by ${aheadCount} commit${aheadCount === 1 ? '' : 's'}.`)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (behindCount > 0) {
|
|
216
|
+
logProcessing(`Synchronizing local branch ${currentBranch} with its upstream...`)
|
|
217
|
+
try {
|
|
218
|
+
await runCommand('git', ['pull', '--ff-only'], { cwd: rootDir })
|
|
219
|
+
logSuccess('Local branch fast-forwarded with upstream changes.')
|
|
220
|
+
} catch (error) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`Unable to fast-forward ${currentBranch} with upstream changes. Resolve conflicts manually, then rerun the deployment.\n${error.message}`
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
200
227
|
if (currentBranch !== targetBranch) {
|
|
201
228
|
if (hasPendingChanges) {
|
|
202
229
|
throw new Error(
|