How Openship Detects and Builds Programming Stacks: Framework Detection Deep Dive
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 (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, 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 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
fileMatchfunction or checks for the presence ofrootMarkersdefined in the stack registry. - Dependency gate — Confirms either a custom
depMatchvalidator or verifies that requireddepsare present in the collected dependencies. - Content gate — Applies either a custom
contentMatchfunction or regex-basedcontentPatternsagainst 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.
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.
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.
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.tsto define stack metadata, detection rules, and build configurations for supported frameworks. - The detection algorithm in
apps/api/src/lib/stack-detector.tsemploys 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
getBuildImageandgetRuntimeImage, with sensible language defaults when stacks don't specify overrides. - The
defaultBuildStrategyfield determines whether builds execute locally or in remote workspaces, configured throughgetStackDefaultsbased 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, go.mod, or 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →