- 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
1125 lines
26 KiB
JavaScript
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!
|
|
`);
|