A collection of core utility functions for the Gatsby ecosystem including content hashing, path manipulation, system information, and process coordination
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Service locking and process coordination utilities for managing concurrent Gatsby processes, preventing conflicts, and enabling inter-process communication during builds and development.
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()
}
);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);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`);
}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();
}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;
}
}
}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);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)}`);
}
}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`);
}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
}Process coordination is critical for Gatsby's multi-process architecture:
These utilities ensure that Gatsby's complex build system operates reliably even with multiple concurrent processes and shared resources.