@fission-ai/openspec
Version:
AI-native system for spec-driven development
111 lines • 4.23 kB
JavaScript
/**
* Shared Types and Utilities for Artifact Workflow Commands
*
* This module contains types, constants, and validation helpers used across
* multiple artifact workflow commands.
*/
import chalk from 'chalk';
import path from 'path';
import * as fs from 'fs';
import { getSchemaDir, listSchemas } from '../../core/artifact-graph/index.js';
import { validateChangeName } from '../../utils/change-utils.js';
// -----------------------------------------------------------------------------
// Constants
// -----------------------------------------------------------------------------
export const DEFAULT_SCHEMA = 'spec-driven';
// -----------------------------------------------------------------------------
// Utility Functions
// -----------------------------------------------------------------------------
/**
* Checks if color output is disabled via NO_COLOR env or --no-color flag.
*/
export function isColorDisabled() {
return process.env.NO_COLOR === '1' || process.env.NO_COLOR === 'true';
}
/**
* Gets the color function based on status.
*/
export function getStatusColor(status) {
if (isColorDisabled()) {
return (text) => text;
}
switch (status) {
case 'done':
return chalk.green;
case 'ready':
return chalk.yellow;
case 'blocked':
return chalk.red;
}
}
/**
* Gets the status indicator for an artifact.
*/
export function getStatusIndicator(status) {
const color = getStatusColor(status);
switch (status) {
case 'done':
return color('[x]');
case 'ready':
return color('[ ]');
case 'blocked':
return color('[-]');
}
}
/**
* Validates that a change exists and returns available changes if not.
* Checks directory existence directly to support scaffolded changes (without proposal.md).
*/
export async function validateChangeExists(changeName, projectRoot) {
const changesPath = path.join(projectRoot, 'openspec', 'changes');
// Get all change directories (not just those with proposal.md)
const getAvailableChanges = async () => {
try {
const entries = await fs.promises.readdir(changesPath, { withFileTypes: true });
return entries
.filter((e) => e.isDirectory() && e.name !== 'archive' && !e.name.startsWith('.'))
.map((e) => e.name);
}
catch {
return [];
}
};
if (!changeName) {
const available = await getAvailableChanges();
if (available.length === 0) {
throw new Error('No changes found. Create one with: openspec new change <name>');
}
throw new Error(`Missing required option --change. Available changes:\n ${available.join('\n ')}`);
}
// Validate change name format to prevent path traversal
const nameValidation = validateChangeName(changeName);
if (!nameValidation.valid) {
throw new Error(`Invalid change name '${changeName}': ${nameValidation.error}`);
}
// Check directory existence directly
const changePath = path.join(changesPath, changeName);
const exists = fs.existsSync(changePath) && fs.statSync(changePath).isDirectory();
if (!exists) {
const available = await getAvailableChanges();
if (available.length === 0) {
throw new Error(`Change '${changeName}' not found. No changes exist. Create one with: openspec new change <name>`);
}
throw new Error(`Change '${changeName}' not found. Available changes:\n ${available.join('\n ')}`);
}
return changeName;
}
/**
* Validates that a schema exists and returns available schemas if not.
*
* @param schemaName - The schema name to validate
* @param projectRoot - Optional project root for project-local schema resolution
*/
export function validateSchemaExists(schemaName, projectRoot) {
const schemaDir = getSchemaDir(schemaName, projectRoot);
if (!schemaDir) {
const availableSchemas = listSchemas(projectRoot);
throw new Error(`Schema '${schemaName}' not found. Available schemas:\n ${availableSchemas.join('\n ')}`);
}
return schemaName;
}
//# sourceMappingURL=shared.js.map