Skip to content
wyxosPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

@wyxos/zephyr

A streamlined deployment tool for web applications with intelligent Laravel project detection.

Installation

npm install -g @wyxos/zephyr

Or run directly with npx:

npx @wyxos/zephyr

Usage

Navigate to your app or package directory and run:

npm run release

Or invoke Zephyr directly:

zephyr

See all flags:

zephyr --help

Common workflows:

# Deploy an app using the saved preset or the interactive prompts
zephyr

# Deploy an app with Zephyr's recommended local npm package version bump
zephyr

# Deploy an app with an explicit local npm package version bump
zephyr minor

# Deploy an app while bypassing Zephyr's built-in local checks
zephyr minor --skip-checks

# Deploy a configured app non-interactively
zephyr --non-interactive --preset wyxos-release --maintenance off

# Build frontend assets locally, verify the upload, and activate them during deploy
zephyr --non-interactive --preset wyxos-release --maintenance on --frontend-build-strategy local-artifact

# Deploy several configured app presets as one grouped release
zephyr --non-interactive --group "Development v1-v4" --maintenance off --skip-versioning

# Configure a Laravel app target and verify SSH without deploying
zephyr --setup

# Deploy a configured app non-interactively and auto-commit dirty changes
zephyr --non-interactive --preset wyxos-release --auto-commit

# Deploy without mutating the local package version
zephyr --non-interactive --preset wyxos-release --skip-versioning

# Resume a pending non-interactive deployment
zephyr --non-interactive --preset wyxos-release --resume-pending --maintenance off

# Emit NDJSON events for automation or agent tooling
zephyr --non-interactive --json --preset wyxos-release --maintenance on

# Release a Node/Vue package with Zephyr's recommended bump
zephyr --type node

# Release a Node/Vue package with an explicit bump
zephyr --type node minor

# Release a Node/Vue package, update a local consumer app, then deploy that app
zephyr --type node --then-deploy ../php/atlas --consumer-preset wyxos-release --consumer-package @wyxos/vibe

# Release a Packagist package
zephyr --type packagist patch

# Release the current package/composer version without bumping version files
zephyr --type node --skip-versioning
zephyr --type packagist --skip-versioning

When an app deployment or a --type node/--type vue package release omits the bump argument, Zephyr recommends a version bump from changes since the last release reference, using Codex when available and deterministic heuristics as a fallback.

SDKs with custom build/tag workflows can reuse Zephyr’s adviser without running a release:

import {recommendReleaseType} from '@wyxos/zephyr/release-advisor'

const bump = await recommendReleaseType({
  rootDir: process.cwd(),
  currentVersion: '1.1.100',
  packageName: 'wyxos/shift-php',
  latestTag: 'v1.1.100',
  logStep: console.log,
  logWarning: console.warn
})

This API inspects committed and pending changes, asks Codex for a bump, and returns the release type without modifying manifests, commits, tags, or published packages. If Codex is unavailable or fails, Zephyr warns and uses its existing heuristic fallback. Version assessment uses the CLI’s account-compatible default model; ZEPHYR_CODEX_MODEL selects an explicit model. ZEPHYR_CODEX_COMMAND selects an explicit CLI executable; on macOS, the installed ChatGPT/Codex app bundle is used when codex is absent from PATH.

Interactive and Non-Interactive Modes

Interactive mode remains the default and is the best fit for first-time setup, config repair, and one-off deployments.

For app deployments, interactive mode now requires a real interactive terminal. If stdin/stdout are not attached to a TTY, Zephyr refuses to continue in interactive mode and tells you to rerun with --non-interactive --preset --maintenance on|off.

Non-interactive mode is strict and is intended for already-configured projects:

  • --non-interactive fails instead of prompting
  • app deployments require --preset <name> or --group <name>
  • Laravel app deployments require either a saved preset maintenance preference, --maintenance on|off, or a resumable snapshot that already contains the choice
  • pending deployment snapshots require either --resume-pending or --discard-pending
  • stale remote locks are never auto-removed in non-interactive mode
  • --json is only supported together with --non-interactive

If Laravel maintenance mode has already been enabled and Zephyr exits because of SIGINT, SIGTERM, or SIGHUP between known completed commands, it makes a best-effort attempt to run artisan up. If a command is in flight or its outcome is unknown, recovery is withheld and locks/state are retained for inspection.

--frontend-build-strategy local-artifact builds public/build from the final prepared Git commit on the deploying machine. Zephyr uploads a checksummed archive before remote app mutations, verifies the remote commit before activation, retains the previous build until deployment finalization, and restores it on a failed or interrupted deployment. tracked-assets is for legacy applications that compile locally and commit their generated frontend assets; Zephyr pulls those files from Git and skips remote Node installation and compilation. The default remote strategy keeps the existing server-side npm install/build behavior.

If Zephyr would normally prompt to:

  • create or repair config
  • save a preset
  • install the release script
  • confirm local path dependency updates
  • resolve a stale remote lock

then non-interactive mode stops immediately with a clear error instead.

For Laravel app deployments, --maintenance on|off overrides both the saved preset preference and the maintenance prompt when you want an explicit choice for the current run.

--setup is Laravel-only. It first verifies that the current project is a local Laravel app, then runs the normal local configuration prompts, tests SSH authentication to the selected server, and exits before local deploy preparation, pending snapshot handling, maintenance-mode decisions, locks, or remote deployment commands. On non-Laravel projects it fails before local setup changes are written.

--auto-commit is available for app deployments and package releases. It tells Zephyr to let local Codex inspect the repo and generate the dirty-tree commit message instead of prompting for one.

--skip-versioning keeps Zephyr from mutating package.json or composer.json. On app deploys it skips the local npm version bump step. On package release workflows it releases the version already present in the manifest and creates the release tag from the current HEAD.

--then-deploy <path> is available for --type node and --type vue package releases. After the package release succeeds and the published version is visible on npm, Zephyr updates the local consumer repository, commits the consumer dependency change, and deploys that consumer through its normal Zephyr app-deploy preset. Use --consumer-preset <name> to choose the app preset and --consumer-package <name> when the consumer dependency name differs from the released package name. Pass --consumer-auto-commit when the consumer app may already have pending changes; Zephyr will stage, commit, and push all pending consumer changes before applying the dependency bump. Consumer lockfiles are refreshed only when the consumer repository already tracks an npm lockfile; Zephyr does not create or commit server-side lockfiles.

AI Agents and Automation

Zephyr can be used safely by Codex, CI jobs, or other automation once configuration is already in place.

Recommended pattern for app deployments:

zephyr --non-interactive --json --preset wyxos-release --maintenance off

Recommended pattern for grouped app deployments:

zephyr --non-interactive --json --group "Development v1-v4" --maintenance off --skip-versioning

Recommended pattern for package releases:

zephyr --type node --non-interactive --json minor
zephyr --type packagist --non-interactive --json patch

Recommended pattern for releasing a package and immediately deploying a consumer app:

zephyr --type node --non-interactive --json --then-deploy ../php/atlas --consumer-preset wyxos-release --consumer-package @wyxos/vibe minor

The consumer update happens in the local consumer repository before deployment. Production receives the committed consumer state through the normal deployment path.

In --json mode Zephyr emits NDJSON events on stdout with a stable shape:

  • run_started
  • log
  • prompt_required
  • run_completed
  • run_failed

Each event includes:

  • event
  • timestamp
  • workflow
  • message
  • level where relevant
  • data

run_failed also includes a stable code field for automation checks.

In --json mode, Zephyr reserves stdout for NDJSON events and routes inherited local command output to stderr so agent parsers do not get corrupted.

On a first run inside a project with package.json, Zephyr can:

  • add .zephyr/ to .gitignore
  • add a release script that runs npx @wyxos/zephyr@latest
  • create global server config and per-project deployment config interactively

Follow the interactive prompts to configure your deployment target:

  • Server name and IP address
  • Project path on the remote server
  • Git branch to deploy
  • SSH user and private key

Configuration is saved automatically for future deployments.

Project Scripts

The recommended entrypoint in consumer projects is:

npm run release
  • npm run release is the recommended app/package entrypoint once the release script has been installed.
  • For --type node workflows, Zephyr runs your project's lint script when present.
  • For --type node workflows, Zephyr runs test:run or test when present.
  • For fresh Laravel app deploys, Zephyr runs local lint/test checks before creating the version-bump commit so failed checks do not force repeated bump retries.
  • For critical app deploys, --skip-checks is shorthand for --skip-lint --skip-tests. It skips Zephyr's built-in local checks, but any local pre-push hook can still run its own checks during git push unless you also opt into --skip-git-hooks.
  • For non-interactive app deploys, use a saved preset name instead of relying on prompt fallback.

Features

  • Automated Git operations (branch switching, commits, pushes)
  • SSH-based deployment to remote servers
  • Laravel project detection with smart task execution
  • Intelligent dependency management (Composer, npm)
  • Database migrations when detected
  • Frontend asset compilation
  • Cache clearing and queue worker management
  • SSH key validation and management
  • Deployment locking to prevent concurrent runs
  • Task snapshots for resuming failed deployments
  • Comprehensive logging of all remote operations

Smart Task Execution

Zephyr analyzes changed files and runs appropriate tasks:

  • Always: git pull origin <branch>
  • Composer files changed (composer.json / composer.lock): require a tracked composer.lock, strictly validate manifest/lock agreement, run composer install --no-dev --no-interaction --no-progress --prefer-dist --optimize-autoloader, and fail if the lockfile changes
  • Migrations changed (database/migrations/*.php): php artisan migrate --force
  • Node dependency files changed (package.json / package-lock.json / npm-shrinkwrap.json, including nested): use npm ci when an npm lockfile is tracked and fail if it changes; otherwise use npm install --no-package-lock so an intentional no-lock policy stays non-mutating
  • Frontend files changed (.vue/.js/.ts/.tsx/.css/.scss/.less): npm run build
    • Note: npm run build is also scheduled when Node dependency installation is scheduled.
    • With the local-artifact frontend strategy, the build runs locally against the final prepared commit. Zephyr uploads and verifies public/build, then skips remote Node installation and frontend compilation for that deployment.
  • PHP files changed: clear caches + restart queue workers (Horizon if configured)

Configuration

Global Server Configuration

Servers are stored globally at ~/.config/zephyr/servers.json:

[
  {
    "id": "server_abc123",
    "serverName": "production",
    "serverIp": "192.168.1.100",
    "sshAlias": "production-app"
  }
]

sshAlias is optional. When present, Zephyr runs remote commands through the system OpenSSH client using that Host entry, so settings such as ProxyJump, custom ports, and host-specific connection options in ~/.ssh/config are honoured. The configured application SSH user and key are still passed explicitly. Without sshAlias, Zephyr keeps using its direct node-ssh transport.

Project Configuration

Deployment targets are stored per-project at .zephyr/config.json:

{
  "presets": [
    {
      "name": "prod-main",
      "appId": "app_def456",
      "branch": "main",
      "options": {
        "maintenanceMode": true,
        "frontendBuildStrategy": "local-artifact",
        "skipGitHooks": false,
        "skipTests": false,
        "skipLint": false,
        "skipVersioning": false,
        "autoCommit": true
      }
    }
  ],
  "apps": [
    {
      "id": "app_def456",
      "serverId": "server_abc123",
      "serverName": "production",
      "projectPath": "~/webapps/myapp",
      "branch": "main",
      "sshUser": "forge",
      "sshKey": "~/.ssh/id_rsa"
    }
  ],
  "groups": [
    {
      "name": "Production targets",
      "presets": [
        "prod-main"
      ]
    }
  ]
}

Preset options capture repeatable deploy behavior so Zephyr can reuse the same maintenance, dirty-tree, and deploy-check preferences on later runs.

Deployment groups run the named presets in order. Zephyr performs local dependency validation once before the group starts, and after the first successful target for a branch it skips local checks and versioning for later targets on that same branch. Targets on a different branch are prepared separately.

Project Directory Structure

Zephyr creates a .zephyr/ directory in your project with:

  • config.json - Project deployment configuration
  • deploy.lock - Lock file to prevent concurrent deployments
  • pending-tasks.json - Task snapshot for resuming failed deployments
  • {timestamp}.log - Log files for each deployment run

The .zephyr/ directory is automatically added to .gitignore.

Notes

  • If Zephyr reports "No upstream file changes detected", ordinary change-based tasks are skipped. Explicitly configured lifecycle/health tasks still run, and a pinned release verifies its exact commit even when there is no diff.
  • If Zephyr prompts to update local file dependencies (path-based deps outside the repo), it may also prompt to commit those updates before continuing.

Requirements

  • Node.js 16+
  • Git
  • SSH access to target servers

Optional CI gate and verified application releases

Existing presets keep their behavior. A preset can opt into an exact-commit GitHub Actions gate and explicit Laravel deployment tasks through its options:

{
  "ci": {
    "enabled": true,
    "requiredWorkflows": ["tests"],
    "timeoutMs": 900000
  },
  "deployment": {
    "packageManager": "pnpm",
    "publishShiftAssets": true,
    "optimizeCaches": true,
    "restartSsr": true,
    "healthChecks": [
      {"label": "Check Inertia SSR", "command": "php8.4 artisan inertia:check-ssr"}
    ],
    "healthUrl": "https://your-app.example/",
    "healthTimeoutMs": 15000
  }
}

These belong beside existing maintenance/validation preferences in the saved preset's options, not at the top level of .zephyr/config.json. Keep that file machine-local and ignored. Arbitrary health commands run as the configured deployment SSH user; configure only trusted commands and never embed credentials. packageManager supports npm and pnpm. linkStorage: true adds artisan storage:link after migrations; restartReverb: true adds artisan reverb:restart after queue/SSR restarts. keepMaintenanceOnFailure: true preserves Laravel maintenance mode for operator recovery after a confirmed deployment failure or signal interruption; its default is false, retaining Zephyr’s existing automatic maintenance recovery. Unknown remote outcomes already skip automatic recovery regardless of this flag. Omitted lifecycle flags remain off. No CI gate means no GitHub CLI requirement or CI queries.

An enabled gate checks successful whole workflows, matched by their unique displayed names, for the final local Git SHA, selected branch and push event in the origin repository. It supports private repositories when the authenticated gh account has Actions read access. Missing CLI/access prompts interactive users to install gh or run gh auth login, then choose Retry or Cancel. Non-interactive releases stop with actionable errors. Missing/pending workflows wait within the total deadline; failures, cancellation and unavailable providers stop deployment. The gate cannot be bypassed by --skip-checks. External commit statuses and individual jobs are not configuration targets in this version.

The gate uses bounded periodic GitHub status requests only while an explicitly enabled release is waiting (a narrow release-watcher exception to the shared no-polling preference). It does not run a background monitor or require a public webhook endpoint. The default wait is fifteen minutes, including provider reads and interactive access recovery. Cancellation aborts pending GitHub subprocesses; nothing proceeds to the remote deployment lock after a cancelled or failed gate.

Opting into either deployment settings or CI pins the deployment to the prepared commit. The remote branch and checkout must match that SHA; an advanced branch fails rather than deploying unchecked code. Pending snapshots retain the SHA and explicit CI/deployment settings, and reject another local commit or target on resume. Existing snapshots and legacy presets remain compatible.

pnpm deployment uses Corepack and a tracked frozen lockfile. SHIFT publishing runs after any needed Composer installation. Cache optimization clears configuration before migrations and optimizes after the build; queue and opted-in SSR restarts follow. Named shell health checks run after maintenance mode is disabled. The optional HTTP check makes a single bounded request and requires a 2xx response; it does not follow redirects or retry. A release reports success only after its configured checks and deployed-commit verification succeed.

Remote stdout/stderr now stream during each named step and remain available in the release log. JSON mode emits remote_output events with data.stepLabel and data.stream, preserving valid NDJSON. Confirmed command failures retain the failed step and exit code. Lost SSH or interrupted in-flight commands produce REMOTE_OUTCOME_UNKNOWN; Zephyr preserves pending state/locks and does not automatically rollback, disable maintenance or repeat commands that may still be running. Inspect the remote process, checkout, maintenance state and logs before explicitly resolving the lock and choosing resume/discard. There is no automatic rollback of database migrations.

Forge can continue managing server provisioning, TLS and supervised services while Zephyr performs the application deployment over SSH. Disable other deployment triggers when adopting one release entrypoint. Verify the new preset against its intended host before retiring an existing deployment script.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages