# How the Openship Build Pipeline Handles Clone, Install, and Build Steps

> Discover how the Openship build pipeline efficiently manages clone, install, and build steps. Learn about its streamlined process for real-time visibility and error handling.

- Repository: [oblien/openship](https://github.com/oblien/openship)
- Tags: internals
- Published: 2026-07-30

---

**The Openship build pipeline orchestrates clone, install, and build steps through a shared `runBuildPipeline()` function that delegates commands to a `BuildEnvironment` implementation while streaming output via `BuildLogger` to provide real-time visibility and structured error handling.**

The oblien/openship repository defines a unified deployment system that standardizes application builds across local machines, remote SSH servers, and cloud environments. The **Openship build pipeline** executes a deterministic three-phase process—clone, install, and build—through a shared abstraction layer that ensures consistent logging and failure handling regardless of the underlying runtime implementation.

## Pipeline Entry Point and Architecture

The central coordination logic resides in `runBuildPipeline()`, documented in [`packages/adapters/docs/BUILD-PIPELINE.md`](https://github.com/oblien/openship/blob/main/packages/adapters/docs/BUILD-PIPELINE.md). This function accepts three core dependencies: a `BuildEnvironment` instance that handles command execution, a `DeployConfig` object containing repository and build settings, and a `BuildLogger` that manages log streaming and step lifecycle events.

The pipeline executes three distinct phases sequentially: repository cloning, dependency installation, and optional build execution. Each phase wraps its command execution in `logger.runStep()` calls, which emit structured events marking transitions from `running` to `completed` or `failed` states.

## Step 1: Repository Cloning

The pipeline begins by cloning the target repository into the working directory. According to the source code in [`packages/adapters/docs/BUILD-PIPELINE.md`](https://github.com/oblien/openship/blob/main/packages/adapters/docs/BUILD-PIPELINE.md) (lines 55‑56), the system executes a shallow clone to minimize bandwidth and storage:

```typescript
// BUILD-PIPELINE.md – line 55‑56
env.exec(`git clone --branch ${branch} --depth 1 ${repo} .`, logger.callback)

```

The `--depth 1` flag ensures only the latest commit history is fetched, while the branch specification guarantees the correct deployment target is checked out. The `env.exec()` method delegates the actual command execution to the specific `BuildEnvironment` implementation, which handles process spawning and directory context.

## Step 2: Dependency Installation

Following a successful clone, the pipeline detects the configured package manager and executes the appropriate install command. As implemented in the pipeline logic, the system supports **npm**, **yarn**, and **pnpm** through conditional command selection:

```typescript
// BUILD-PIPELINE.md – line 57‑58
env.exec(`npm install` /* or yarn / pnpm install */, logger.callback)

```

This step streams all console output—both stdout and stderr—through the `logger.callback` to provide real-time installation logs in the UI. The command executes within the project directory established during the clone phase.

## Step 3: Build Execution

If the deployment configuration defines a `buildCommand`, the pipeline executes it as the final phase. Common examples include `npm run build` or `yarn build`. The implementation in [`packages/adapters/docs/BUILD-PIPELINE.md`](https://github.com/oblien/openship/blob/main/packages/adapters/docs/BUILD-PIPELINE.md) (lines 60‑62) shows this conditional execution:

```typescript
// BUILD-PIPELINE.md – line 60‑62
env.exec(buildCommand, logger.callback)

```

Like the previous steps, build output streams continuously to the logger, allowing developers to monitor compilation progress and errors as they occur. The complete pipeline implementation illustrates how these steps compose together:

```typescript
async function runBuildPipeline(env: BuildEnvironment, config: DeployConfig, logger: BuildLogger) {
  // 1️⃣ Clone repository
  await logger.runStep("clone", async () => {
    await env.exec(`git clone --branch ${config.branch} --depth 1 ${config.repo} .`, logger.callback);
  });

  // 2️⃣ Install dependencies
  await logger.runStep("install", async () => {
    const cmd = config.packageManager === "yarn" ? "yarn" :
                config.packageManager === "pnpm" ? "pnpm install" :
                "npm install";
    await env.exec(cmd, logger.callback);
  });

  // 3️⃣ Build the project (optional)
  if (config.buildCommand) {
    await logger.runStep("build", async () => {
      await env.exec(config.buildCommand, logger.callback);
    });
  }

  return { status: "deploying", durationMs: Date.now() - start };
}

```

## BuildLogger and Step Lifecycle Management

The `BuildLogger` class, located in [`packages/adapters/src/logger/BuildLogger.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/logger/BuildLogger.ts), serves as the single source of truth for log emission across all pipeline phases. The `logger.runStep()` wrapper encapsulates each phase in structured lifecycle events that update the deployment status and make logs available via Server-Sent Events (SSE).

When a step initiates, the logger marks it as `running`. Upon completion, it transitions to `completed`. If the `BuildEnvironment.exec` method encounters a non-zero exit code, it throws an error that the pipeline catches to record a `failed` status via `logger.step(step, "failed", …)`.

## BuildEnvironment Abstraction

The pipeline achieves runtime portability through the `BuildEnvironment` interface. The `BareBuildEnv` implementation in [`packages/adapters/src/runtime/bareRuntime.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/runtime/bareRuntime.ts) demonstrates how local and SSH-based builds execute:

```typescript
class BareBuildEnv implements BuildEnvironment {
  projectDir = "/var/openship/apps/my-app";

  async exec(command: string, onLog: LogCallback) {
    // Delegates to the executor that streams the command output
    await executor.streamExec(command, { cwd: this.projectDir }, onLog);
  }
}

```

This abstraction delegates command execution to [`packages/adapters/src/executor.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/executor.ts), which handles the actual process streaming. The same pipeline logic operates identically across Bare, Cloud, and future Docker runtimes without modification.

## Error Handling and Deployment Status

Error propagation follows a strict pattern: if any `env.exec()` call returns a non-zero exit code, the `BuildEnvironment` throws an error immediately. The pipeline catches this exception, invokes the failure logging protocol, and terminates further execution. The [`packages/adapters/src/build/build.service.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/build/build.service.ts) file coordinates these status updates, switching the deployment state to **failed** and persisting the error details for UI display.

## Summary

- The **Openship build pipeline** centralizes build logic in `runBuildPipeline()` within [`packages/adapters/docs/BUILD-PIPELINE.md`](https://github.com/oblien/openship/blob/main/packages/adapters/docs/BUILD-PIPELINE.md)
- **Three sequential phases**—clone, install, and build—execute via the `BuildEnvironment` abstraction
- **Shallow cloning** (`--depth 1`) optimizes repository fetch performance for the specified branch
- **Multi-package-manager support** automatically selects npm, yarn, or pnpm based on configuration
- **BuildLogger** provides unified step lifecycle management and SSE streaming across all runtime environments
- **Non-zero exit codes** immediately trigger failure states and halt pipeline execution

## Frequently Asked Questions

### What happens if a step in the Openship build pipeline fails?

If any command executed via `env.exec()` returns a non-zero exit code, the `BuildEnvironment` throws an error that the pipeline catches and logs via `logger.step(step, "failed", …)`. This immediately halts further execution, updates the deployment status to failed, and streams the error details to the UI through the existing SSE connection.

### Does the Openship build pipeline support package managers other than npm?

Yes. The pipeline automatically detects and executes the appropriate command for **npm**, **yarn**, or **pnpm** based on the `packageManager` field in the deployment configuration. The logic uses conditional selection to run either `npm install`, `yarn`, or `pnpm install` during the install phase.

### How does the pipeline handle logging across different runtime environments?

The `BuildLogger` class in [`packages/adapters/src/logger/BuildLogger.ts`](https://github.com/oblien/openship/blob/main/packages/adapters/src/logger/BuildLogger.ts) acts as a unified logging abstraction. Regardless of whether the build runs locally via `BareBuildEnv`, remotely via SSH, or through the Oblien Cloud API, all environments stream output through the same `logger.callback` interface, ensuring consistent real-time log availability via SSE.

### Is the build step optional in the Openship pipeline?

Yes. The build phase only executes if the `buildCommand` property is defined in the deployment configuration. The pipeline checks for this condition before invoking `logger.runStep("build", …)`, allowing deployments to proceed directly from install to deployment for applications that do not require a compilation step.