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

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. 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 (lines 55‑56), the system executes a shallow clone to minimize bandwidth and storage:

// 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:

// 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 (lines 60‑62) shows this conditional execution:

// 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:

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, 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 demonstrates how local and SSH-based builds execute:

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, 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 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
  • 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →