diff --git a/package-lock.json b/package-lock.json index 2cdb24c2..93b5830a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -217,6 +217,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -240,6 +241,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1456,6 +1458,7 @@ "integrity": "sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -1495,6 +1498,7 @@ "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", @@ -1950,6 +1954,7 @@ "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2114,6 +2119,7 @@ "integrity": "sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/browser": "4.0.16", "@vitest/mocker": "4.0.16", @@ -2249,6 +2255,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2516,6 +2523,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -3166,6 +3174,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4582,6 +4591,7 @@ "integrity": "sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@asamuzakjp/dom-selector": "^2.0.1", "cssstyle": "^4.0.1", @@ -5587,6 +5597,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5717,6 +5728,7 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -6460,6 +6472,7 @@ "integrity": "sha512-ZhLtvroYxUxr+HQJfMZEDRsGsmU46x12RvAv/zi9584f5KOX7bUrEbhPJ7cKFmUvZTJXi/CFZUYwDC6M1FigPw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -6620,6 +6633,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6744,6 +6758,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6833,6 +6848,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -7359,6 +7375,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -7392,6 +7409,7 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -7829,24 +7847,6 @@ "dev": true, "license": "MIT" }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index c42211fa..f16d669d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "bin": { "statue-setup": "./postinstall.js", - "statue": "./scripts/statue-cli.js" + "statue": "./scripts/statue-cli.sh" }, "files": [ "src", @@ -43,7 +43,7 @@ "scripts": { "dev": "vite dev", "build": "vite build", - "postbuild": "node scripts/generate-seo-files.js && node scripts/run-pagefind.js", + "postbuild": "./scripts/generate-seo-files.sh && ./scripts/run-pagefind.sh", "preview": "npx -y serve build", "preview:vite": "vite preview", "prepublishOnly": "npm run build", @@ -58,9 +58,9 @@ "test:local": "./scripts/test-local.sh", "docker:build": "cd test/hermetic && docker build -t statue-ssg .", "docker:test": "npm run docker:build && ./test/hermetic/docker_test.sh", - "template:load": "node scripts/manage-templates.js load", - "template:save": "node scripts/manage-templates.js save", - "template:list": "node scripts/manage-templates.js list", + "template:load": "./scripts/manage-templates.sh load", + "template:save": "./scripts/manage-templates.sh save", + "template:list": "./scripts/manage-templates.sh list", "test:unit": "vitest" }, "devDependencies": { diff --git a/scripts/generate-seo-files.js b/scripts/generate-seo-files.js deleted file mode 100644 index bd1efb0e..00000000 --- a/scripts/generate-seo-files.js +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env node - -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { execSync } from 'child_process'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -async function generateSEOFiles() { - try { - // Import site config - const { siteConfig } = await import('../site.config.js'); - - // Get the site URL from config - const siteUrl = siteConfig.site.url; - - console.log('🚀 Generating SEO files...'); - console.log(`🔗 Using site URL: ${siteUrl}`); - - // Generate sitemap using svelte-sitemap - console.log('📄 Generating sitemap...'); - execSync(`npx svelte-sitemap --domain ${siteUrl}`, { stdio: 'inherit' }); - - // Create robots.txt content - const robotsContent = `User-agent: * -Allow: / - -Sitemap: ${siteUrl}/sitemap.xml -`; - - // Write robots.txt to build directory - const buildDir = path.join(__dirname, '..', 'build'); - const robotsPath = path.join(buildDir, 'robots.txt'); - - // Ensure build directory exists - if (!fs.existsSync(buildDir)) { - console.error('❌ Build directory does not exist. Please run "npm run build" first.'); - process.exit(1); - } - - // Write the robots.txt file - fs.writeFileSync(robotsPath, robotsContent); - - console.log('✅ All SEO files generated successfully!'); - console.log(`📍 Sitemap: ${buildDir}/sitemap.xml`); - console.log(`📍 Robots: ${robotsPath}`); - console.log(`🌐 Sitemap URL: ${siteUrl}/sitemap.xml`); - - } catch (error) { - console.error('❌ Error generating SEO files:', error.message); - process.exit(1); - } -} - -generateSEOFiles(); \ No newline at end of file diff --git a/scripts/generate-seo-files.sh b/scripts/generate-seo-files.sh new file mode 100755 index 00000000..65377d03 --- /dev/null +++ b/scripts/generate-seo-files.sh @@ -0,0 +1,83 @@ +#!/bin/bash +## generate-seo-files.sh - Generate sitemap.xml and robots.txt +# +# Usage: +# ./generate-seo-files.sh +# + +set -e # Exit on error + +# Script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_success() { + echo -e "${GREEN}$1${NC}" +} + +# Main function +generate_seo_files() { + log_info "🚀 Generating SEO files..." + + # Get site URL from site.config.js using node + local site_url + site_url=$(node -e " + import('$PROJECT_ROOT/site.config.js') + .then(m => console.log(m.siteConfig.site.url)) + .catch(e => { + console.error('Error loading site.config.js:', e.message); + process.exit(1); + }); + " 2>&1) + + if [ $? -ne 0 ]; then + log_error "Failed to load site.config.js" + log_error "$site_url" + exit 1 + fi + + log_info "🔗 Using site URL: $site_url" + + # Check if build directory exists + local build_dir="$PROJECT_ROOT/build" + if [ ! -d "$build_dir" ]; then + log_error "❌ Build directory does not exist. Please run \"npm run build\" first." + exit 1 + fi + + # Generate sitemap using svelte-sitemap + log_info "📄 Generating sitemap..." + cd "$PROJECT_ROOT" + npx svelte-sitemap --domain "$site_url" + + # Create robots.txt + log_info "📝 Generating robots.txt..." + cat > "$build_dir/robots.txt" << EOF +User-agent: * +Allow: / + +Sitemap: $site_url/sitemap.xml +EOF + + log_success "✅ All SEO files generated successfully!" + echo "📍 Sitemap: $build_dir/sitemap.xml" + echo "📍 Robots: $build_dir/robots.txt" + echo "🌐 Sitemap URL: $site_url/sitemap.xml" +} + +generate_seo_files diff --git a/scripts/manage-templates.js b/scripts/manage-templates.js deleted file mode 100644 index 78b713b5..00000000 --- a/scripts/manage-templates.js +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/env node - -import fs from 'fs-extra'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import chalk from 'chalk'; -import { Command } from 'commander'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const rootDir = path.resolve(__dirname, '..'); -const templatesDir = path.join(rootDir, 'templates'); - -const program = new Command(); - -program - .name('template-manager') - .description('Manage Statue SSG templates for development') - .version('1.0.0'); - -// LOAD: Template -> Workspace -program - .command('load ') - .description('Load a template into the workspace (src/routes, content) for development') - .option('-f, --force', 'Force overwrite of current workspace', false) - .action(async (templateName, options) => { - // Special handling for 'default' - if (templateName === 'default') { - console.log(chalk.yellow('⚠️ The "default" template lives in the project root (src/routes).')); - console.log(chalk.yellow(' To restore the default template, please use git:')); - console.log(chalk.white(' git checkout src/routes content site.config.js')); - return; - } - - const sourceTemplateDir = path.join(templatesDir, templateName); - - if (!fs.existsSync(sourceTemplateDir)) { - console.error(chalk.red(`❌ Template '${templateName}' not found in ${templatesDir}`)); - return; - } - - console.log(chalk.blue(`📂 Loading template '${templateName}' into workspace...`)); - if (!options.force) { - console.log(chalk.yellow('⚠️ Warning: This will overwrite:')); - console.log(chalk.yellow(' - src/routes/')); - console.log(chalk.yellow(' - content/')); - console.log(chalk.yellow(' - src/lib/components/ (if template has custom components)')); - console.log(chalk.yellow(' - src/lib/themes/ (if template has custom themes)')); - console.log(chalk.yellow(' - src/lib/index.ts and src/lib/index.css (if template has them)')); - console.log(chalk.yellow(' Ensure you have committed your changes to "default" (or other templates).')); - console.error(chalk.red('Operation aborted. Use -f or --force to proceed.')); - return; - } - - // Targets in workspace - const targetRoutes = path.join(rootDir, 'src/routes'); - const targetContent = path.join(rootDir, 'content'); - const targetConfig = path.join(rootDir, 'site.config.js'); - const targetLibIndexTs = path.join(rootDir, 'src/lib/index.ts'); - const targetLibIndexCss = path.join(rootDir, 'src/lib/index.css'); - const targetLibComponents = path.join(rootDir, 'src/lib/components'); - const targetLibThemes = path.join(rootDir, 'src/lib/themes'); - - // 1. Clear current workspace - console.log(chalk.gray('Cleaning current workspace...')); - fs.emptyDirSync(targetRoutes); - fs.emptyDirSync(targetContent); - - // 2. Copy from Template -> Workspace - try { - // Routes - if (fs.existsSync(path.join(sourceTemplateDir, 'src/routes'))) { - fs.copySync(path.join(sourceTemplateDir, 'src/routes'), targetRoutes); - console.log(chalk.gray(' ✓ Copied src/routes')); - } - // Content - if (fs.existsSync(path.join(sourceTemplateDir, 'content'))) { - fs.copySync(path.join(sourceTemplateDir, 'content'), targetContent); - console.log(chalk.gray(' ✓ Copied content')); - } - // Config - if (fs.existsSync(path.join(sourceTemplateDir, 'site.config.js'))) { - fs.copySync(path.join(sourceTemplateDir, 'site.config.js'), targetConfig); - console.log(chalk.gray(' ✓ Copied site.config.js')); - } - - // src/lib/index.ts - if (fs.existsSync(path.join(sourceTemplateDir, 'src/lib/index.ts'))) { - fs.copySync(path.join(sourceTemplateDir, 'src/lib/index.ts'), targetLibIndexTs); - console.log(chalk.gray(' ✓ Copied src/lib/index.ts')); - } - - // src/lib/index.css - if (fs.existsSync(path.join(sourceTemplateDir, 'src/lib/index.css'))) { - fs.copySync(path.join(sourceTemplateDir, 'src/lib/index.css'), targetLibIndexCss); - console.log(chalk.gray(' ✓ Copied src/lib/index.css')); - } - - // src/lib/components - if (fs.existsSync(path.join(sourceTemplateDir, 'src/lib/components'))) { - // Clear existing custom components - if (fs.existsSync(targetLibComponents)) { - fs.emptyDirSync(targetLibComponents); - } - fs.copySync(path.join(sourceTemplateDir, 'src/lib/components'), targetLibComponents); - console.log(chalk.gray(' ✓ Copied src/lib/components')); - } - - // src/lib/themes - if (fs.existsSync(path.join(sourceTemplateDir, 'src/lib/themes'))) { - // Clear existing custom themes - if (fs.existsSync(targetLibThemes)) { - fs.emptyDirSync(targetLibThemes); - } - fs.copySync(path.join(sourceTemplateDir, 'src/lib/themes'), targetLibThemes); - console.log(chalk.gray(' ✓ Copied src/lib/themes')); - } - - console.log(chalk.green(`✅ Template '${templateName}' loaded successfully!`)); - console.log(chalk.yellow('Run "npm run dev" to test it.')); - } catch (e) { - console.error(chalk.red('Error loading template:'), e); - } - }); - -// SAVE: Workspace -> Template -program - .command('save ') - .description('Save current workspace (src/routes, content) into a template folder') - .action(async (templateName) => { - // Special handling for 'default' - if (templateName === 'default') { - console.log(chalk.green('✅ The "default" template is already in the project root.')); - console.log(chalk.gray(' Just git commit your changes to save them.')); - return; - } - - const targetTemplateDir = path.join(templatesDir, templateName); - - console.log(chalk.blue(`💾 Saving workspace to template '${templateName}'...`)); - - // Sources from workspace - const sourceRoutes = path.join(rootDir, 'src/routes'); - const sourceContent = path.join(rootDir, 'content'); - const sourceConfig = path.join(rootDir, 'site.config.js'); - const sourceLibIndexTs = path.join(rootDir, 'src/lib/index.ts'); - const sourceLibIndexCss = path.join(rootDir, 'src/lib/index.css'); - const sourceLibComponents = path.join(rootDir, 'src/lib/components'); - const sourceLibThemes = path.join(rootDir, 'src/lib/themes'); - - // 1. Ensure template dir exists - fs.ensureDirSync(path.join(targetTemplateDir, 'src')); - fs.ensureDirSync(path.join(targetTemplateDir, 'src/lib')); - - // 2. Copy Workspace -> Template - try { - // Routes - if (fs.existsSync(sourceRoutes)) { - fs.emptyDirSync(path.join(targetTemplateDir, 'src/routes')); - fs.copySync(sourceRoutes, path.join(targetTemplateDir, 'src/routes')); - console.log(chalk.gray(' ✓ Saved src/routes')); - } - // Content - if (fs.existsSync(sourceContent)) { - fs.emptyDirSync(path.join(targetTemplateDir, 'content')); - fs.copySync(sourceContent, path.join(targetTemplateDir, 'content')); - console.log(chalk.gray(' ✓ Saved content')); - } - // Config - if (fs.existsSync(sourceConfig)) { - fs.copySync(sourceConfig, path.join(targetTemplateDir, 'site.config.js')); - console.log(chalk.gray(' ✓ Saved site.config.js')); - } - - // src/lib/index.ts - if (fs.existsSync(sourceLibIndexTs)) { - fs.copySync(sourceLibIndexTs, path.join(targetTemplateDir, 'src/lib/index.ts')); - console.log(chalk.gray(' ✓ Saved src/lib/index.ts')); - } - - // src/lib/index.css - if (fs.existsSync(sourceLibIndexCss)) { - fs.copySync(sourceLibIndexCss, path.join(targetTemplateDir, 'src/lib/index.css')); - console.log(chalk.gray(' ✓ Saved src/lib/index.css')); - } - - // src/lib/components - if (fs.existsSync(sourceLibComponents)) { - const targetComponents = path.join(targetTemplateDir, 'src/lib/components'); - if (fs.existsSync(targetComponents)) { - fs.emptyDirSync(targetComponents); - } - fs.copySync(sourceLibComponents, targetComponents); - console.log(chalk.gray(' ✓ Saved src/lib/components')); - } - - // src/lib/themes - if (fs.existsSync(sourceLibThemes)) { - const targetThemes = path.join(targetTemplateDir, 'src/lib/themes'); - if (fs.existsSync(targetThemes)) { - fs.emptyDirSync(targetThemes); - } - fs.copySync(sourceLibThemes, targetThemes); - console.log(chalk.gray(' ✓ Saved src/lib/themes')); - } - - console.log(chalk.green(`✅ Workspace saved to template '${templateName}'!`)); - } catch (e) { - console.error(chalk.red('Error saving template:'), e); - } - }); - -// LIST -program - .command('list') - .description('List available templates') - .action(() => { - console.log(chalk.blue('Available Templates:')); - console.log(' - default (Project Root)'); - - if (fs.existsSync(templatesDir)) { - const templates = fs.readdirSync(templatesDir).filter(t => fs.statSync(path.join(templatesDir, t)).isDirectory()); - templates.forEach(t => console.log(` - ${t}`)); - } - }); - -program.parse(process.argv); diff --git a/scripts/manage-templates.sh b/scripts/manage-templates.sh new file mode 100755 index 00000000..767cebea --- /dev/null +++ b/scripts/manage-templates.sh @@ -0,0 +1,326 @@ +#!/bin/bash +## manage-templates.sh - Manage Statue SSG templates for development +# +# Usage: +# manage-templates.sh load [--force] # Load template into workspace +# manage-templates.sh save # Save workspace as template +# manage-templates.sh list # List available templates +# + +set -e # Exit on error + +# Script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +TEMPLATES_DIR="$PROJECT_ROOT/templates" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +GRAY='\033[0;90m' +NC='\033[0m' + +log_info() { + echo -e "${BLUE}$1${NC}" +} + +log_error() { + echo -e "${RED}$1${NC}" +} + +log_warn() { + echo -e "${YELLOW}$1${NC}" +} + +log_success() { + echo -e "${GREEN}$1${NC}" +} + +log_gray() { + echo -e "${GRAY}$1${NC}" +} + +# Show usage +show_usage() { + cat << EOF +Template Manager - Manage Statue SSG templates for development + +Usage: + manage-templates.sh load [--force] Load a template into workspace + manage-templates.sh save Save workspace as template + manage-templates.sh list List available templates + +Commands: + load Load a template into the workspace (src/routes, content) + save Save current workspace into a template folder + list List available templates + +Examples: + manage-templates.sh list + manage-templates.sh load blog --force + manage-templates.sh save my-template + +EOF +} + +# Load template into workspace +load_template() { + local template_name="$1" + local force=false + + # Parse options + shift + while [ $# -gt 0 ]; do + case "$1" in + --force|-f) + force=true + shift + ;; + *) + log_error "Unknown option: $1" + exit 1 + ;; + esac + done + + # Special handling for 'default' + if [ "$template_name" = "default" ]; then + log_warn "⚠️ The \"default\" template lives in the project root (src/routes)." + log_warn " To restore the default template, please use git:" + echo " git checkout src/routes content site.config.js" + return 0 + fi + + local source_template_dir="$TEMPLATES_DIR/$template_name" + + if [ ! -d "$source_template_dir" ]; then + log_error "❌ Template '$template_name' not found in $TEMPLATES_DIR" + return 1 + fi + + log_info "📂 Loading template '$template_name' into workspace..." + + if [ "$force" != "true" ]; then + log_warn "⚠️ Warning: This will overwrite:" + log_warn " - src/routes/" + log_warn " - content/" + log_warn " - src/lib/components/ (if template has custom components)" + log_warn " - src/lib/themes/ (if template has custom themes)" + log_warn " - src/lib/index.ts and src/lib/index.css (if template has them)" + log_warn " Ensure you have committed your changes to \"default\" (or other templates)." + log_error "Operation aborted. Use -f or --force to proceed." + return 1 + fi + + # Targets in workspace + local target_routes="$PROJECT_ROOT/src/routes" + local target_content="$PROJECT_ROOT/content" + local target_config="$PROJECT_ROOT/site.config.js" + local target_lib_index_ts="$PROJECT_ROOT/src/lib/index.ts" + local target_lib_index_css="$PROJECT_ROOT/src/lib/index.css" + local target_lib_components="$PROJECT_ROOT/src/lib/components" + local target_lib_themes="$PROJECT_ROOT/src/lib/themes" + + # 1. Clear current workspace + log_gray "Cleaning current workspace..." + rm -rf "$target_routes"/* + rm -rf "$target_content"/* + + # 2. Copy from Template -> Workspace + # Routes + if [ -d "$source_template_dir/src/routes" ]; then + cp -r "$source_template_dir/src/routes"/* "$target_routes/" + log_gray " ✓ Copied src/routes" + fi + + # Content + if [ -d "$source_template_dir/content" ]; then + cp -r "$source_template_dir/content"/* "$target_content/" + log_gray " ✓ Copied content" + fi + + # Config + if [ -f "$source_template_dir/site.config.js" ]; then + cp "$source_template_dir/site.config.js" "$target_config" + log_gray " ✓ Copied site.config.js" + fi + + # src/lib/index.ts + if [ -f "$source_template_dir/src/lib/index.ts" ]; then + cp "$source_template_dir/src/lib/index.ts" "$target_lib_index_ts" + log_gray " ✓ Copied src/lib/index.ts" + fi + + # src/lib/index.css + if [ -f "$source_template_dir/src/lib/index.css" ]; then + cp "$source_template_dir/src/lib/index.css" "$target_lib_index_css" + log_gray " ✓ Copied src/lib/index.css" + fi + + # src/lib/components + if [ -d "$source_template_dir/src/lib/components" ]; then + rm -rf "$target_lib_components" + mkdir -p "$target_lib_components" + cp -r "$source_template_dir/src/lib/components"/* "$target_lib_components/" + log_gray " ✓ Copied src/lib/components" + fi + + # src/lib/themes + if [ -d "$source_template_dir/src/lib/themes" ]; then + rm -rf "$target_lib_themes" + mkdir -p "$target_lib_themes" + cp -r "$source_template_dir/src/lib/themes"/* "$target_lib_themes/" + log_gray " ✓ Copied src/lib/themes" + fi + + log_success "✅ Template '$template_name' loaded successfully!" + log_warn "Run \"npm run dev\" to test it." +} + +# Save workspace to template +save_template() { + local template_name="$1" + + # Special handling for 'default' + if [ "$template_name" = "default" ]; then + log_success "✅ The \"default\" template is already in the project root." + log_gray " Just git commit your changes to save them." + return 0 + fi + + local target_template_dir="$TEMPLATES_DIR/$template_name" + + log_info "💾 Saving workspace to template '$template_name'..." + + # Sources from workspace + local source_routes="$PROJECT_ROOT/src/routes" + local source_content="$PROJECT_ROOT/content" + local source_config="$PROJECT_ROOT/site.config.js" + local source_lib_index_ts="$PROJECT_ROOT/src/lib/index.ts" + local source_lib_index_css="$PROJECT_ROOT/src/lib/index.css" + local source_lib_components="$PROJECT_ROOT/src/lib/components" + local source_lib_themes="$PROJECT_ROOT/src/lib/themes" + + # 1. Ensure template dir exists + mkdir -p "$target_template_dir/src/lib" + + # 2. Copy Workspace -> Template + # Routes + if [ -d "$source_routes" ]; then + rm -rf "$target_template_dir/src/routes" + mkdir -p "$target_template_dir/src/routes" + cp -r "$source_routes"/* "$target_template_dir/src/routes/" + log_gray " ✓ Saved src/routes" + fi + + # Content + if [ -d "$source_content" ]; then + rm -rf "$target_template_dir/content" + mkdir -p "$target_template_dir/content" + cp -r "$source_content"/* "$target_template_dir/content/" + log_gray " ✓ Saved content" + fi + + # Config + if [ -f "$source_config" ]; then + cp "$source_config" "$target_template_dir/site.config.js" + log_gray " ✓ Saved site.config.js" + fi + + # src/lib/index.ts + if [ -f "$source_lib_index_ts" ]; then + cp "$source_lib_index_ts" "$target_template_dir/src/lib/index.ts" + log_gray " ✓ Saved src/lib/index.ts" + fi + + # src/lib/index.css + if [ -f "$source_lib_index_css" ]; then + cp "$source_lib_index_css" "$target_template_dir/src/lib/index.css" + log_gray " ✓ Saved src/lib/index.css" + fi + + # src/lib/components + if [ -d "$source_lib_components" ]; then + rm -rf "$target_template_dir/src/lib/components" + mkdir -p "$target_template_dir/src/lib/components" + cp -r "$source_lib_components"/* "$target_template_dir/src/lib/components/" + log_gray " ✓ Saved src/lib/components" + fi + + # src/lib/themes + if [ -d "$source_lib_themes" ]; then + rm -rf "$target_template_dir/src/lib/themes" + mkdir -p "$target_template_dir/src/lib/themes" + cp -r "$source_lib_themes"/* "$target_template_dir/src/lib/themes/" + log_gray " ✓ Saved src/lib/themes" + fi + + log_success "✅ Workspace saved to template '$template_name'!" +} + +# List available templates +list_templates() { + log_info "Available Templates:" + echo " - default (Project Root)" + + if [ -d "$TEMPLATES_DIR" ]; then + for dir in "$TEMPLATES_DIR"/*; do + if [ -d "$dir" ]; then + local template_name=$(basename "$dir") + echo " - $template_name" + fi + done + fi +} + +# Main command router +main() { + if [ $# -eq 0 ]; then + show_usage + exit 0 + fi + + local command="$1" + shift + + case "$command" in + load) + if [ $# -eq 0 ]; then + log_error "Error: Template name required" + echo "" + show_usage + exit 1 + fi + load_template "$@" + ;; + + save) + if [ $# -eq 0 ]; then + log_error "Error: Template name required" + echo "" + show_usage + exit 1 + fi + save_template "$@" + ;; + + list) + list_templates + ;; + + help|--help|-h) + show_usage + ;; + + *) + log_error "Unknown command: $command" + echo "" + show_usage + exit 1 + ;; + esac +} + +main "$@" diff --git a/scripts/run-pagefind.js b/scripts/run-pagefind.js deleted file mode 100644 index 2eb51e19..00000000 --- a/scripts/run-pagefind.js +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env node -import { execSync } from 'child_process'; -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -async function runPagefind() { - try { - const configPath = join(__dirname, '../site.config.js'); - const { siteConfig } = await import(configPath); - - // Only index if search is enabled - const searchEnabled = siteConfig?.search?.enabled ?? false; - - if (searchEnabled) { - console.log('Search is enabled. Running pagefind indexing...'); - execSync('npx pagefind --site build', { stdio: 'inherit' }); - console.log('Pagefind indexing completed.'); - } else { - console.log('Search is disabled. Skipping pagefind indexing.'); - } - } catch (error) { - console.error('Error running pagefind:', error.message); - process.exit(1); - } -} - -runPagefind(); diff --git a/scripts/run-pagefind.sh b/scripts/run-pagefind.sh new file mode 100755 index 00000000..cd0c7288 --- /dev/null +++ b/scripts/run-pagefind.sh @@ -0,0 +1,60 @@ +#!/bin/bash +## run-pagefind.sh - Run pagefind indexing if search is enabled +# +# Usage: +# ./run-pagefind.sh +# + +set -e # Exit on error + +# Script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Main function +run_pagefind() { + # Check if search is enabled in site.config.js + local search_enabled + search_enabled=$(node -e " + import('$PROJECT_ROOT/site.config.js') + .then(m => { + const enabled = m.siteConfig?.search?.enabled ?? false; + console.log(enabled); + }) + .catch(e => { + console.error('Error loading site.config.js:', e.message); + process.exit(1); + }); + " 2>&1) + + if [ $? -ne 0 ]; then + log_error "Failed to load site.config.js" + log_error "$search_enabled" + exit 1 + fi + + if [ "$search_enabled" = "true" ]; then + log_info "Search is enabled. Running pagefind indexing..." + cd "$PROJECT_ROOT" + npx pagefind --site build + log_info "Pagefind indexing completed." + else + log_info "Search is disabled. Skipping pagefind indexing." + fi +} + +run_pagefind diff --git a/scripts/statue-cli.js b/scripts/statue-cli.js deleted file mode 100755 index 6d0ae532..00000000 --- a/scripts/statue-cli.js +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env node - -import { Command } from 'commander'; -import path from 'path'; -import fs from 'fs-extra'; -import { fileURLToPath, pathToFileURL } from 'url'; -import chalk from 'chalk'; - -// Get __dirname with ESM -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const packageDir = path.join(__dirname, '..'); - -const program = new Command(); - -program - .name('statue') - .description('Statue SSG - SvelteKit Static Site Generator for Markdown Content') - .version('0.2.0'); - -program - .command('init') - .description('Initialize Statue SSG in your project') - .option('-t, --template ', 'Specify a template to use (default: "default")', 'default') - .action(async (options) => { - const templateName = options.template; - console.log(chalk.blue(`🗿 Statue SSG - Initializing with template: ${chalk.bold(templateName)}`)); - - try { - // Check if template exists before proceeding - // 'default' always exists (it's the package root) - if (templateName !== 'default') { - const templatePath = path.join(packageDir, 'templates', templateName); - if (!fs.existsSync(templatePath)) { - console.error(chalk.red(`❌ Template '${templateName}' does not exist.`)); - console.log(chalk.yellow('Available templates:')); - console.log(' - default'); - try { - const templates = fs.readdirSync(path.join(packageDir, 'templates')) - .filter(t => fs.statSync(path.join(packageDir, 'templates', t)).isDirectory()); - templates.forEach(t => console.log(` - ${t}`)); - } catch (e) { - // Ignore - } - process.exit(1); - } - } - - // Run the postinstall script with options - const postinstallPath = path.join(packageDir, 'postinstall.js'); - const { default: postinstall } = await import(pathToFileURL(postinstallPath).href); - - // Execute setup with the selected template - if (typeof postinstall === 'function') { - await postinstall({ template: templateName }); - } else { - console.error(chalk.red('❌ Internal Error: postinstall script is not exporting a function.')); - } - - } catch (error) { - console.error(chalk.red('❌ Error initializing Statue SSG:'), error); - process.exit(1); - } - }); - -program.parse(process.argv); diff --git a/scripts/statue-cli.sh b/scripts/statue-cli.sh new file mode 100755 index 00000000..e7a89a36 --- /dev/null +++ b/scripts/statue-cli.sh @@ -0,0 +1,128 @@ +#!/bin/bash +## statue-cli.sh - Unified Statue SSG CLI +# +# Usage: +# statue init [--template ] # Initialize Statue SSG in your project +# statue template [args] # Manage templates (load/save/list) +# statue seo # Generate SEO files (sitemap, robots.txt) +# statue search # Run pagefind search indexing +# + +set -e # Exit on error + +# Script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# Helper functions +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_success() { + echo -e "${GREEN}$1${NC}" +} + +# Show usage +show_usage() { + cat << EOF +${CYAN}Statue SSG - SvelteKit Static Site Generator for Markdown Content${NC} + +Usage: + statue init [--template ] Initialize Statue SSG in your project + statue template [args] Manage templates (load/save/list) + statue seo Generate SEO files (sitemap, robots.txt) + statue search Run pagefind search indexing + statue help Show this help message + +Commands: + init Initialize Statue SSG with a template + template load Load a template into workspace + template save Save workspace as a template + template list List available templates + seo Generate sitemap.xml and robots.txt + search Index site for search functionality + +Examples: + statue init + statue init --template blog + statue template list + statue template load blog --force + statue seo + statue search + +EOF +} + +# Main command router +main() { + if [ $# -eq 0 ]; then + show_usage + exit 0 + fi + + local command="$1" + shift + + case "$command" in + init) + # Call the existing postinstall.js with template option + local template="default" + while [ $# -gt 0 ]; do + case "$1" in + --template|-t) + template="$2" + shift 2 + ;; + *) + log_error "Unknown option: $1" + exit 1 + ;; + esac + done + + log_info "Initializing Statue SSG with template: $template" + npx statue-setup --template "$template" + ;; + + template) + "$SCRIPT_DIR/manage-templates.sh" "$@" + ;; + + seo) + "$SCRIPT_DIR/generate-seo-files.sh" "$@" + ;; + + search) + "$SCRIPT_DIR/run-pagefind.sh" "$@" + ;; + + help|--help|-h) + show_usage + ;; + + *) + log_error "Unknown command: $command" + echo "" + show_usage + exit 1 + ;; + esac +} + +main "$@"