clovie 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -12,11 +12,31 @@ import { createClovie } from "../lib/createClovie.js";
12
12
 
13
13
  // Check for create command first (before any argument parsing)
14
14
  if (process.argv.includes('create') && process.argv.length > 2) {
15
- const projectName = process.argv[3]; // The name after 'create'
15
+ const createArgIndex = process.argv.indexOf('create');
16
+ const projectName = process.argv[createArgIndex + 1];
16
17
 
17
18
  if (!projectName) {
18
19
  console.error('Error: Please provide a project name');
19
- console.error('Usage: clovie create <project-name>');
20
+ console.error('Usage: clovie create <project-name> [--template <template-type>]');
21
+ console.error('Available templates: default, static, server');
22
+ process.exit(1);
23
+ }
24
+
25
+ // Parse arguments to get template option
26
+ const createArgs = process.argv.slice(createArgIndex + 2);
27
+ let templateType = 'default';
28
+
29
+ // Look for --template or -t flag
30
+ const templateIndex = createArgs.findIndex(arg => arg === '--template' || arg === '-t');
31
+ if (templateIndex !== -1 && createArgs[templateIndex + 1]) {
32
+ templateType = createArgs[templateIndex + 1];
33
+ }
34
+
35
+ // Validate template type
36
+ const validTemplates = ['default', 'static', 'server'];
37
+ if (!validTemplates.includes(templateType)) {
38
+ console.error(`Error: Invalid template type '${templateType}'`);
39
+ console.error(`Available templates: ${validTemplates.join(', ')}`);
20
40
  process.exit(1);
21
41
  }
22
42
 
@@ -28,8 +48,8 @@ if (process.argv.includes('create') && process.argv.length > 2) {
28
48
  process.exit(1);
29
49
  }
30
50
 
31
- // Copy template files
32
- const templateDir = path.join(__dirname, '../templates/default');
51
+ // Copy template files from the specified template
52
+ const templateDir = path.join(__dirname, `../templates/${templateType}`);
33
53
 
34
54
  // Create project directory first
35
55
  fs.mkdirSync(projectPath, { recursive: true });
@@ -54,12 +74,37 @@ if (process.argv.includes('create') && process.argv.length > 2) {
54
74
  };
55
75
 
56
76
  try {
77
+ // Check if template directory exists
78
+ if (!fs.existsSync(templateDir)) {
79
+ console.error(`Error: Template '${templateType}' not found at ${templateDir}`);
80
+ console.error(`Available templates: ${validTemplates.join(', ')}`);
81
+ process.exit(1);
82
+ }
83
+
57
84
  await copyDir(templateDir, projectPath);
58
- console.log(`✅ Clovie project created successfully at ${projectPath}`);
85
+ console.log(`✅ Clovie ${templateType} project created successfully!`);
86
+ console.log(`📁 Project location: ${projectPath}`);
87
+ console.log(`🎨 Template: ${templateType}`);
59
88
  console.log('\nNext steps:');
60
89
  console.log(` cd ${projectName}`);
61
90
  console.log(' npm install');
62
91
  console.log(' npm run dev');
92
+
93
+ // Show template-specific next steps
94
+ if (templateType === 'server') {
95
+ console.log('\n🌐 Server template features:');
96
+ console.log(' • API endpoints ready at /api/*');
97
+ console.log(' • Admin dashboard at /dashboard.html');
98
+ console.log(' • User profiles at /user/:id');
99
+ console.log(' • Use "npm run start" for production server');
100
+ } else if (templateType === 'static') {
101
+ console.log('\n📄 Static template features:');
102
+ console.log(' • SEO optimized pages');
103
+ console.log(' • Modern responsive design');
104
+ console.log(' • Ready for JAMstack deployment');
105
+ console.log(' • Use "npm run build" to generate static files');
106
+ }
107
+
63
108
  process.exit(0);
64
109
  } catch (err) {
65
110
  console.error('Error creating project:', err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clovie",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Vintage web dev tooling with modern quality of life",
5
5
  "keywords": [
6
6
  "static-site-generator",
@@ -0,0 +1,111 @@
1
+ # {{projectName}}
2
+
3
+ A server-side web application built with [Clovie](https://github.com/adrianjonmiller/clovie).
4
+
5
+ ## Features
6
+
7
+ - 🌐 Express.js server with Clovie integration
8
+ - 🔌 API routes with state management
9
+ - 📄 Server-side rendered views
10
+ - 🎨 Modern responsive UI
11
+ - 🔄 Live reload in development
12
+ - 📊 Built-in dashboard and user management
13
+ - ⚡ Real-time WebSocket support
14
+
15
+ ## Getting Started
16
+
17
+ ```bash
18
+ # Install dependencies
19
+ npm install
20
+
21
+ # Start development server (with live reload)
22
+ npm run dev
23
+
24
+ # Start production server
25
+ npm start
26
+ ```
27
+
28
+ ## Project Structure
29
+
30
+ ```
31
+ ├── views/ # Server-rendered templates
32
+ │ ├── index.html # Homepage
33
+ │ ├── dashboard.html # Admin dashboard
34
+ │ ├── login.html # Login page
35
+ │ └── profile.html # User profile page
36
+ ├── scripts/ # Client-side JavaScript
37
+ │ └── app.js # Main application logic
38
+ ├── styles/ # SCSS stylesheets
39
+ │ └── main.scss # Main styles with dashboard UI
40
+ ├── partials/ # Reusable template components
41
+ │ ├── header.html # Site header with user menu
42
+ │ ├── nav.html # Sidebar navigation
43
+ │ └── footer.html # Site footer
44
+ └── clovie.config.js # Server configuration
45
+ ```
46
+
47
+ ## Available API Endpoints
48
+
49
+ This template includes these working API endpoints:
50
+
51
+ - `GET /api/status` - Server status and uptime
52
+ - `GET /api/users` - List all users with total count
53
+ - `POST /api/users` - Create a new user (requires name, email in body)
54
+ - `GET /api/users/:id` - Get specific user by ID
55
+
56
+ ## Server Routes
57
+
58
+ - `/` - Homepage
59
+ - `/dashboard.html` - Admin dashboard
60
+ - `/login.html` - Login page
61
+ - `/user/:id` - Dynamic user profile page (server-rendered)
62
+
63
+ ## Configuration
64
+
65
+ The `clovie.config.js` file shows how to:
66
+
67
+ - Set up API routes with `action` functions
68
+ - Configure middleware with Express functions
69
+ - Create dynamic routes with templates and data functions
70
+ - Use Clovie's state management system
71
+
72
+ ## State Management
73
+
74
+ Clovie provides a state system accessible in API actions and route data functions:
75
+
76
+ ```javascript
77
+ // In API actions and route data functions:
78
+ const users = state.get('users') || [];
79
+ state.set('users', newUsersArray);
80
+ ```
81
+
82
+ ## Development
83
+
84
+ ```bash
85
+ # Start with live reload
86
+ npm run dev
87
+ # Server runs at http://localhost:3000
88
+ # Live reload via WebSocket
89
+ ```
90
+
91
+ ## API Usage Examples
92
+
93
+ ```javascript
94
+ // Get server status
95
+ fetch('/api/status')
96
+
97
+ // Create a user
98
+ fetch('/api/users', {
99
+ method: 'POST',
100
+ headers: { 'Content-Type': 'application/json' },
101
+ body: JSON.stringify({ name: 'John', email: 'john@example.com' })
102
+ })
103
+
104
+ // Get user by ID
105
+ fetch('/api/users/123')
106
+ ```
107
+
108
+ ## Learn More
109
+
110
+ - [Clovie Documentation](https://github.com/adrianjonmiller/clovie)
111
+ - [Express.js Guide](https://expressjs.com/)
@@ -0,0 +1,107 @@
1
+ import express from 'express';
2
+
3
+ export default {
4
+ // Server-side application configuration
5
+ type: 'server',
6
+ port: 3000,
7
+
8
+ data: {
9
+ title: '{{projectName}}',
10
+ description: 'Full-stack web application built with Clovie',
11
+ author: 'Your Name',
12
+ version: '1.0.0'
13
+ },
14
+
15
+ // Middleware configuration (functions that run before routes)
16
+ middleware: [
17
+ express.json(),
18
+ express.urlencoded({ extended: true })
19
+ ],
20
+
21
+ // API routes configuration
22
+ api: [
23
+ {
24
+ name: 'API Status',
25
+ path: '/api/status',
26
+ method: 'GET',
27
+ action: async (state, event) => {
28
+ return {
29
+ status: 'ok',
30
+ timestamp: new Date().toISOString(),
31
+ uptime: process.uptime()
32
+ };
33
+ }
34
+ },
35
+ {
36
+ name: 'Get Users',
37
+ path: '/api/users',
38
+ method: 'GET',
39
+ action: async (state, event) => {
40
+ // Get users from state or return mock data
41
+ const users = state.get ? state.get('users') || [] : [];
42
+ return {
43
+ users,
44
+ total: users.length
45
+ };
46
+ }
47
+ },
48
+ {
49
+ name: 'Create User',
50
+ path: '/api/users',
51
+ method: 'POST',
52
+ action: async (state, event) => {
53
+ const { name, email } = event.body;
54
+ const newUser = {
55
+ id: Date.now(),
56
+ name,
57
+ email,
58
+ createdAt: new Date().toISOString()
59
+ };
60
+
61
+ // Add user to state if available
62
+ if (state.set) {
63
+ const users = state.get('users') || [];
64
+ users.push(newUser);
65
+ state.set('users', users);
66
+ }
67
+
68
+ return { success: true, user: newUser };
69
+ }
70
+ },
71
+ {
72
+ name: 'Get User by ID',
73
+ path: '/api/users/:id',
74
+ method: 'GET',
75
+ action: async (state, event) => {
76
+ const userId = parseInt(event.params.id);
77
+ const users = state.get ? state.get('users') || [] : [];
78
+ const user = users.find(u => u.id === userId);
79
+
80
+ if (!user) {
81
+ return { error: 'User not found', status: 404 };
82
+ }
83
+
84
+ return { user };
85
+ }
86
+ }
87
+ ],
88
+
89
+ // Configured routes (server-rendered pages with templates)
90
+ routes: [
91
+ {
92
+ name: 'User Profile',
93
+ path: '/user/:id',
94
+ template: './views/profile.html',
95
+ data: async (state, params) => {
96
+ const userId = parseInt(params.id);
97
+ const users = state.get ? state.get('users') || [] : [];
98
+ const user = users.find(u => u.id === userId);
99
+
100
+ return {
101
+ user: user || { name: 'Unknown User', email: 'unknown@example.com' },
102
+ title: `User Profile - ${user?.name || 'Unknown'}`
103
+ };
104
+ }
105
+ }
106
+ ]
107
+ };
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "1.0.0",
4
+ "description": "Server-side web application built with Clovie",
5
+ "main": "server.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "clovie watch",
9
+ "start": "clovie server",
10
+ "build": "clovie build"
11
+ },
12
+ "dependencies": {
13
+ "@brickworks/clovie": "^0.1.5",
14
+ "express": "^4.18.2"
15
+ },
16
+ "keywords": [
17
+ "server",
18
+ "web-app",
19
+ "clovie",
20
+ "full-stack",
21
+ "api"
22
+ ],
23
+ "license": "MIT"
24
+ }
@@ -0,0 +1,16 @@
1
+ <footer class="footer">
2
+ <div class="container">
3
+ <div class="footer__content">
4
+ <div class="footer__section">
5
+ <p>&copy; 2024 {{title}}. Built with <a href="https://github.com/adrianjonmiller/clovie" target="_blank">Clovie</a></p>
6
+ </div>
7
+ <div class="footer__section">
8
+ <div class="footer__links">
9
+ <a href="/api/status">API Status</a>
10
+ <a href="/docs">Documentation</a>
11
+ <a href="/support">Support</a>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </footer>
@@ -0,0 +1,26 @@
1
+ <header class="header">
2
+ <div class="container">
3
+ <div class="header__content">
4
+ <a href="/" class="header__brand">
5
+ <span class="brand-icon">⚡</span>
6
+ {{title}}
7
+ </a>
8
+
9
+ <div class="header__actions">
10
+ <button class="btn btn--small btn--outline" onclick="toggleTheme()">🌙</button>
11
+ <div class="user-menu" id="user-menu">
12
+ <button class="user-avatar" onclick="toggleUserMenu()">
13
+ <img src="https://ui-avatars.com/api/?name=User&background=2563eb&color=fff" alt="User Avatar">
14
+ </button>
15
+ <div class="user-dropdown" id="user-dropdown">
16
+ <a href="/dashboard.html">Dashboard</a>
17
+ <a href="/profile">Profile</a>
18
+ <a href="/settings">Settings</a>
19
+ <hr>
20
+ <a href="#" onclick="logout()">Sign Out</a>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </header>
@@ -0,0 +1,49 @@
1
+ <nav class="sidebar" id="sidebar">
2
+ <div class="sidebar__content">
3
+ <ul class="nav-menu">
4
+ <li class="nav-item">
5
+ <a href="/dashboard.html" class="nav-link" data-page="dashboard">
6
+ <span class="nav-icon">📊</span>
7
+ Dashboard
8
+ </a>
9
+ </li>
10
+ <li class="nav-item">
11
+ <a href="/users" class="nav-link" data-page="users">
12
+ <span class="nav-icon">👥</span>
13
+ Users
14
+ </a>
15
+ </li>
16
+ <li class="nav-item">
17
+ <a href="/posts" class="nav-link" data-page="posts">
18
+ <span class="nav-icon">📝</span>
19
+ Posts
20
+ </a>
21
+ </li>
22
+ <li class="nav-item">
23
+ <a href="/analytics" class="nav-link" data-page="analytics">
24
+ <span class="nav-icon">📈</span>
25
+ Analytics
26
+ </a>
27
+ </li>
28
+ <li class="nav-item">
29
+ <a href="/settings" class="nav-link" data-page="settings">
30
+ <span class="nav-icon">⚙️</span>
31
+ Settings
32
+ </a>
33
+ </li>
34
+ </ul>
35
+
36
+ <div class="sidebar__footer">
37
+ <div class="server-status">
38
+ <span class="status-dot status-dot--online"></span>
39
+ <span class="status-text">Server Online</span>
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ <button class="sidebar-toggle" onclick="toggleSidebar()">
45
+ <span></span>
46
+ <span></span>
47
+ <span></span>
48
+ </button>
49
+ </nav>
@@ -0,0 +1,213 @@
1
+ // Server Application JavaScript
2
+
3
+ // API helper functions
4
+ const api = {
5
+ async get(url) {
6
+ const response = await fetch(url);
7
+ if (!response.ok) {
8
+ throw new Error(`HTTP error! status: ${response.status}`);
9
+ }
10
+ return response.json();
11
+ },
12
+
13
+ async post(url, data) {
14
+ const response = await fetch(url, {
15
+ method: 'POST',
16
+ headers: {
17
+ 'Content-Type': 'application/json',
18
+ },
19
+ body: JSON.stringify(data)
20
+ });
21
+ if (!response.ok) {
22
+ throw new Error(`HTTP error! status: ${response.status}`);
23
+ }
24
+ return response.json();
25
+ }
26
+ };
27
+
28
+ // Dashboard functionality
29
+ async function loadDashboardData() {
30
+ try {
31
+ // Load API status
32
+ const status = await api.get('/api/status');
33
+ document.getElementById('server-uptime').textContent = `${Math.floor(status.uptime / 60)}m`;
34
+
35
+ // Load users
36
+ const usersData = await api.get('/api/users');
37
+ document.getElementById('user-count').textContent = usersData.total;
38
+
39
+ // Update recent activity
40
+ updateActivity(`System status: ${status.status}`);
41
+ updateActivity(`${usersData.total} users registered`);
42
+
43
+ } catch (error) {
44
+ console.error('Error loading dashboard data:', error);
45
+ document.getElementById('user-count').textContent = 'Error';
46
+ document.getElementById('server-uptime').textContent = 'Error';
47
+ updateActivity('Failed to load data');
48
+ }
49
+ }
50
+
51
+ // User management functions
52
+ async function createUser() {
53
+ const name = prompt('Enter user name:');
54
+ const email = prompt('Enter user email:');
55
+
56
+ if (name && email) {
57
+ try {
58
+ const result = await api.post('/api/users', { name, email });
59
+ if (result.success) {
60
+ updateActivity(`Created user: ${name}`);
61
+ loadDashboardData(); // Refresh data
62
+ alert('User created successfully!');
63
+ }
64
+ } catch (error) {
65
+ console.error('Error creating user:', error);
66
+ alert('Failed to create user');
67
+ }
68
+ }
69
+ }
70
+
71
+ async function refreshData() {
72
+ updateActivity('Refreshing data...');
73
+ await loadDashboardData();
74
+ updateActivity('Data refreshed');
75
+ }
76
+
77
+ async function checkApiStatus() {
78
+ try {
79
+ const status = await api.get('/api/status');
80
+ alert(`API Status: ${status.status}\nUptime: ${Math.floor(status.uptime / 60)} minutes`);
81
+ } catch (error) {
82
+ alert('API is not responding');
83
+ }
84
+ }
85
+
86
+ // Utility functions
87
+ function updateActivity(message) {
88
+ const activityList = document.getElementById('recent-activity');
89
+ if (activityList) {
90
+ // Remove loading message if present
91
+ const loading = activityList.querySelector('.loading');
92
+ if (loading) loading.remove();
93
+
94
+ // Add new activity
95
+ const li = document.createElement('li');
96
+ li.className = 'activity-item';
97
+ li.innerHTML = `
98
+ <span class="activity-time">${new Date().toLocaleTimeString()}</span>
99
+ <span class="activity-message">${message}</span>
100
+ `;
101
+ activityList.insertBefore(li, activityList.firstChild);
102
+
103
+ // Keep only last 5 items
104
+ while (activityList.children.length > 5) {
105
+ activityList.removeChild(activityList.lastChild);
106
+ }
107
+ }
108
+ }
109
+
110
+ // User menu functionality
111
+ function toggleUserMenu() {
112
+ const dropdown = document.getElementById('user-dropdown');
113
+ dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
114
+ }
115
+
116
+ // Close dropdown when clicking outside
117
+ document.addEventListener('click', (e) => {
118
+ const userMenu = document.getElementById('user-menu');
119
+ const dropdown = document.getElementById('user-dropdown');
120
+ if (userMenu && !userMenu.contains(e.target)) {
121
+ dropdown.style.display = 'none';
122
+ }
123
+ });
124
+
125
+ // Sidebar functionality
126
+ function toggleSidebar() {
127
+ const sidebar = document.getElementById('sidebar');
128
+ sidebar.classList.toggle('sidebar--collapsed');
129
+ }
130
+
131
+ // Theme toggle
132
+ function toggleTheme() {
133
+ document.body.classList.toggle('dark-theme');
134
+ const isDark = document.body.classList.contains('dark-theme');
135
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
136
+ }
137
+
138
+ // Load saved theme
139
+ if (localStorage.getItem('theme') === 'dark') {
140
+ document.body.classList.add('dark-theme');
141
+ }
142
+
143
+ // Login form handling
144
+ async function handleLogin(e) {
145
+ e.preventDefault();
146
+ const formData = new FormData(e.target);
147
+ const email = formData.get('email');
148
+ const password = formData.get('password');
149
+
150
+ // Show loading state
151
+ const button = e.target.querySelector('button[type="submit"]');
152
+ const buttonText = button.querySelector('.btn__text');
153
+ const buttonLoading = button.querySelector('.btn__loading');
154
+
155
+ buttonText.style.display = 'none';
156
+ buttonLoading.style.display = 'inline';
157
+ button.disabled = true;
158
+
159
+ try {
160
+ // In a real app, you'd call your login API
161
+ // const result = await api.post('/api/auth/login', { email, password });
162
+
163
+ // For demo, simulate login
164
+ await new Promise(resolve => setTimeout(resolve, 1000));
165
+
166
+ // Redirect to dashboard
167
+ window.location.href = '/dashboard.html';
168
+
169
+ } catch (error) {
170
+ // Show error
171
+ const errorDiv = document.getElementById('auth-error');
172
+ const errorMessage = document.getElementById('error-message');
173
+ errorMessage.textContent = 'Login failed. Please try again.';
174
+ errorDiv.style.display = 'block';
175
+
176
+ // Reset button
177
+ buttonText.style.display = 'inline';
178
+ buttonLoading.style.display = 'none';
179
+ button.disabled = false;
180
+ }
181
+ }
182
+
183
+ // Profile functions
184
+ function editProfile() {
185
+ alert('Edit profile functionality would go here');
186
+ }
187
+
188
+ function viewActivity() {
189
+ alert('User activity view would go here');
190
+ }
191
+
192
+ function downloadData() {
193
+ alert('Data download functionality would go here');
194
+ }
195
+
196
+ function loadUserActivity(userId) {
197
+ // Load user-specific activity
198
+ updateActivity(`Viewing profile for user ${userId}`);
199
+ }
200
+
201
+ // Initialize based on current page
202
+ document.addEventListener('DOMContentLoaded', () => {
203
+ const currentPath = window.location.pathname;
204
+
205
+ // Update active navigation
206
+ document.querySelectorAll('.nav-link').forEach(link => {
207
+ if (link.getAttribute('href') === currentPath) {
208
+ link.classList.add('nav-link--active');
209
+ }
210
+ });
211
+
212
+ console.log('🚀 Server app initialized');
213
+ });