Files
strata-compile/cli/create-strata/index.js
Carlos Gutierrez a63a758cc5 feat: initial release of Strata framework v0.1.0
- Static compiler with STRC pattern (Static Template Resolution with
   Compartmentalized Layers)
   - Template syntax: { } interpolation, { s-for }, { s-if/s-elif/s-else
    }
   - File types: .strata, .compiler.sts, .service.sts, .api.sts, .sts,
   .scss
   - CLI tools: strata-compile dev, strata-compile build, strata-compile
    g (generators)
   - create-strata-compile scaffolding CLI with Pokemon API example
   - Dev server with WebSocket HMR (Hot Module Replacement)
   - Documentation: README, ARCHITECTURE, CHANGELOG, CONTRIBUTING,
   LICENSE
2026-01-16 09:13:14 -05:00

1125 lines
26 KiB
JavaScript

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const args = process.argv.slice(2);
const projectName = args[0];
const templateArg = args.find(a => a.startsWith('--template='));
const template = templateArg ? templateArg.split('=')[1] : 'pokemon';
if (!projectName || projectName === '--help' || projectName === '-h') {
console.log(`
create-strata-compile - Create a new Strata project
Usage:
npx create-strata-compile <project-name> [options]
Options:
--template=<name> Use a specific template (default: pokemon)
Available: pokemon, minimal
Examples:
npx create-strata-compile my-app
npx create-strata-compile my-app --template=minimal
CLI Generator Commands (after project creation):
strata-compile g component <Name> Create component with all files
strata-compile g page <name> Create page with all files
strata-compile g service <name> Create service file
strata-compile g api <name> Create API contract file
strata-compile g util <name> Create utility file
strata-compile g store <name> Create store definition
File Structure:
.strata → Template (HTML structure only)
.compiler.sts → Build-time execution (variable definitions)
.service.sts → Logic layer (API calls, business logic)
.api.sts → API contracts (declarative endpoints)
.sts → Pure logic (utilities, helpers)
.scss → Scoped styles
Template Syntax:
{ variable } → Variable interpolation
{ s-for item in items } → Loop (with { /s-for } closing)
{ s-for item, i in arr } → Loop with index
{ s-if condition } → Conditional
{ s-elif condition } → Else-if branch
{ s-else } → Else branch
{ /s-if } → Close conditional
{ s-imp "@path/file" } → Import component
`);
process.exit(0);
}
const projectPath = path.resolve(process.cwd(), projectName);
if (fs.existsSync(projectPath)) {
console.error(`Error: Directory "${projectName}" already exists.`);
process.exit(1);
}
console.log(`
Creating Strata project: ${projectName}
Template: ${template}
`);
// Create project structure
const dirs = [
'',
'src',
'src/components',
'src/pages',
'src/pages/index', // Index page directory
'src/pages/pokemon', // Pokemon example page
'src/services',
'src/api',
'src/stores',
'src/utils',
'src/assets',
'src/assets/styles',
'public',
'.vscode',
];
for (const dir of dirs) {
fs.mkdirSync(path.join(projectPath, dir), { recursive: true });
}
// Create package.json
const packageJson = {
name: projectName,
version: '0.1.0',
private: true,
scripts: {
dev: '~/.strata/bin/strata-compile dev',
build: '~/.strata/bin/strata-compile build',
preview: '~/.strata/bin/strata-compile preview',
},
devDependencies: {
typescript: '^5.3.0',
},
};
fs.writeFileSync(
path.join(projectPath, 'package.json'),
JSON.stringify(packageJson, null, 2)
);
// Create strataconfig.ts
const strataConfig = `import { defineConfig } from 'strata';
export default defineConfig({
app: {
title: '${projectName}',
description: 'A Strata application with Pokemon API example',
},
// API configuration
api: {
baseUrl: process.env.API_URL || 'https://pokeapi.co/api/v2',
cache: {
enabled: true,
ttl: '5m', // 5 minute cache
},
},
// State management
state: {
encrypt: false, // Encrypt sensitive state fields
persist: true, // Persist state to localStorage
},
// Build options
build: {
static: true, // Static-first compilation
minify: true,
sourceMaps: false,
},
});
`;
fs.writeFileSync(path.join(projectPath, 'strataconfig.ts'), strataConfig);
// ============================================================
// INDEX PAGE - Welcome page with all syntax examples
// ============================================================
const indexStrata = `<template>
<main class="index-page">
<!-- Header with variable interpolation -->
<header class="hero">
<h1>{ title }</h1>
<p class="subtitle">{ subtitle }</p>
</header>
<!-- Feature cards showing framework capabilities -->
<section class="features">
<h2>Framework Features</h2>
<!-- s-for loop example -->
<div class="feature-grid">
{ s-for feature in features }
<div class="feature-card">
<span class="icon">{ feature.icon }</span>
<h3>{ feature.name }</h3>
<p>{ feature.description }</p>
</div>
{ /s-for }
</div>
</section>
<!-- Conditional rendering example -->
<section class="status">
{ s-if isProduction }
<div class="badge production">Production Mode</div>
{ s-elif isDevelopment }
<div class="badge development">Development Mode</div>
{ s-else }
<div class="badge unknown">Unknown Mode</div>
{ /s-if }
</section>
<!-- Statistics with index in loop -->
<section class="stats">
<h2>Statistics</h2>
<div class="stats-grid">
{ s-for stat, index in stats }
<div class="stat-card">
<span class="stat-index">#{ index }</span>
<span class="stat-value">{ stat.value }</span>
<span class="stat-label">{ stat.label }</span>
</div>
{ /s-for }
</div>
</section>
<!-- Navigation to Pokemon page -->
<section class="cta">
<h2>Explore the Pokemon Example</h2>
<p>See how Strata fetches data from the PokeAPI at build time</p>
<a href="/pokemon" class="btn">View Pokemon →</a>
</section>
<!-- Footer -->
<footer>
<p>Built with Strata Framework | Static Template Rendering Architecture</p>
<p class="version">Version { version }</p>
</footer>
</main>
</template>
`;
const indexCompiler = `// index.compiler.sts - Build-time execution layer
// All values resolved at compile time → pure static HTML output
// Static values - resolved during build
export const title = '${projectName}';
export const subtitle = 'Static Template Rendering Architecture';
export const version = '1.0.0';
// Environment flags - evaluated at build time
export const isProduction = false;
export const isDevelopment = true;
// Feature list - demonstrates s-for loop
export const features = [
{
icon: '⚡',
name: 'Static Compilation',
description: 'Templates compiled to pure HTML at build time'
},
{
icon: '🔄',
name: 'Build-time Data Fetching',
description: 'API calls executed during compilation'
},
{
icon: '📦',
name: 'Zero Runtime Overhead',
description: 'No framework JavaScript in production'
},
{
icon: '🎯',
name: 'Type-Safe Templates',
description: 'Full TypeScript support for data and logic'
},
{
icon: '🔒',
name: 'Encrypted State',
description: 'Optional encryption for sensitive client state'
},
{
icon: '🌐',
name: 'Cross-Tab Sync',
description: 'Shared Worker for tab coordination'
}
];
// Statistics with index example
export const stats = [
{ value: '< 5KB', label: 'Runtime Size' },
{ value: '100ms', label: 'Build Time' },
{ value: '0', label: 'Runtime Dependencies' },
{ value: '∞', label: 'Possibilities' }
];
export default {
title,
subtitle,
version,
isProduction,
isDevelopment,
features,
stats
};
`;
const indexService = `// index.service.sts - Logic layer
// This file is for runtime interactions (if needed)
// For static pages, most logic happens in .compiler.sts
export default {};
`;
const indexScss = `.index-page {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.hero {
text-align: center;
padding: 4rem 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 1rem;
color: white;
margin-bottom: 3rem;
h1 {
font-size: 3rem;
margin-bottom: 1rem;
}
.subtitle {
font-size: 1.25rem;
opacity: 0.9;
}
}
.features {
margin-bottom: 3rem;
h2 {
text-align: center;
margin-bottom: 2rem;
}
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.feature-card {
background: white;
border: 1px solid #e5e7eb;
border-radius: 0.75rem;
padding: 1.5rem;
transition: transform 0.2s, box-shadow 0.2s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
}
.icon {
font-size: 2rem;
display: block;
margin-bottom: 0.75rem;
}
h3 {
margin-bottom: 0.5rem;
color: #1f2937;
}
p {
color: #6b7280;
font-size: 0.875rem;
}
}
.status {
text-align: center;
margin-bottom: 3rem;
.badge {
display: inline-block;
padding: 0.5rem 1rem;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 500;
&.production {
background: #dcfce7;
color: #166534;
}
&.development {
background: #fef3c7;
color: #92400e;
}
&.unknown {
background: #f3f4f6;
color: #374151;
}
}
}
.stats {
margin-bottom: 3rem;
h2 {
text-align: center;
margin-bottom: 2rem;
}
}
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
@media (max-width: 768px) {
grid-template-columns: repeat(2, 1fr);
}
}
.stat-card {
background: #f9fafb;
border-radius: 0.75rem;
padding: 1.5rem;
text-align: center;
display: flex;
flex-direction: column;
gap: 0.25rem;
.stat-index {
font-size: 0.75rem;
color: #9ca3af;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: #667eea;
}
.stat-label {
font-size: 0.875rem;
color: #6b7280;
}
}
.cta {
text-align: center;
padding: 3rem;
background: #f9fafb;
border-radius: 1rem;
margin-bottom: 3rem;
h2 {
margin-bottom: 0.5rem;
}
p {
color: #6b7280;
margin-bottom: 1.5rem;
}
.btn {
display: inline-block;
background: #667eea;
color: white;
padding: 0.75rem 2rem;
border-radius: 0.5rem;
text-decoration: none;
font-weight: 500;
transition: background 0.2s;
&:hover {
background: #5a67d8;
}
}
}
footer {
text-align: center;
padding: 2rem;
color: #9ca3af;
font-size: 0.875rem;
.version {
margin-top: 0.5rem;
font-family: monospace;
}
}
`;
// Write index page files
fs.writeFileSync(path.join(projectPath, 'src/pages/index/index.strata'), indexStrata);
fs.writeFileSync(path.join(projectPath, 'src/pages/index/index.compiler.sts'), indexCompiler);
fs.writeFileSync(path.join(projectPath, 'src/pages/index/index.service.sts'), indexService);
fs.writeFileSync(path.join(projectPath, 'src/pages/index/index.scss'), indexScss);
// ============================================================
// POKEMON PAGE - API fetching at build time example
// ============================================================
const pokemonStrata = `<template>
<main class="pokemon-page">
<header>
<h1>{ title }</h1>
<p>{ description }</p>
<p class="meta">Fetched { totalCount } Pokemon at build time</p>
</header>
<!-- Search section (static filter) -->
<section class="search-info">
<p>This page demonstrates build-time API fetching.</p>
<p>All { totalCount } Pokemon were fetched from PokeAPI during compilation.</p>
</section>
<!-- Pokemon grid -->
<section class="pokemon-grid">
{ s-for pokemon, index in pokemons }
<div class="pokemon-card">
<span class="pokemon-number">#{ pokemon.id }</span>
<img src="{ pokemon.sprite }" alt="{ pokemon.name }" loading="lazy" />
<h3>{ pokemon.name }</h3>
<div class="pokemon-types">
{ s-for type in pokemon.types }
<span class="type-badge { type }">{ type }</span>
{ /s-for }
</div>
<div class="pokemon-stats">
{ s-if pokemon.stats }
<div class="stat">
<span>HP</span>
<span>{ pokemon.stats.hp }</span>
</div>
<div class="stat">
<span>ATK</span>
<span>{ pokemon.stats.attack }</span>
</div>
<div class="stat">
<span>DEF</span>
<span>{ pokemon.stats.defense }</span>
</div>
{ /s-if }
</div>
</div>
{ /s-for }
</section>
<!-- Conditional: Show message if no pokemon -->
{ s-if !hasPokemons }
<div class="no-results">
<p>No Pokemon found. Build-time fetch may have failed.</p>
</div>
{ /s-if }
<!-- Back link -->
<nav class="back-nav">
<a href="/">← Back to Home</a>
</nav>
</main>
</template>
`;
const pokemonCompiler = `// pokemon.compiler.sts - Build-time data fetching
// This file runs during compilation to fetch Pokemon data
// Page metadata
export const title = 'Pokemon Gallery';
export const description = 'Pokemon data fetched at build time from PokeAPI';
// Fetch Pokemon list at build time
// The static compiler will execute this during build
export const pokemons = await fetchPokemonList();
// Computed values
export const totalCount = pokemons.length;
export const hasPokemons = pokemons.length > 0;
// Build-time fetch function
async function fetchPokemonList() {
// This runs at compile time, not in browser
// The compiler has a built-in fetch capability
// For demonstration, return static data
// In real build, the compiler fetches from: https://pokeapi.co/api/v2/pokemon?limit=20
return [
{
id: 1,
name: 'Bulbasaur',
sprite: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png',
types: ['grass', 'poison'],
stats: { hp: 45, attack: 49, defense: 49 }
},
{
id: 4,
name: 'Charmander',
sprite: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/4.png',
types: ['fire'],
stats: { hp: 39, attack: 52, defense: 43 }
},
{
id: 7,
name: 'Squirtle',
sprite: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/7.png',
types: ['water'],
stats: { hp: 44, attack: 48, defense: 65 }
},
{
id: 25,
name: 'Pikachu',
sprite: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png',
types: ['electric'],
stats: { hp: 35, attack: 55, defense: 40 }
},
{
id: 39,
name: 'Jigglypuff',
sprite: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/39.png',
types: ['normal', 'fairy'],
stats: { hp: 115, attack: 45, defense: 20 }
},
{
id: 52,
name: 'Meowth',
sprite: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/52.png',
types: ['normal'],
stats: { hp: 40, attack: 45, defense: 35 }
},
{
id: 133,
name: 'Eevee',
sprite: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/133.png',
types: ['normal'],
stats: { hp: 55, attack: 55, defense: 50 }
},
{
id: 143,
name: 'Snorlax',
sprite: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/143.png',
types: ['normal'],
stats: { hp: 160, attack: 110, defense: 65 }
}
];
}
export default {
title,
description,
pokemons,
totalCount,
hasPokemons
};
`;
const pokemonService = `// pokemon.service.sts - Runtime logic (if needed)
// For static pages, this file is typically empty
// Use for runtime interactions like search filtering
export default {};
`;
const pokemonScss = `.pokemon-page {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
header {
text-align: center;
margin-bottom: 2rem;
h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
color: #1f2937;
}
p {
color: #6b7280;
}
.meta {
font-family: monospace;
background: #f3f4f6;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
display: inline-block;
margin-top: 1rem;
}
}
.search-info {
background: #eff6ff;
border: 1px solid #bfdbfe;
border-radius: 0.75rem;
padding: 1rem 1.5rem;
margin-bottom: 2rem;
text-align: center;
p {
color: #1e40af;
margin: 0.25rem 0;
}
}
.pokemon-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.pokemon-card {
background: white;
border: 1px solid #e5e7eb;
border-radius: 1rem;
padding: 1.5rem;
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
&:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
}
.pokemon-number {
font-size: 0.75rem;
color: #9ca3af;
font-family: monospace;
}
img {
width: 96px;
height: 96px;
margin: 0.5rem 0;
image-rendering: pixelated;
}
h3 {
text-transform: capitalize;
margin-bottom: 0.5rem;
color: #1f2937;
}
.pokemon-types {
display: flex;
gap: 0.25rem;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 0.75rem;
}
.type-badge {
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
text-transform: capitalize;
color: white;
&.grass { background: #22c55e; }
&.poison { background: #a855f7; }
&.fire { background: #f97316; }
&.water { background: #3b82f6; }
&.electric { background: #eab308; color: #1f2937; }
&.normal { background: #a8a29e; }
&.fairy { background: #ec4899; }
}
.pokemon-stats {
display: flex;
justify-content: space-around;
padding-top: 0.75rem;
border-top: 1px solid #e5e7eb;
.stat {
display: flex;
flex-direction: column;
font-size: 0.75rem;
span:first-child {
color: #9ca3af;
font-weight: 500;
}
span:last-child {
font-weight: 700;
color: #1f2937;
}
}
}
}
.no-results {
text-align: center;
padding: 3rem;
background: #fef2f2;
border-radius: 0.75rem;
color: #991b1b;
}
.back-nav {
text-align: center;
padding: 2rem;
a {
color: #667eea;
text-decoration: none;
font-weight: 500;
&:hover {
text-decoration: underline;
}
}
}
`;
// Write Pokemon page files
fs.writeFileSync(path.join(projectPath, 'src/pages/pokemon/pokemon.strata'), pokemonStrata);
fs.writeFileSync(path.join(projectPath, 'src/pages/pokemon/pokemon.compiler.sts'), pokemonCompiler);
fs.writeFileSync(path.join(projectPath, 'src/pages/pokemon/pokemon.service.sts'), pokemonService);
fs.writeFileSync(path.join(projectPath, 'src/pages/pokemon/pokemon.scss'), pokemonScss);
// ============================================================
// API CONTRACT EXAMPLE
// ============================================================
const pokemonApi = `// pokemon.api.sts - API contracts (declarative, dual-mode)
// Describe API endpoints once, executable in compiler and runtime mode
import { defineApi } from 'strata';
/**
* Get Pokemon list
*/
export const getPokemonListApi = defineApi({
path: 'https://pokeapi.co/api/v2/pokemon',
method: 'GET',
cache: '1h', // Cache for 1 hour
params: {
limit: 20,
offset: 0,
},
});
/**
* Get single Pokemon by ID or name
*/
export const getPokemonApi = defineApi({
path: 'https://pokeapi.co/api/v2/pokemon/:id',
method: 'GET',
cache: '1h',
});
/**
* Get Pokemon species (for evolution data)
*/
export const getPokemonSpeciesApi = defineApi({
path: 'https://pokeapi.co/api/v2/pokemon-species/:id',
method: 'GET',
cache: '1h',
});
export default {
getPokemonListApi,
getPokemonApi,
getPokemonSpeciesApi,
};
`;
fs.writeFileSync(path.join(projectPath, 'src/api/pokemon.api.sts'), pokemonApi);
// ============================================================
// GLOBAL STYLES
// ============================================================
const globalStyles = `// Global styles
@import 'variables';
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--text-primary);
line-height: 1.6;
background: var(--bg-primary);
min-height: 100vh;
}
#app {
min-height: 100vh;
}
a {
color: var(--primary);
}
img {
max-width: 100%;
height: auto;
}
`;
const variables = `// Design tokens
:root {
// Colors
--primary: #667eea;
--primary-dark: #5a67d8;
--secondary: #764ba2;
// Text
--text-primary: #1f2937;
--text-secondary: #6b7280;
--text-muted: #9ca3af;
// Backgrounds
--bg-primary: #ffffff;
--bg-secondary: #f9fafb;
--bg-tertiary: #f3f4f6;
// Borders
--border-color: #e5e7eb;
// Shadows
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.1);
}
`;
fs.writeFileSync(path.join(projectPath, 'src/assets/styles/global.scss'), globalStyles);
fs.writeFileSync(path.join(projectPath, 'src/assets/styles/_variables.scss'), variables);
// ============================================================
// UTILITY EXAMPLE
// ============================================================
const formatUtil = `// format.sts - Pure utility functions
// No side effects. Deterministic output.
/**
* Capitalize first letter of a string
*/
export function capitalize(str) {
if (!str) return '';
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Format number with commas
*/
export function formatNumber(num) {
return num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');
}
/**
* Truncate string with ellipsis
*/
export function truncate(str, length = 50) {
if (!str || str.length <= length) return str;
return str.slice(0, length) + '...';
}
export default { capitalize, formatNumber, truncate };
`;
fs.writeFileSync(path.join(projectPath, 'src/utils/format.sts'), formatUtil);
// ============================================================
// CONFIGURATION FILES
// ============================================================
const gitignore = `node_modules
dist
.strata
*.log
.DS_Store
.env
.env.local
`;
const vscodeSettings = {
"files.associations": {
"*.strata": "html",
"*.sts": "typescript",
"*.compiler.sts": "typescript",
"*.service.sts": "typescript",
"*.api.sts": "typescript"
},
"emmet.includeLanguages": {
"strata": "html"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.tsdk": "node_modules/typescript/lib"
};
const nvimConfig = `-- Strata file type detection for NeoVim
vim.filetype.add({
extension = {
strata = "html",
sts = "typescript",
},
pattern = {
[".*%.compiler%.sts"] = "typescript",
[".*%.service%.sts"] = "typescript",
[".*%.api%.sts"] = "typescript",
},
})
`;
fs.writeFileSync(path.join(projectPath, '.gitignore'), gitignore);
fs.writeFileSync(path.join(projectPath, '.vscode/settings.json'), JSON.stringify(vscodeSettings, null, 2));
fs.writeFileSync(path.join(projectPath, '.nvim.lua'), nvimConfig);
// ============================================================
// README
// ============================================================
const readme = `# ${projectName}
A Strata application with Pokemon API example.
## Getting Started
\`\`\`bash
npm install
npm run dev
\`\`\`
## Project Structure
\`\`\`
src/
├── pages/
│ ├── index/ # Home page
│ │ ├── index.strata # Template
│ │ ├── index.compiler.sts # Build-time data
│ │ ├── index.service.sts # Runtime logic
│ │ └── index.scss # Styles
│ └── pokemon/ # Pokemon example
│ ├── pokemon.strata
│ ├── pokemon.compiler.sts
│ ├── pokemon.service.sts
│ └── pokemon.scss
├── api/ # API contracts
├── services/ # Business logic
├── utils/ # Pure utilities
└── assets/
└── styles/
\`\`\`
## CLI Commands
### Generate Components
\`\`\`bash
strata-compile g component UserCard # Create component
strata-compile g page users # Create page
strata-compile g service user # Create service
strata-compile g api user # Create API contract
strata-compile g util format # Create utility
strata-compile g store user # Create store
\`\`\`
## Template Syntax
| Syntax | Description |
|--------|-------------|
| \`{ variable }\` | Variable interpolation |
| \`{ s-for item in items }\` | Loop |
| \`{ s-for item, i in arr }\` | Loop with index |
| \`{ s-if condition }\` | Conditional |
| \`{ s-elif condition }\` | Else-if |
| \`{ s-else }\` | Else |
| \`{ /s-if }\`, \`{ /s-for }\` | Close block |
## File Types
| Extension | Purpose |
|-----------|---------|
| \`.strata\` | Template (HTML structure) |
| \`.compiler.sts\` | Build-time data & logic |
| \`.service.sts\` | Runtime logic |
| \`.api.sts\` | API contracts |
| \`.sts\` | Pure utilities |
| \`.scss\` | Scoped styles |
## Learn More
- [Strata Documentation](https://strata.dev)
`;
fs.writeFileSync(path.join(projectPath, 'README.md'), readme);
// Initialize git repository
try {
execSync('git init', { cwd: projectPath, stdio: 'ignore' });
execSync('git add .', { cwd: projectPath, stdio: 'ignore' });
execSync('git commit -m "Initial commit - Strata project with Pokemon example"', {
cwd: projectPath,
stdio: 'ignore',
env: { ...process.env, GIT_AUTHOR_NAME: 'Strata', GIT_AUTHOR_EMAIL: 'strata@localhost', GIT_COMMITTER_NAME: 'Strata', GIT_COMMITTER_EMAIL: 'strata@localhost' }
});
console.log(' ✓ Git repository initialized');
} catch (err) {
console.log(' ⚠ Could not initialize git repository');
}
console.log(`
✓ Project created successfully!
Project structure:
src/pages/index/ - Home page with feature examples
src/pages/pokemon/ - Pokemon API example (build-time fetch)
src/api/ - API contracts
src/utils/ - Pure utility functions
Template syntax examples included:
{ variable } - Variable interpolation
{ s-for } - Loop with items
{ s-for item, i } - Loop with index
{ s-if/s-elif/s-else } - Conditionals
Next steps:
cd ${projectName}
npm install
npm run dev
CLI generator commands:
strata-compile g component <Name> - Create component
strata-compile g page <name> - Create page
strata-compile g service <name> - Create service
strata-compile g api <name> - Create API contract
strata-compile g util <name> - Create utility
Happy coding with Strata!
`);