CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-now--build-utils

Build utilities for Now (Vercel) serverless platform runtime development

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

detection.mddocs/

Detection System

Automatic detection of project frameworks and appropriate builders for zero-configuration deployments.

Capabilities

Builder Detection

Automatically detect appropriate builders for a project based on its files and configuration.

/**
 * Detect appropriate builders for a project
 * @param files - Array of file paths in the project
 * @param pkg - Package.json contents (for npm projects)
 * @param options - Detection options
 * @returns Object containing detected builders, errors, and warnings
 */
function detectBuilders(
  files: string[],
  pkg?: PackageJson,
  options?: Options
): { builders: Builder[]; errors: ErrorResponse[]; warnings: WarningResponse[] };

interface Options {
  tag?: 'canary' | 'latest' | string;
  functions?: BuilderFunctions;
  ignoreBuildScript?: boolean;
  projectSettings?: {
    framework?: string | null;
    devCommand?: string | null;
    buildCommand?: string | null;
    outputDirectory?: string | null;
  };
}

interface Builder {
  use: string;
  src: string;
  config?: Config;
}

interface ErrorResponse {
  code: string;
  message: string;
}

Usage Examples:

import { detectBuilders } from "@now/build-utils";

// Detect builders for a Node.js project
const files = [
  "index.js",
  "package.json",
  "api/users.js",
  "api/posts.js",
  "public/index.html"
];

const packageJson = {
  name: "my-app",
  scripts: {
    build: "next build"
  },
  dependencies: {
    next: "^12.0.0"
  }
};

const result = detectBuilders(files, packageJson);
console.log("Detected builders:", result.builders);
// Output: [
//   { use: "@vercel/next", src: "package.json" },
//   { use: "@vercel/node", src: "api/**/*.js" }
// ]

// With custom options
const customResult = detectBuilders(files, packageJson, {
  tag: "canary",
  functions: {
    "api/slow.js": { memory: 1024, maxDuration: 60 }
  }
});

Framework Detection

Detect which framework is being used in a project.

/**
 * Detect framework being used in a project
 * @param options - Detection options with filesystem interface
 * @returns Promise resolving to framework slug or null if none detected
 */
function detectFramework(options: DetectFrameworkOptions): Promise<string | null>;

interface DetectFrameworkOptions {
  /** Filesystem interface for reading project files */
  fs: DetectorFilesystem;
  /** List of frameworks to check against */
  frameworkList: Framework[];
}

interface Framework {
  slug: string;
  name: string;
  detectors?: {
    every?: FrameworkDetectionItem[];
    some?: FrameworkDetectionItem[];
  };
}

interface FrameworkDetectionItem {
  path: string;
  matchContent?: string;
}

Usage Examples:

import { detectFramework } from "@now/build-utils";

// Implement filesystem interface
class ProjectFilesystem extends DetectorFilesystem {
  protected async _hasPath(path: string): Promise<boolean> {
    return fs.existsSync(path);
  }
  
  protected async _readFile(path: string): Promise<Buffer> {
    return fs.readFileSync(path);
  }
  
  protected async _isFile(path: string): Promise<boolean> {
    const stat = await fs.lstat(path);
    return stat.isFile();
  }
}

const framework = await detectFramework({
  fs: new ProjectFilesystem(),
  frameworkList: [
    {
      slug: "nextjs",
      name: "Next.js",
      detectors: {
        some: [
          { path: "next.config.js" },
          { path: "package.json", matchContent: '"next"' }
        ]
      }
    }
  ]
});

console.log("Detected framework:", framework); // "nextjs" or null

Route Detection

Detect API routes and generate routing configuration from filesystem structure.

/**
 * Detect API routes from filesystem
 * @param filePaths - Array of file paths to analyze
 * @param builders - Array of detected builders
 * @param options - Route detection options
 * @returns Array of detected routes
 */
function detectRoutes(
  filePaths: string[],
  builders: Builder[],
  options?: DetectRoutesOptions
): Route[];

interface DetectRoutesOptions {
  cleanUrls?: boolean;
  trailingSlash?: boolean;
}

interface Route {
  src: string;
  dest?: string;
  headers?: { [key: string]: string };
  methods?: string[];
  status?: number;
}

Usage Examples:

import { detectRoutes } from "@now/build-utils";

const filePaths = [
  "api/users/[id].js",
  "api/posts/index.js",
  "api/auth/login.js"
];

const builders = [
  { use: "@vercel/node", src: "api/**/*.js" }
];

const routes = detectRoutes(filePaths, builders, {
  cleanUrls: true
});

console.log("Generated routes:", routes);
// Routes for dynamic routing, API endpoints, etc.

Directory Detection

Detect specific directories and their purposes from builder configurations.

/**
 * Detect output directory from builders
 * @param builders - Array of detected builders
 * @returns Output directory path or null if not found
 */
function detectOutputDirectory(builders: Builder[]): string | null;

/**
 * Detect API directory from builders
 * @param builders - Array of detected builders
 * @returns API directory path or null if not found
 */
function detectApiDirectory(builders: Builder[]): string | null;

/**
 * Detect API file extensions from builders
 * @param builders - Array of detected builders
 * @returns Set of supported file extensions
 */
function detectApiExtensions(builders: Builder[]): Set<string>;

Usage Examples:

import { 
  detectBuilders,
  detectOutputDirectory, 
  detectApiDirectory, 
  detectApiExtensions 
} from "@now/build-utils";

// First detect builders from project files
const files = [
  "dist/index.html",
  "dist/assets/style.css", 
  "api/hello.js",
  "api/users.ts"
];

const builders = detectBuilders(files).builders;

// Then use builders to detect directories and extensions
const outputDir = detectOutputDirectory(builders);
console.log("Output directory:", outputDir); // "dist" or null

const apiDir = detectApiDirectory(builders);
console.log("API directory:", apiDir); // "api" or null

const apiExts = detectApiExtensions(builders);
console.log("API extensions:", apiExts); // Set([".js", ".ts"])

Filesystem Detection Interface

DetectorFilesystem

Abstract filesystem interface for framework detection that can be implemented for different storage backends.

/**
 * Abstract filesystem interface for framework detection
 */
abstract class DetectorFilesystem {
  /** Check if a path exists */
  hasPath(path: string): Promise<boolean>;
  /** Check if a path represents a file */
  isFile(path: string): Promise<boolean>;
  /** Read file contents as Buffer */
  readFile(path: string): Promise<Buffer>;
  
  // Abstract methods that must be implemented
  protected abstract _hasPath(path: string): Promise<boolean>;
  protected abstract _readFile(path: string): Promise<Buffer>;
  protected abstract _isFile(path: string): Promise<boolean>;
}

Usage Examples:

import { DetectorFilesystem } from "@now/build-utils";

// Local filesystem implementation
class LocalFilesystem extends DetectorFilesystem {
  constructor(private basePath: string) {
    super();
  }
  
  protected async _hasPath(path: string): Promise<boolean> {
    const fullPath = require('path').join(this.basePath, path);
    return require('fs').existsSync(fullPath);
  }
  
  protected async _readFile(path: string): Promise<Buffer> {
    const fullPath = require('path').join(this.basePath, path);
    return require('fs').readFileSync(fullPath);
  }
  
  protected async _isFile(path: string): Promise<boolean> {
    const fullPath = require('path').join(this.basePath, path);
    const stat = await require('fs').promises.lstat(fullPath);
    return stat.isFile();
  }
}

// HTTP filesystem implementation
class HttpFilesystem extends DetectorFilesystem {
  constructor(private baseUrl: string) {
    super();
  }
  
  protected async _hasPath(path: string): Promise<boolean> {
    try {
      const response = await fetch(`${this.baseUrl}/${path}`, { method: 'HEAD' });
      return response.ok;
    } catch {
      return false;
    }
  }
  
  protected async _readFile(path: string): Promise<Buffer> {
    const response = await fetch(`${this.baseUrl}/${path}`);
    const arrayBuffer = await response.arrayBuffer();
    return Buffer.from(arrayBuffer);
  }
  
  protected async _isFile(path: string): Promise<boolean> {
    // Implementation depends on HTTP API capabilities
    return this._hasPath(path);
  }
}

Detection Patterns

Framework Detection Patterns

Common patterns used for framework detection:

const frameworkPatterns = {
  nextjs: {
    some: [
      { path: "next.config.js" },
      { path: "next.config.ts" },
      { path: "package.json", matchContent: '"next"' }
    ]
  },
  nuxtjs: {
    some: [
      { path: "nuxt.config.js" },
      { path: "nuxt.config.ts" },
      { path: "package.json", matchContent: '"nuxt"' }
    ]
  },
  gatsby: {
    some: [
      { path: "gatsby-config.js" },
      { path: "gatsby-config.ts" },
      { path: "package.json", matchContent: '"gatsby"' }
    ]
  }
};

Builder Selection Logic

The detection system uses the following priority for selecting builders:

  1. API functions: Files in api/ directory get function builders
  2. Framework detection: Detected frameworks get their specific builders
  3. Build scripts: Projects with build scripts get static builders
  4. File extensions: Fallback based on file types (.js, .ts, .py, etc.)
  5. Zero config: Default builders for common patterns

Install with Tessl CLI

npx tessl i tessl/npm-now--build-utils

docs

detection.md

file-classes.md

filesystem.md

index.md

lambda.md

script-execution.md

tile.json