# How Openship Detects and Builds Programming Stacks: Framework Detection Deep Dive

> Explore how Openship detects and builds programming stacks by scanning repository files against a centralized registry. Learn about framework detection rules and build commands.

- Repository: [oblien/openship](https://github.com/oblien/openship)
- Tags: deep-dive
- Published: 2026-07-23

---

**Openship determines a project's framework and build configuration by scanning repository files and manifests against a centralized registry that defines detection rules, Docker images, and build commands for each supported stack.**

Openship automates the process of recognizing programming languages and frameworks within a repository. By analyzing file structures, dependency manifests, and content patterns, it eliminates manual configuration when deploying applications. This article examines how the `oblien/openship` repository implements algorithms to detect and build programming stacks from source code analysis.

## The Centralized Stack Registry

The foundation of Openship's detection system resides in [`packages/core/src/stacks.ts`](https://github.com/oblien/openship/blob/main/packages/core/src/stacks.ts) (lines 2-140), which serves as the single source of truth for all supported programming stacks. This registry contains a comprehensive list where each entry defines the stack's name, language, category, default Docker images, output directories, ports, and build commands.

Each stack definition includes **root markers** (files that identify the framework), **dependency names**, and **content patterns** used during the detection phase. The registry also stores optional overrides for custom build strategies or required tool versions, ensuring that specific framework quirks are handled without hard-coded logic scattered throughout the codebase.

## Manifest-Based Detection Algorithm

The core detection logic lives in [`apps/api/src/lib/stack-detector.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/lib/stack-detector.ts), where the `detectStack` function analyzes repository snapshots to match projects against registry definitions. The algorithm employs a systematic approach to gather context and apply prioritized matching rules.

### Gathering Repository Context

The detector first collects a **file set** and **file contents** (normalized to lowercase for uniform lookups). It extracts dependencies by parsing [`package.json`](https://github.com/oblien/openship/blob/main/package.json) and invoking language-specific manifest parsers through the `collectDependencies` function (lines 55-76), creating a comprehensive view of the project's requirements before attempting classification.

### Three-Gate Matching System

Using the prioritized `FRAMEWORK_RULES` list defined in the source, Openship evaluates each potential stack through three optional gates (lines 158-176):

- **File gate** — Validates either a custom `fileMatch` function or checks for the presence of `rootMarkers` defined in the stack registry.
- **Dependency gate** — Confirms either a custom `depMatch` validator or verifies that required `deps` are present in the collected dependencies.
- **Content gate** — Applies either a custom `contentMatch` function or regex-based `contentPatterns` against file contents.

Only when all three gates for a specific rule succeed does the algorithm select that stack ID as the matched framework.

### Priority-Based Resolution

The order of `FRAMEWORK_RULES` is intentional: front-end and full-stack frameworks are checked before generic back-end frameworks (lines 96-104). This prioritization prevents false positives where a Next.js application might otherwise be misidentified as Express due to shared dependencies or overlapping file patterns.

```typescript
import { detectStack, type RepoFile } from "@repo/api/src/lib/stack-detector";

const files: RepoFile[] = [
  { name: "package.json" },
  { name: "next.config.js" },
  { name: "pages/index.tsx" },
];
const pkg = { dependencies: { next: "^14.0.0" } };

const result = detectStack(files, pkg);
console.log(result.stack);          // → "nextjs"
console.log(result.buildCommand);   // → "next build"

```

## Deriving Build Configurations

Once a stack is identified, helper functions in the core package resolve the complete build pipeline configuration without requiring additional user input.

### Docker Image Resolution

The `getBuildImage` and `getRuntimeImage` functions (lines 1047-1055) retrieve the appropriate Docker images from the registry, accepting parameters like package manager preferences (e.g., `"pnpm"`) to select optimized base images. When a specific stack doesn't override the defaults, these functions fall back to language-wide defaults defined in the registry.

```typescript
import { getBuildImage } from "@repo/core";

const image = getBuildImage("nextjs", "pnpm"); // respects stack‑specific overrides
// image === "node:22" (default for JavaScript/TypeScript stacks)

```

### Build Strategy Selection

The `getStackDefaults` function (lines 1080-1085) returns the complete stack definition, including the critical `defaultBuildStrategy` field. This strategy determines whether Openship builds the project locally and transfers artifacts (strategy `"local"`) or executes the build remotely in a workspace environment (strategy `"server"`), along with output directories and cache configurations.

```typescript
import { getStackDefaults } from "@repo/core";

const defaults = getStackDefaults("nextjs");
if (defaults.defaultBuildStrategy === "local") {
  // run `next build` locally then transfer the `.next` folder
} else {
  // trigger a remote workspace build
}

```

## Summary

- Openship uses a centralized registry in [`packages/core/src/stacks.ts`](https://github.com/oblien/openship/blob/main/packages/core/src/stacks.ts) to define stack metadata, detection rules, and build configurations for supported frameworks.
- The detection algorithm in [`apps/api/src/lib/stack-detector.ts`](https://github.com/oblien/openship/blob/main/apps/api/src/lib/stack-detector.ts) employs a **three-gate system** (file, dependency, content) with prioritized framework rules to avoid false positives between similar stacks.
- Build images and runtime environments are resolved dynamically using `getBuildImage` and `getRuntimeImage`, with sensible language defaults when stacks don't specify overrides.
- The `defaultBuildStrategy` field determines whether builds execute locally or in remote workspaces, configured through `getStackDefaults` based on the detected stack ID.

## Frequently Asked Questions

### How does Openship determine which programming language a repository uses?

Openship examines the repository's file list and manifest files such as [`package.json`](https://github.com/oblien/openship/blob/main/package.json), `go.mod`, or [`pyproject.toml`](https://github.com/oblien/openship/blob/main/pyproject.toml). It collects dependencies through the `collectDependencies` utility and applies detection rules from the stack registry to identify the specific framework and language category.

### What prevents Openship from confusing similar frameworks like Next.js and Express?

The detection algorithm uses a prioritized `FRAMEWORK_RULES` list where specific front-end and full-stack frameworks are evaluated before generic back-end frameworks. This ordering ensures that a Next.js application containing Express dependencies is correctly identified as Next.js rather than a generic Express application.

### Can Openship detect custom or non-standard stacks?

Yes, the system supports custom detection through extensible rules in the stack registry, including `fileMatch` functions, `depMatch` validators, and `contentPatterns` regex. Organizations can extend the definitions in [`packages/core/src/stacks.ts`](https://github.com/oblien/openship/blob/main/packages/core/src/stacks.ts) to add proprietary frameworks with unique detection requirements.

### How does Openship decide whether to build locally or in a remote workspace?

Each stack definition includes a `defaultBuildStrategy` field set to either `"local"` or `"server"`. The `getStackDefaults` function retrieves this value, determining whether the build executes on the local machine with artifacts transferred afterward, or directly in the remote workspace environment according to the stack's requirements.