@goldenpath-cloud/frontend-standards
Version:
Golden Path frontend development standards for Svelte 5 with TypeScript, testing, and validation
656 lines (542 loc) • 20 kB
JavaScript
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
/**
* Golden Path Frontend Standards Installation Script
* NPX entry point with cross-platform compatibility
*/
class FrontendStandardsInstaller {
constructor() {
this.projectRoot = process.cwd()
this.packageRoot = path.dirname(path.dirname(__filename))
this.backupDir = path.join(this.projectRoot, '.goldenpath-backup')
this.options = {
dryRun: false,
verbose: false,
rollback: false,
help: false,
version: false
}
}
async install() {
try {
this.parseArguments()
if (this.options.help) {
this.showHelp()
return
}
if (this.options.version) {
this.showVersion()
return
}
if (this.options.rollback) {
await this.rollback()
return
}
console.log('🚀 Installing Golden Path Frontend Standards...')
console.log('')
if (this.options.dryRun) {
console.log('🔍 DRY RUN MODE - No files will be modified')
console.log('')
}
// Installation steps
await this.validateEnvironment()
await this.detectProjectType()
await this.createBackup()
await this.installCoreStandards()
await this.installConfigurations()
await this.installDependencies()
await this.validateInstallation()
await this.cleanupBackup()
console.log('')
console.log('✅ Golden Path Frontend Standards installed successfully!')
console.log('')
this.showNextSteps()
} catch (error) {
console.error('❌ Installation failed:', error.message)
if (this.options.verbose) {
console.error('Stack trace:', error.stack)
}
console.log('')
console.log('🔄 To rollback changes, run:')
console.log(' npx @goldenpath/frontend-standards --rollback')
process.exit(1)
}
}
parseArguments() {
const args = process.argv.slice(2)
for (const arg of args) {
switch (arg) {
case '--dry-run':
this.options.dryRun = true
break
case '--verbose':
this.options.verbose = true
break
case '--rollback':
this.options.rollback = true
break
case '--help':
case '-h':
this.options.help = true
break
case '--version':
case '-v':
this.options.version = true
break
default:
if (arg.startsWith('--')) {
throw new Error(`Unknown option: ${arg}`)
}
}
}
}
showHelp() {
console.log(`
Golden Path Frontend Standards Installer
USAGE:
npx /frontend-standards [OPTIONS]
OPTIONS:
--dry-run Preview installation without making changes
--verbose Show detailed installation progress
--rollback Restore project to pre-installation state
--help, -h Show this help message
--version, -v Show version information
EXAMPLES:
npx /frontend-standards
npx /frontend-standards --dry-run
npx /frontend-standards --verbose
npx /frontend-standards --rollback
CORE STANDARDS INSTALLED:
• TypeScript - ESLint rules and configuration
• Svelte 5 - Component rules and IDE settings
• Testing - Vitest, Playwright, Testing Library
• Zod - Validation patterns and form handling
For more information, visit: https://github.com/goldenpath/frontend-standards
`)
}
showVersion() {
const packageJson = JSON.parse(fs.readFileSync(path.join(this.packageRoot, 'package.json'), 'utf8'))
console.log(`Golden Path Frontend Standards v${packageJson.version}`)
}
async validateEnvironment() {
this.log('🔍 Validating environment...')
// 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+ required, found ${nodeVersion}`)
}
// Check if we're in a project directory
if (!fs.existsSync(path.join(this.projectRoot, 'package.json'))) {
throw new Error('No package.json found. Please run this command in a project directory.')
}
this.log('✅ Environment validation passed')
}
async detectProjectType() {
this.log('🔍 Detecting project type...')
const packageJson = JSON.parse(fs.readFileSync(path.join(this.projectRoot, 'package.json'), 'utf8'))
// Check for SvelteKit
if (packageJson.devDependencies?.['@sveltejs/kit'] || packageJson.dependencies?.['@sveltejs/kit']) {
this.projectType = 'sveltekit'
this.log('📦 Detected SvelteKit project')
return
}
// Check for Svelte + Vite
if ((packageJson.devDependencies?.svelte || packageJson.dependencies?.svelte) &&
(packageJson.devDependencies?.vite || packageJson.dependencies?.vite)) {
this.projectType = 'svelte-vite'
this.log('📦 Detected Svelte + Vite project')
return
}
// Check for general Svelte
if (packageJson.devDependencies?.svelte || packageJson.dependencies?.svelte) {
this.projectType = 'svelte'
this.log('📦 Detected Svelte project')
return
}
// Default to generic frontend
this.projectType = 'frontend'
this.log('📦 Detected frontend project (generic)')
}
async createBackup() {
this.log('💾 Creating backup...')
if (this.options.dryRun) {
this.log('📋 Would create backup of existing configuration files')
return
}
// Create backup directory
if (fs.existsSync(this.backupDir)) {
fs.rmSync(this.backupDir, { recursive: true, force: true })
}
fs.mkdirSync(this.backupDir, { recursive: true })
// Backup existing configuration files
const configFiles = [
'.eslintrc.js',
'.eslintrc.cjs',
'.eslintrc.json',
'.eslintrc.yaml',
'.eslintrc.yml',
'eslint.config.js',
'.amazonq.json',
'tsconfig.json',
'vitest.config.ts',
'vitest.config.js',
'playwright.config.ts',
'playwright.config.js',
'.vscode/settings.json'
]
let backedUpFiles = 0
for (const configFile of configFiles) {
const filePath = path.join(this.projectRoot, configFile)
if (fs.existsSync(filePath)) {
const backupPath = path.join(this.backupDir, configFile)
const backupDir = path.dirname(backupPath)
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir, { recursive: true })
}
fs.copyFileSync(filePath, backupPath)
backedUpFiles++
this.log(`📋 Backed up: ${configFile}`)
}
}
// Create backup manifest
const backupManifest = {
timestamp: new Date().toISOString(),
projectType: this.projectType,
backedUpFiles: backedUpFiles,
nodeVersion: process.version,
installerVersion: this.getInstallerVersion()
}
fs.writeFileSync(
path.join(this.backupDir, 'manifest.json'),
JSON.stringify(backupManifest, null, 2)
)
this.log(`✅ Backup created (${backedUpFiles} files)`)
}
async installCoreStandards() {
this.log('📦 Installing core standards...')
const coreStandardsSource = path.join(this.packageRoot, 'core-standards')
const coreStandardsTarget = path.join(this.projectRoot, '.goldenpath', 'core-standards')
if (this.options.dryRun) {
this.log('📋 Would copy core standards to .goldenpath/core-standards/')
return
}
// Create target directory
if (fs.existsSync(coreStandardsTarget)) {
fs.rmSync(coreStandardsTarget, { recursive: true, force: true })
}
fs.mkdirSync(coreStandardsTarget, { recursive: true })
// Copy core standards
this.copyDirectory(coreStandardsSource, coreStandardsTarget)
this.log('✅ Core standards installed')
}
async installConfigurations() {
this.log('⚙️ Installing configuration files...')
if (this.options.dryRun) {
this.log('📋 Would install configuration files that reference core standards')
return
}
// Install main ESLint configuration
await this.installEslintConfig()
// Install Amazon Q configuration
await this.installAmazonQConfig()
// Install other configurations
await this.installOtherConfigs()
this.log('✅ Configuration files installed')
}
async installEslintConfig() {
// Check if project uses ESM
const packageJsonPath = path.join(this.projectRoot, 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
const isESM = packageJson.type === 'module'
// Use .cjs extension for ESM projects, .js for CommonJS
const configFileName = isESM ? '.eslintrc.cjs' : '.eslintrc.js'
const eslintConfigPath = path.join(this.projectRoot, configFileName)
const eslintConfig = `// Golden Path Frontend Standards - Generated Configuration
// This file references core standards - do not edit directly
// To customize rules, modify the core standards in .goldenpath/core-standards/
module.exports = {
root: true,
extends: [
'./.goldenpath/core-standards/typescript/.eslintrc.cjs',
'./.goldenpath/core-standards/svelte5/.eslintrc.cjs',
'./.goldenpath/core-standards/zod/.eslintrc.cjs',
'./.goldenpath/core-standards/testing/.eslintrc.cjs'
],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
}
}`
fs.writeFileSync(eslintConfigPath, eslintConfig)
this.log(`📝 Installed ${configFileName}`)
}
async installAmazonQConfig() {
const amazonqConfigPath = path.join(this.projectRoot, '.amazonq.json')
const sourceConfigPath = path.join(this.packageRoot, 'configs', '.amazonq.json')
if (fs.existsSync(sourceConfigPath)) {
fs.copyFileSync(sourceConfigPath, amazonqConfigPath)
this.log('📝 Installed .amazonq.json')
}
}
async installOtherConfigs() {
const configMappings = [
{ source: 'configs/tsconfig.json', target: 'tsconfig.json' },
{ source: 'configs/vitest.config.ts', target: 'vitest.config.ts' },
{ source: 'configs/playwright.config.ts', target: 'playwright.config.ts' },
{ source: 'configs/.vscode/settings.json', target: '.vscode/settings.json' }
]
for (const mapping of configMappings) {
const sourcePath = path.join(this.packageRoot, mapping.source)
const targetPath = path.join(this.projectRoot, mapping.target)
if (fs.existsSync(sourcePath)) {
// Create target directory if needed
const targetDir = path.dirname(targetPath)
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true })
}
fs.copyFileSync(sourcePath, targetPath)
this.log(`📝 Installed ${mapping.target}`)
}
}
}
async installDependencies() {
this.log('📦 Installing dependencies...')
if (this.options.dryRun) {
this.log('📋 Would install required npm dependencies')
return
}
// Read package.json to check existing dependencies
const packageJsonPath = path.join(this.projectRoot, 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
// Required dependencies for Golden Path Standards
const requiredDeps = {
devDependencies: {
'eslint': '^8.57.0',
'@typescript-eslint/eslint-plugin': '^7.0.0',
'@typescript-eslint/parser': '^7.0.0',
'eslint-plugin-svelte': '^2.46.0',
'eslint-plugin-playwright': '^2.2.0',
'eslint-plugin-testing-library': '^7.5.3',
'svelte-eslint-parser': '^0.41.0',
'vitest': '^3.2.4',
'@playwright/test': '^1.53.2',
'@testing-library/svelte': '^5.2.8',
'@testing-library/user-event': '^14.6.1',
'@testing-library/jest-dom': '^6.6.3'
}
}
// Add missing dependencies
let depsToInstall = []
for (const [dep, version] of Object.entries(requiredDeps.devDependencies)) {
if (!packageJson.devDependencies?.[dep] && !packageJson.dependencies?.[dep]) {
depsToInstall.push(`${dep}@${version}`)
}
}
if (depsToInstall.length > 0) {
this.log(`📦 Installing ${depsToInstall.length} missing dependencies...`)
try {
// Detect package manager
const packageManager = this.detectPackageManager()
if (packageManager === 'npm') {
execSync(`npm install --save-dev ${depsToInstall.join(' ')} --legacy-peer-deps`, {
cwd: this.projectRoot,
stdio: this.options.verbose ? 'inherit' : 'pipe'
})
} else if (packageManager === 'yarn') {
execSync(`yarn add --dev ${depsToInstall.join(' ')}`, {
cwd: this.projectRoot,
stdio: this.options.verbose ? 'inherit' : 'pipe'
})
} else if (packageManager === 'pnpm') {
execSync(`pnpm add --save-dev ${depsToInstall.join(' ')}`, {
cwd: this.projectRoot,
stdio: this.options.verbose ? 'inherit' : 'pipe'
})
}
this.log('✅ Dependencies installed')
} catch (error) {
throw new Error(`Failed to install dependencies: ${error.message}`)
}
} else {
this.log('✅ All required dependencies already present')
}
}
async validateInstallation() {
this.log('🔍 Validating installation...')
if (this.options.dryRun) {
this.log('📋 Would validate installation (skipped in dry-run mode)')
return
}
// Check that core standards are installed
const coreStandardsPath = path.join(this.projectRoot, '.goldenpath', 'core-standards')
if (!fs.existsSync(coreStandardsPath)) {
throw new Error('Core standards not found after installation')
}
// Check that configuration files exist
const packageJsonPath = path.join(this.projectRoot, 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
const isESM = packageJson.type === 'module'
const eslintConfigFile = isESM ? '.eslintrc.cjs' : '.eslintrc.js'
const requiredConfigs = [eslintConfigFile, '.amazonq.json']
for (const config of requiredConfigs) {
const configPath = path.join(this.projectRoot, config)
if (!fs.existsSync(configPath)) {
throw new Error(`Configuration file not found: ${config}`)
}
}
// Test ESLint configuration (skip for now to avoid dependency issues)
// try {
// execSync(`npx eslint --print-config ${eslintConfigFile}`, {
// cwd: this.projectRoot,
// stdio: 'pipe'
// })
// this.log('✅ ESLint configuration valid')
// } catch (error) {
// throw new Error(`ESLint configuration invalid: ${error.message}`)
// }
this.log('✅ ESLint configuration installed (validation skipped)')
this.log('✅ Installation validation passed')
}
async cleanupBackup() {
if (this.options.dryRun) {
return
}
// Keep backup for rollback capability
this.log('💾 Backup preserved for rollback capability')
}
async rollback() {
console.log('🔄 Rolling back Golden Path Frontend Standards...')
if (!fs.existsSync(this.backupDir)) {
throw new Error('No backup found. Cannot rollback.')
}
// Read backup manifest
const manifestPath = path.join(this.backupDir, 'manifest.json')
if (!fs.existsSync(manifestPath)) {
throw new Error('Backup manifest not found. Cannot rollback safely.')
}
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
console.log(`📋 Restoring backup from ${manifest.timestamp}`)
// Remove installed files
const installedFiles = [
'.eslintrc.js',
'.eslintrc.cjs',
'.amazonq.json',
'tsconfig.json',
'vitest.config.ts',
'playwright.config.ts',
'.vscode/settings.json',
'.goldenpath'
]
for (const file of installedFiles) {
const filePath = path.join(this.projectRoot, file)
if (fs.existsSync(filePath)) {
if (fs.statSync(filePath).isDirectory()) {
fs.rmSync(filePath, { recursive: true, force: true })
} else {
fs.unlinkSync(filePath)
}
console.log(`🗑️ Removed: ${file}`)
}
}
// Restore backed up files
const backupFiles = fs.readdirSync(this.backupDir, { withFileTypes: true })
.filter(dirent => !dirent.name.endsWith('.json'))
for (const file of backupFiles) {
const backupPath = path.join(this.backupDir, file.name)
const restorePath = path.join(this.projectRoot, file.name)
if (file.isDirectory()) {
this.copyDirectory(backupPath, restorePath)
} else {
// Create directory if needed
const restoreDir = path.dirname(restorePath)
if (!fs.existsSync(restoreDir)) {
fs.mkdirSync(restoreDir, { recursive: true })
}
fs.copyFileSync(backupPath, restorePath)
}
console.log(`📋 Restored: ${file.name}`)
}
// Remove backup
fs.rmSync(this.backupDir, { recursive: true, force: true })
console.log('')
console.log('✅ Rollback completed successfully!')
}
// Utility methods
copyDirectory(src, dest) {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true })
}
const entries = fs.readdirSync(src, { withFileTypes: true })
for (const entry of entries) {
const srcPath = path.join(src, entry.name)
const destPath = path.join(dest, entry.name)
if (entry.isDirectory()) {
this.copyDirectory(srcPath, destPath)
} else {
fs.copyFileSync(srcPath, destPath)
}
}
}
detectPackageManager() {
if (fs.existsSync(path.join(this.projectRoot, 'yarn.lock'))) {
return 'yarn'
}
if (fs.existsSync(path.join(this.projectRoot, 'pnpm-lock.yaml'))) {
return 'pnpm'
}
return 'npm'
}
getInstallerVersion() {
try {
const packageJson = JSON.parse(fs.readFileSync(path.join(this.packageRoot, 'package.json'), 'utf8'))
return packageJson.version
} catch {
return 'unknown'
}
}
log(message) {
if (this.options.verbose || !message.startsWith('📋')) {
console.log(message)
}
}
showNextSteps() {
console.log('🎉 Next Steps:')
console.log('')
console.log('1. Run linting to see the standards in action:')
console.log(' npm run lint')
console.log('')
console.log('2. Run tests to validate your setup:')
console.log(' npm run test')
console.log('')
console.log('3. Check your IDE integration:')
console.log(' - VS Code should show real-time linting')
console.log(' - Amazon Q should generate compliant code')
console.log('')
console.log('4. Customize core standards if needed:')
console.log(' - Edit files in .goldenpath/core-standards/')
console.log(' - Changes will be reflected automatically')
console.log('')
console.log('📚 Documentation: https://github.com/goldenpath/frontend-standards')
}
}
// CLI interface
async function main() {
const installer = new FrontendStandardsInstaller()
await installer.install()
}
if (require.main === module) {
main().catch(error => {
console.error('Fatal error:', error.message)
process.exit(1)
})
}
module.exports = { FrontendStandardsInstaller }