npm package: @pxilab/nextship
Description: Next.js deployment tool - Build, SSH upload, PM2 restart
Author: PXI Lab
Latest version: 0.5.1

# PXI NextShip

Next.js deployment tool - Build, SSH upload, PM2 restart.

## Installation

```bash
# npm
npm install -g @pxilab/nextship

# bun
bun add -g @pxilab/nextship

# Or run directly
npx @pxilab/nextship ship
bunx @pxilab/nextship ship
```

## Quick Start

1. **Configure your Next.js project for standalone output:**

```js
// next.config.js
module.exports = {
  output: 'standalone',
}
```

2. **Create a config file:**

```bash
cp pxnship.config.example.js pxnship.config.js
```

3. **Deploy:**

```bash
pxnship ship
```

## Usage

```bash
# Full deployment (build → upload → restart)
pxnship ship

# Individual steps
pxnship build      # Build Next.js application
pxnship upload     # Upload files to server
pxnship restart    # Restart PM2 application

# Options
pxnship ship --dry-run      # Preview without making changes
pxnship ship --skip-build   # Skip build step (use existing build)
pxnship ship --verbose      # Enable detailed output
pxnship ship --config ./custom-config.js  # Use custom config file
```

## Configuration

### Config File

Create a config file in your project root. Supported formats:
- `pxnship.config.mjs` (recommended - ES modules)
- `pxnship.config.js`
- `pxnship.config.ts`
- `.pxnshiprc` or `.pxnshiprc.json`

Example `pxnship.config.mjs`:

```js
export default {
  ssh: {
    host: "server.example.com",
    user: "deploy",
    port: 22,
    privateKeyPath: "~/.ssh/id_ed25519",
  },

  build: {
    command: "bun run build",
    standalone: true,
    prepareLocally: true, // public/ ve static/ dosyalarını local'de standalone içine kopyalar
  },

  upload: {
    remotePath: "/var/www/myapp",
    exclude: [".git", "node_modules", ".env.local"],
  },

  pm2: {
    appName: "myapp",
    ecosystem: true,  // true = auto-detect, false = don't use, "filename.js" = specific file
    reload: true,
    port: 3000,       // Used when ecosystem is false
  },
};
```

### Environment Variables

PXI NextShip loads environment files in the following order (later files override earlier ones):

1. `.env` - Base environment variables
2. `.env.local` - Local overrides (add to `.gitignore`)

```bash
# .env.local (recommended for local development)
SSH_HOST=server.example.com
SSH_USER=deploy
SSH_PASSWORD=your-password

# Or use SSH key
SSH_KEY_PATH=~/.ssh/id_ed25519
```

**Available Variables:**

```bash
# Required
SSH_HOST=server.example.com
SSH_USER=deploy

# Authentication (use one)
SSH_KEY=<private-key-content>    # Inline private key (for CI/CD)
SSH_KEY_PATH=~/.ssh/id_ed25519   # Path to private key file
SSH_PASSWORD=<password>          # Password auth

# Optional
SSH_PORT=22
REMOTE_PATH=/var/www/myapp
PM2_APP_NAME=myapp
BUILD_COMMAND="bun run build"
```

## GitHub Actions

```yaml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: oven-sh/setup-bun@v2

      - run: bun install

      - name: Deploy
        run: bunx @pxilab/nextship ship
        env:
          SSH_HOST: ${{ secrets.SSH_HOST }}
          SSH_USER: ${{ secrets.SSH_USER }}
          SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          PM2_APP_NAME: myapp
          REMOTE_PATH: /var/www/myapp
```

## Server Setup

### Directory Structure

```
/var/www/myapp/
├── .next/
│   └── standalone/
│       ├── .next/
│       │   └── static/     # Build sırasında kopyalanır
│       ├── public/         # Build sırasında kopyalanır
│       ├── package.json    # Next.js standalone build oluşturur
│       └── server.js
└── ecosystem.config.js     # Optional (ayrıca upload edilmeli)
```

> **Not:** `prepareLocally: true` (default) kullanıldığında `public/` ve `.next/static/`
> klasörleri build sırasında local'de `.next/standalone/` içine kopyalanır.
> Sunucuya sadece `.next/standalone/` gönderilir. `package.json` zaten standalone içinde.

### PM2 Configuration

PXI NextShip supports three modes for PM2:

| `ecosystem` value | Behavior |
|-------------------|----------|
| `true` (default) | Auto-detect `ecosystem.config.js` in remote directory |
| `false` | Don't use ecosystem file, start with `server.js` directly |
| `"custom.config.js"` | Use a specific ecosystem file |

When `ecosystem: false`, you can specify a custom port with the `port` option.

### PM2 Environment Variables

Inject environment variables when PM2 starts or reloads:

```js
pm2: {
  appName: "myapp",
  ecosystem: false,  // Standalone mode
  port: 3000,
  env: {
    NODE_ENV: "production",
    API_BASE_URL: process.env.API_BASE_URL,
    DATABASE_URL: process.env.DATABASE_URL,
  },
}
```

Environment variables are passed to PM2 via shell environment and persist after `pm2 save`.

**Note:** When using `ecosystem: true`, define environment variables in your `ecosystem.config.js` file instead for better management.

### PM2 Ecosystem File (Optional)

```js
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'myapp',
    script: '.next/standalone/server.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 3000,
    },
  }],
};
```

### Initial Server Setup

```bash
# Install PM2
npm install -g pm2

# Create directory
mkdir -p /var/www/myapp
chown deploy:deploy /var/www/myapp

# After first deploy
cd /var/www/myapp
pm2 start ecosystem.config.js  # or: pm2 start .next/standalone/server.js --name myapp
pm2 save
pm2 startup
```

## Platform Support

| Platform | Transfer Method | Performance |
|----------|-----------------|-------------|
| Linux / macOS | rsync | Fast (delta transfer) |
| Windows + WSL | rsync via WSL | Fast (delta transfer) |
| Windows (no rsync) | SFTP fallback | Slower (full file transfer) |

### Windows Server Recommendation

For optimal performance on Windows servers, install WSL + rsync:

```powershell
wsl --install -d Ubuntu
wsl sudo apt update && sudo apt install rsync -y
```

Without rsync, the tool automatically falls back to SFTP.

## Programmatic API

```typescript
import { loadConfig, runShip, runBuild, runUpload, runRestart } from '@pxilab/nextship';

const config = await loadConfig();

// Full deployment
await runShip(config);

// Or individual steps
await runBuild(config.build);
await runUpload(config.ssh, config.upload);
await runRestart(config.ssh, config.pm2);
```

## License

MIT