@stillrivercode/agentic-workflow-template
Version:
NPM package to create AI-powered GitHub workflow automation projects
227 lines (188 loc) • 6 kB
JavaScript
const validateNpmName = require('validate-npm-package-name');
const fs = require('fs-extra');
const path = require('path');
// Sanitize path to prevent directory traversal
function sanitizePath(userPath) {
// Remove any path traversal attempts
const normalized = path.normalize(userPath).replace(/^(\.\.(\/|\\|$))+/, '');
// Ensure the path doesn't contain any remaining traversal patterns
if (normalized.includes('..')) {
throw new Error('Invalid path: Path traversal detected');
}
return normalized;
}
/**
* Validate project name
*/
function validateProjectName(name) {
if (!name || typeof name !== 'string') {
throw new Error('Project name is required');
}
const trimmed = name.trim();
if (trimmed.length === 0) {
throw new Error('Project name cannot be empty');
}
if (trimmed.length > 214) {
throw new Error('Project name must be less than 214 characters');
}
if (trimmed !== trimmed.toLowerCase()) {
throw new Error('Project name must be lowercase');
}
if (!/^[a-z0-9-_]+$/.test(trimmed)) {
throw new Error(
'Project name can only contain lowercase letters, numbers, hyphens, and underscores'
);
}
if (trimmed.startsWith('-') || trimmed.endsWith('-')) {
throw new Error('Project name cannot start or end with a hyphen');
}
// Check for reserved names
const reservedNames = ['node', 'npm', 'core', 'console', 'global', 'process'];
if (reservedNames.includes(trimmed)) {
throw new Error(`Project name "${trimmed}" is reserved and cannot be used`);
}
// Check against npm name validation
const npmValidation = validateNpmName(trimmed);
if (!npmValidation.validForNewPackages) {
const errors = npmValidation.errors || [];
const warnings = npmValidation.warnings || [];
const issues = [...errors, ...warnings];
throw new Error(`Invalid project name: ${issues.join(', ')}`);
}
return true;
}
/**
* Validate OpenRouter API key
*/
function validateApiKey(key) {
if (!key || typeof key !== 'string') {
return 'API key is required';
}
const trimmed = key.trim();
if (trimmed.length === 0) {
return 'API key cannot be empty';
}
// OpenRouter API keys typically start with 'sk-or-'
if (!trimmed.startsWith('sk-or-')) {
return 'OpenRouter API key should start with "sk-or-"';
}
if (trimmed.length < 20) {
return 'API key appears to be too short';
}
if (trimmed.length > 200) {
return 'API key appears to be too long';
}
// Check for valid characters (base64-like)
if (!/^[a-zA-Z0-9-_]+$/.test(trimmed)) {
return 'API key contains invalid characters';
}
return true;
}
/**
* Validate system requirements
*/
async function validateSystem() {
console.log('🔍 Checking system requirements...');
// Skip system checks in CI environment
if (process.env.CI || process.env.NODE_ENV === 'test') {
console.log('✅ Skipping system requirements check in CI/test environment');
return;
}
// Check Node.js version
const nodeVersion = process.version;
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
if (majorVersion < 18) {
throw new Error(
`Node.js 18.0.0 or higher is required. Current version: ${nodeVersion}`
);
}
// Check npm
try {
const { execSync } = require('child_process');
const npmVersion = execSync('npm --version', { encoding: 'utf8' }).trim();
const npmMajor = parseInt(npmVersion.split('.')[0]);
if (npmMajor < 8) {
throw new Error(
`npm 8.0.0 or higher is required. Current version: ${npmVersion}`
);
}
} catch (_error) {
throw new Error('npm is not installed or not accessible');
}
// Check git (optional but recommended)
try {
const { execSync } = require('child_process');
execSync('git --version', { encoding: 'utf8' });
} catch (_error) {
console.warn(
'⚠️ Git is not installed. Some features may not work properly.'
);
}
// Check network connectivity (basic check)
try {
const https = require('https');
await new Promise((resolve, reject) => {
const req = https.request(
'https://api.github.com',
{ method: 'HEAD', timeout: 5000 },
(_res) => {
resolve();
}
);
req.on('error', reject);
req.on('timeout', () => reject(new Error('Network timeout')));
req.end();
});
} catch (_error) {
console.warn(
'⚠️ Network connectivity check failed. GitHub API may not be accessible.'
);
}
console.log('✅ System requirements validated');
}
/**
* Validate GitHub organization/username
*/
function validateGitHubOrg(org) {
if (!org || typeof org !== 'string') {
return 'GitHub organization/username is required';
}
const trimmed = org.trim();
if (trimmed.length === 0) {
return 'GitHub organization/username cannot be empty';
}
if (trimmed.length > 39) {
return 'GitHub organization/username must be 39 characters or fewer';
}
if (!/^[a-zA-Z0-9-]+$/.test(trimmed)) {
return 'GitHub organization/username can only contain alphanumeric characters and hyphens';
}
if (trimmed.startsWith('-') || trimmed.endsWith('-')) {
return 'GitHub organization/username cannot start or end with a hyphen';
}
if (trimmed.includes('--')) {
return 'GitHub organization/username cannot contain consecutive hyphens';
}
return true;
}
/**
* Validate directory write permissions
*/
async function validateWritePermissions(dirPath) {
try {
const testFile = path.join(sanitizePath(dirPath), '.write-test');
// eslint-disable-next-line security/detect-non-literal-fs-filename
await fs.writeFile(testFile, 'test');
await fs.remove(testFile);
return true;
} catch (_error) {
throw new Error(`No write permission for directory: ${dirPath}`);
}
}
module.exports = {
validateProjectName,
validateApiKey,
validateSystem,
validateGitHubOrg,
validateWritePermissions,
};