CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-gatsby-core-utils

A collection of core utility functions for the Gatsby ecosystem including content hashing, path manipulation, system information, and process coordination

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

process-coordination.mddocs/

Process Coordination

Service locking and process coordination utilities for managing concurrent Gatsby processes, preventing conflicts, and enabling inter-process communication during builds and development.

Capabilities

Service Locking

Creates lockfiles for coordinating between multiple Gatsby processes and preventing conflicts.

/**
 * Creates a lockfile for service coordination between Gatsby processes
 * @param programPath - Path to the Gatsby site directory
 * @param serviceName - Name of the service to lock (e.g., "build", "develop")
 * @param content - Data to store with the lock (process info, configuration, etc.)
 * @returns Promise resolving to unlock function or null if lock failed
 */
function createServiceLock(
  programPath: string,
  serviceName: string,
  content: Record<string, any>
): Promise<UnlockFn | null>;

/**
 * Function type for unlocking services
 */
type UnlockFn = () => Promise<void>;

Usage Examples:

import { createServiceLock } from "gatsby-core-utils";

// Lock build service to prevent concurrent builds
const unlockBuild = await createServiceLock(
  process.cwd(),
  "build",
  {
    pid: process.pid,
    startedAt: Date.now(),
    nodeVersion: process.version,
    command: process.argv.join(" ")
  }
);

if (unlockBuild) {
  try {
    console.log("Build lock acquired, starting build...");
    await runBuild();
  } finally {
    // Always unlock when done
    await unlockBuild();
    console.log("Build lock released");
  }
} else {
  console.error("Could not acquire build lock - another build may be running");
  process.exit(1);
}

// Lock development server
const unlockDevelop = await createServiceLock(
  "/path/to/gatsby-site",
  "develop",
  {
    pid: process.pid,
    port: 8000,
    host: "localhost",
    startedAt: Date.now()
  }
);

Service Information Retrieval

Retrieves information about locked services and their current state.

/**
 * Retrieves service data if the service is currently locked
 * @param programPath - Path to the Gatsby site directory
 * @param serviceName - Name of the service to query
 * @param ignoreLockfile - Whether to ignore lock status and return data anyway
 * @returns Promise resolving to service data or null if not locked/found
 */
function getService<T>(
  programPath: string,
  serviceName: string,
  ignoreLockfile?: boolean
): Promise<T | null>;

Usage Examples:

import { getService } from "gatsby-core-utils";

// Check if build is currently running
const buildService = await getService(process.cwd(), "build");
if (buildService) {
  console.log(`Build running since: ${new Date(buildService.startedAt)}`);
  console.log(`Build PID: ${buildService.pid}`);
  console.log(`Node version: ${buildService.nodeVersion}`);
} else {
  console.log("No build currently running");
}

// Get development server info
interface DevelopService {
  pid: number;
  port: number;
  host: string;
  startedAt: number;
}

const developService = await getService<DevelopService>(
  "/path/to/site",
  "develop"
);

if (developService) {
  console.log(`Dev server running on http://${developService.host}:${developService.port}`);
  console.log(`Started: ${new Date(developService.startedAt)}`);
}

// Force retrieve service data (ignore lock status)
const serviceData = await getService(process.cwd(), "build", true);

All Services Retrieval

Gets information about all services for a Gatsby site.

/**
 * Gets all services for a Gatsby site
 * @param programPath - Path to the Gatsby site directory
 * @returns Promise resolving to object containing all services data
 */
function getServices(programPath: string): Promise<any>;

Usage Examples:

import { getServices } from "gatsby-core-utils";

// Get overview of all running services
const services = await getServices(process.cwd());
console.log("Active services:", Object.keys(services));

// Display service status dashboard
for (const [serviceName, serviceData] of Object.entries(services)) {
  if (serviceData) {
    console.log(`${serviceName}: Running (PID ${serviceData.pid})`);
  }
}

// Check for conflicts before starting new service
const allServices = await getServices("/path/to/site");
const hasConflicts = Object.values(allServices).some(service => 
  service && service.port === desiredPort
);

if (hasConflicts) {
  console.error(`Port ${desiredPort} already in use by another service`);
}

Resource Locking

Generic resource locking for coordinating access to shared resources.

/**
 * Creates a lock for the specified resource
 * @param resources - Resource identifier to lock
 * @returns Promise resolving to unlock function
 */
function lock(resources: string): Promise<() => void>;

Usage Examples:

import { lock } from "gatsby-core-utils";

// Lock shared cache directory
const unlockCache = await lock("cache-directory");
try {
  // Perform cache operations that need exclusive access
  await cleanupCacheFiles();
  await rebuildCacheIndex();
} finally {
  unlockCache();
}

// Lock database or file resources
const unlockDatabase = await lock("site-database");
try {
  await performDatabaseMigration();
} finally {
  unlockDatabase();
}

// Multiple processes coordinating file access
const unlockConfigFile = await lock("gatsby-config");
try {
  const config = await readConfigFile();
  const updatedConfig = await processConfig(config);
  await writeConfigFile(updatedConfig);
} finally {
  unlockConfigFile();
}

Advanced Usage Patterns

Process Lifecycle Management

import { createServiceLock, getService } from "gatsby-core-utils";

class GatsbyBuildManager {
  private unlockFn: UnlockFn | null = null;
  
  async startBuild() {
    // Check if build already running
    const existingBuild = await getService(this.sitePath, "build");
    if (existingBuild) {
      throw new Error(`Build already running (PID: ${existingBuild.pid})`);
    }
    
    // Acquire lock
    this.unlockFn = await createServiceLock(this.sitePath, "build", {
      pid: process.pid,
      startedAt: Date.now(),
      buildId: this.generateBuildId(),
      environment: process.env.NODE_ENV
    });
    
    if (!this.unlockFn) {
      throw new Error("Failed to acquire build lock");
    }
    
    // Register cleanup handlers
    process.on('SIGINT', () => this.cleanup());
    process.on('SIGTERM', () => this.cleanup());
    process.on('uncaughtException', () => this.cleanup());
  }
  
  async cleanup() {
    if (this.unlockFn) {
      await this.unlockFn();
      this.unlockFn = null;
    }
  }
}

Service Discovery and Health Checking

import { getServices, getService } from "gatsby-core-utils";

async function checkServiceHealth(sitePath: string) {
  const services = await getServices(sitePath);
  const healthReport = {};
  
  for (const [serviceName, serviceData] of Object.entries(services)) {
    if (serviceData && serviceData.pid) {
      try {
        // Check if process is still running
        process.kill(serviceData.pid, 0);
        healthReport[serviceName] = {
          status: 'healthy',
          pid: serviceData.pid,
          uptime: Date.now() - serviceData.startedAt
        };
      } catch (error) {
        // Process not found - stale lock
        healthReport[serviceName] = {
          status: 'stale',
          pid: serviceData.pid,
          error: 'Process not found'
        };
      }
    }
  }
  
  return healthReport;
}

// Usage
const health = await checkServiceHealth(process.cwd());
console.log("Service health:", health);

Cross-Process Communication

import { createServiceLock, getService } from "gatsby-core-utils";

// Producer process
async function startDataProcessor() {
  const unlock = await createServiceLock(process.cwd(), "data-processor", {
    pid: process.pid,
    status: "initializing",
    progress: 0,
    lastUpdate: Date.now()
  });
  
  // Update progress periodically
  setInterval(async () => {
    await unlock(); // Release old lock
    const unlock2 = await createServiceLock(process.cwd(), "data-processor", {
      pid: process.pid,
      status: "processing", 
      progress: getCurrentProgress(),
      lastUpdate: Date.now()
    });
    unlock = unlock2;
  }, 5000);
}

// Consumer process
async function monitorDataProcessing() {
  const processorInfo = await getService(process.cwd(), "data-processor");
  if (processorInfo) {
    console.log(`Processing ${processorInfo.progress}% complete`);
    console.log(`Status: ${processorInfo.status}`);
    console.log(`Last update: ${new Date(processorInfo.lastUpdate)}`);
  }
}

Error Handling and Edge Cases

Lock Acquisition Failures

import { createServiceLock } from "gatsby-core-utils";

async function safeLockAcquisition(programPath: string, serviceName: string) {
  const maxRetries = 3;
  const retryDelay = 1000;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const unlock = await createServiceLock(programPath, serviceName, {
        pid: process.pid,
        attempt: attempt,
        timestamp: Date.now()
      });
      
      if (unlock) {
        return unlock;
      }
      
      console.log(`Lock acquisition failed (attempt ${attempt}/${maxRetries})`);
      
      if (attempt < maxRetries) {
        await new Promise(resolve => setTimeout(resolve, retryDelay * attempt));
      }
    } catch (error) {
      console.error(`Lock error on attempt ${attempt}:`, error.message);
    }
  }
  
  throw new Error(`Failed to acquire lock after ${maxRetries} attempts`);
}

Stale Lock Cleanup

import { getServices } from "gatsby-core-utils";

async function cleanupStaleLocks(programPath: string) {
  const services = await getServices(programPath);
  const staleServices = [];
  
  for (const [serviceName, serviceData] of Object.entries(services)) {
    if (serviceData && serviceData.pid) {
      try {
        // Test if process exists
        process.kill(serviceData.pid, 0);
      } catch (error) {
        // Process doesn't exist - stale lock
        staleServices.push(serviceName);
      }
    }
  }
  
  console.log(`Found ${staleServices.length} stale locks:`, staleServices);
  // Manual cleanup would involve removing lock files
}

Integration with Gatsby

Process coordination is critical for Gatsby's multi-process architecture:

  • Build coordination: Preventing multiple builds from running simultaneously
  • Development server: Managing dev server lifecycle and port allocation
  • Plugin processes: Coordinating plugin workers and background tasks
  • Cache management: Preventing concurrent cache operations and corruption
  • File watching: Coordinating file system watchers across processes

These utilities ensure that Gatsby's complex build system operates reliably even with multiple concurrent processes and shared resources.

docs

configuration-management.md

content-hashing.md

file-operations.md

index.md

path-utilities.md

process-coordination.md

system-information.md

tile.json