@zoyth/simple-site-framework 1.0.0
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/LICENSE +21 -0
- package/README.md +572 -0
- package/bin/create-simple-site.js +390 -0
- package/bin/simple-site.js +664 -0
- package/dist/client.js +135 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +107 -0
- package/dist/client.mjs.map +1 -0
- package/dist/components/index.d.mts +3936 -0
- package/dist/components/index.d.ts +3936 -0
- package/dist/components/index.js +38265 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +38173 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config/index.d.mts +298 -0
- package/dist/config/index.d.ts +298 -0
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.mjs +1 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/index.d.mts +2184 -0
- package/dist/index.d.ts +2184 -0
- package/dist/index.js +1713 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1605 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/i18n/index.js +665 -0
- package/dist/lib/i18n/index.js.map +1 -0
- package/dist/lib/i18n/index.mjs +621 -0
- package/dist/lib/i18n/index.mjs.map +1 -0
- package/docs/DOCUMENTATION-STRUCTURE.md +1156 -0
- package/docs/EXPORTS.md +125 -0
- package/docs/PERFORMANCE.md +757 -0
- package/docs/POLICY-PAGES.md +867 -0
- package/docs/ROADMAP.md +334 -0
- package/docs/SEO.md +455 -0
- package/docs/SITEMAP.md +708 -0
- package/docs/STRUCTURED-DATA.md +671 -0
- package/docs/accessibility/common-patterns.md +529 -0
- package/docs/accessibility/keyboard-navigation.md +263 -0
- package/docs/accessibility/overview.md +122 -0
- package/docs/accessibility/screen-readers.md +311 -0
- package/docs/accessibility/wcag-compliance.md +159 -0
- package/docs/api/README.md +164 -0
- package/docs/api/components/Accessibility.md +356 -0
- package/docs/api/components/Button.md +240 -0
- package/docs/api/components/HeroSection.md +306 -0
- package/docs/architecture/decisions.md +449 -0
- package/docs/components/AnalyticsTracker.md +58 -0
- package/docs/components/AnimatedCounter.md +48 -0
- package/docs/components/AnimatedSection.md +56 -0
- package/docs/components/BlogCard.md +42 -0
- package/docs/components/Checkbox.md +56 -0
- package/docs/components/CodeBlock.md +52 -0
- package/docs/components/ComparisonTable.md +40 -0
- package/docs/components/ComponentDemo.md +38 -0
- package/docs/components/CountdownTimer.md +51 -0
- package/docs/components/ExitIntentModal.md +56 -0
- package/docs/components/FAQAccordion.md +66 -0
- package/docs/components/FeaturesGrid.md +55 -0
- package/docs/components/FileUpload.md +54 -0
- package/docs/components/I18nMetaTags.md +55 -0
- package/docs/components/Icon.md +53 -0
- package/docs/components/LazySection.md +46 -0
- package/docs/components/LiveProof.md +53 -0
- package/docs/components/LoadingSpinner.md +46 -0
- package/docs/components/MultiStepForm.md +48 -0
- package/docs/components/PolicyLayout.md +55 -0
- package/docs/components/PricingTable.md +49 -0
- package/docs/components/Radio.md +59 -0
- package/docs/components/SEOMetaTags.md +58 -0
- package/docs/components/ScriptInjector.md +50 -0
- package/docs/components/Select.md +72 -0
- package/docs/components/Skeleton.md +47 -0
- package/docs/components/StatsSection.md +48 -0
- package/docs/components/StickyBar.md +62 -0
- package/docs/components/StructuredData.md +99 -0
- package/docs/components/StyleGuide.md +46 -0
- package/docs/components/TableOfContents.md +47 -0
- package/docs/components/TestimonialCarousel.md +42 -0
- package/docs/components/Timeline.md +51 -0
- package/docs/components/Toast.md +59 -0
- package/docs/components/TrackedLink.md +62 -0
- package/docs/components/TrustBadges.md +44 -0
- package/docs/components/conversion/MobileCTA.md +363 -0
- package/docs/components/forms/ContactForm.md +75 -0
- package/docs/components/forms/FormField.md +74 -0
- package/docs/components/layout/Footer.md +601 -0
- package/docs/components/layout/Header.md +549 -0
- package/docs/components/layout/LanguageSelector.md +54 -0
- package/docs/components/layout/LanguageSwitcher.md +24 -0
- package/docs/components/overview.md +447 -0
- package/docs/components/sections/AboutSection.md +48 -0
- package/docs/components/sections/CTASection.md +596 -0
- package/docs/components/sections/CaseStudySection.md +47 -0
- package/docs/components/sections/ContactSection.md +599 -0
- package/docs/components/sections/FeatureSection.md +44 -0
- package/docs/components/sections/HeroSection.md +404 -0
- package/docs/components/sections/LogosSection.md +47 -0
- package/docs/components/sections/PersonalTaxesSection.md +23 -0
- package/docs/components/sections/RecruitingSection.md +23 -0
- package/docs/components/sections/SecurePortalSection.md +23 -0
- package/docs/components/sections/ServicePageLayout.md +52 -0
- package/docs/components/sections/ServicesSection.md +49 -0
- package/docs/components/sections/TestimonialSection.md +44 -0
- package/docs/components/sections/WhyChooseUsSection.md +54 -0
- package/docs/components/ui/Breadcrumb.md +70 -0
- package/docs/components/ui/Button.md +514 -0
- package/docs/components/ui/Card.md +501 -0
- package/docs/components/ui/Input.md +54 -0
- package/docs/components/ui/MobileLinks.md +43 -0
- package/docs/components/ui/Modal.md +60 -0
- package/docs/components/ui/Tabs.md +62 -0
- package/docs/components/ui/Textarea.md +52 -0
- package/docs/core-concepts/configuration-driven.md +552 -0
- package/docs/core-concepts/overview.md +351 -0
- package/docs/features/accessibility/README.md +73 -0
- package/docs/features/accessibility/aria-support.md +177 -0
- package/docs/features/accessibility/color-contrast.md +155 -0
- package/docs/features/accessibility/focus-management.md +187 -0
- package/docs/features/accessibility/testing.md +196 -0
- package/docs/features/analytics/README.md +51 -0
- package/docs/features/analytics/ab-testing.md +171 -0
- package/docs/features/analytics/conversion-tracking.md +207 -0
- package/docs/features/analytics/custom-events.md +219 -0
- package/docs/features/analytics/privacy.md +198 -0
- package/docs/features/analytics/setup.md +114 -0
- package/docs/features/analytics/tracking-events.md +224 -0
- package/docs/features/i18n/README.md +51 -0
- package/docs/features/i18n/best-practices.md +273 -0
- package/docs/features/i18n/configuration.md +84 -0
- package/docs/features/i18n/formatting.md +133 -0
- package/docs/features/i18n/locale-detection.md +122 -0
- package/docs/features/i18n/routing.md +99 -0
- package/docs/features/i18n/rtl-support.md +191 -0
- package/docs/features/i18n/translations.md +129 -0
- package/docs/features/internationalization.md +595 -0
- package/docs/features/performance/README.md +77 -0
- package/docs/features/performance/bundle-size.md +134 -0
- package/docs/features/performance/caching.md +131 -0
- package/docs/features/performance/code-splitting.md +121 -0
- package/docs/features/performance/image-optimization.md +110 -0
- package/docs/features/performance/lazy-loading.md +92 -0
- package/docs/features/performance/monitoring.md +148 -0
- package/docs/features/seo/README.md +51 -0
- package/docs/features/seo/best-practices.md +184 -0
- package/docs/features/seo/canonical-urls.md +182 -0
- package/docs/features/seo/meta-tags.md +126 -0
- package/docs/features/seo/open-graph.md +166 -0
- package/docs/features/seo/robots-txt.md +146 -0
- package/docs/features/seo/sitemaps.md +162 -0
- package/docs/features/seo/structured-data.md +166 -0
- package/docs/getting-started/installation.md +292 -0
- package/docs/getting-started/introduction.md +195 -0
- package/docs/getting-started/quick-start.md +460 -0
- package/docs/guides/analytics-setup.md +616 -0
- package/docs/i18n/CONFIGURATION.md +353 -0
- package/docs/i18n/EXAMPLES.md +402 -0
- package/docs/i18n/MIGRATION.md +260 -0
- package/docs/i18n/SEO.md +392 -0
- package/docs/i18n/STATIC-GENERATION-FIX.md +71 -0
- package/docs/migration/changelog.md +136 -0
- package/docs/migration/overview.md +233 -0
- package/docs/recipes/adding-animations.md +475 -0
- package/docs/recipes/forms-with-validation.md +393 -0
- package/package.json +152 -0
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* simple-site CLI tool
|
|
5
|
+
* Add components, configure settings, and manage your project
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { Command } = require('commander');
|
|
9
|
+
const inquirer = require('inquirer');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const ora = require('ora');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs-extra');
|
|
14
|
+
|
|
15
|
+
const program = new Command();
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.name('simple-site')
|
|
19
|
+
.description('Simple Site Framework CLI')
|
|
20
|
+
.version('0.1.0');
|
|
21
|
+
|
|
22
|
+
// Add component command
|
|
23
|
+
program
|
|
24
|
+
.command('add <component>')
|
|
25
|
+
.description('Add a component to your project')
|
|
26
|
+
.action(async (component) => {
|
|
27
|
+
try {
|
|
28
|
+
await addComponent(component);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error(chalk.red('\n✖ Error:'), error.message);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Add section command
|
|
36
|
+
program
|
|
37
|
+
.command('section <name>')
|
|
38
|
+
.alias('add-section')
|
|
39
|
+
.description('Add a new section to your project')
|
|
40
|
+
.action(async (name) => {
|
|
41
|
+
try {
|
|
42
|
+
await addSection(name);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(chalk.red('\n✖ Error:'), error.message);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Add page command
|
|
50
|
+
program
|
|
51
|
+
.command('page <path>')
|
|
52
|
+
.alias('add-page')
|
|
53
|
+
.description('Add a new page to your project')
|
|
54
|
+
.action(async (pagePath) => {
|
|
55
|
+
try {
|
|
56
|
+
await addPage(pagePath);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(chalk.red('\n✖ Error:'), error.message);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// List components command
|
|
64
|
+
program
|
|
65
|
+
.command('list')
|
|
66
|
+
.description('List available components')
|
|
67
|
+
.action(() => {
|
|
68
|
+
listComponents();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Search components command
|
|
72
|
+
program
|
|
73
|
+
.command('search <query>')
|
|
74
|
+
.description('Search for components')
|
|
75
|
+
.action((query) => {
|
|
76
|
+
searchComponents(query);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Configuration wizard
|
|
80
|
+
program
|
|
81
|
+
.command('config')
|
|
82
|
+
.description('Interactive configuration wizard')
|
|
83
|
+
.action(async () => {
|
|
84
|
+
try {
|
|
85
|
+
await configWizard();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(chalk.red('\n✖ Error:'), error.message);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Migration command
|
|
93
|
+
program
|
|
94
|
+
.command('migrate')
|
|
95
|
+
.description('Run migration tasks (e.g., migrate i18n)')
|
|
96
|
+
.argument('[task]', 'Migration task to run (i18n)')
|
|
97
|
+
.option('--dry-run', 'Preview changes without writing files')
|
|
98
|
+
.action(async (task, options) => {
|
|
99
|
+
try {
|
|
100
|
+
if (!task || task === 'i18n') {
|
|
101
|
+
await migrateI18n(options);
|
|
102
|
+
} else {
|
|
103
|
+
console.log(chalk.yellow(`\n⚠ Unknown migration task: ${task}`));
|
|
104
|
+
console.log(chalk.gray('Available migrations: i18n\n'));
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(chalk.red('\n✖ Error:'), error.message);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
program.parse();
|
|
113
|
+
|
|
114
|
+
// Component registry
|
|
115
|
+
const COMPONENTS = {
|
|
116
|
+
// Layout
|
|
117
|
+
'hero': { category: 'Layout', name: 'HeroSection', description: 'Hero section with CTA' },
|
|
118
|
+
'features': { category: 'Layout', name: 'FeaturesGrid', description: 'Features grid display' },
|
|
119
|
+
'footer': { category: 'Layout', name: 'Footer', description: 'Site footer' },
|
|
120
|
+
'header': { category: 'Layout', name: 'Header', description: 'Site header with navigation' },
|
|
121
|
+
|
|
122
|
+
// Conversion
|
|
123
|
+
'pricing': { category: 'Conversion', name: 'PricingTable', description: 'Pricing comparison table' },
|
|
124
|
+
'testimonials': { category: 'Conversion', name: 'TestimonialCarousel', description: 'Customer testimonials carousel' },
|
|
125
|
+
'stats': { category: 'Conversion', name: 'StatsSection', description: 'Statistics with animated counters' },
|
|
126
|
+
'trust-badges': { category: 'Conversion', name: 'TrustBadges', description: 'Trust and credibility badges' },
|
|
127
|
+
'countdown': { category: 'Conversion', name: 'CountdownTimer', description: 'Countdown timer for urgency' },
|
|
128
|
+
'exit-intent': { category: 'Conversion', name: 'ExitIntentModal', description: 'Exit intent modal' },
|
|
129
|
+
|
|
130
|
+
// Content
|
|
131
|
+
'faq': { category: 'Content', name: 'FAQAccordion', description: 'FAQ accordion' },
|
|
132
|
+
'blog-card': { category: 'Content', name: 'BlogCard', description: 'Blog post preview card' },
|
|
133
|
+
'timeline': { category: 'Content', name: 'Timeline', description: 'Timeline for history/process' },
|
|
134
|
+
'comparison': { category: 'Content', name: 'ComparisonTable', description: 'Feature comparison table' },
|
|
135
|
+
'tabs': { category: 'Content', name: 'Tabs', description: 'Tabbed content with URL sync' },
|
|
136
|
+
|
|
137
|
+
// Forms
|
|
138
|
+
'contact-form': { category: 'Forms', name: 'ContactSection', description: 'Contact form section' },
|
|
139
|
+
'multi-step-form': { category: 'Forms', name: 'MultiStepForm', description: 'Multi-step form with validation' },
|
|
140
|
+
'file-upload': { category: 'Forms', name: 'FileUpload', description: 'File upload with drag & drop' },
|
|
141
|
+
'select': { category: 'Forms', name: 'Select', description: 'Dropdown select' },
|
|
142
|
+
'checkbox': { category: 'Forms', name: 'Checkbox', description: 'Checkbox input' },
|
|
143
|
+
'radio': { category: 'Forms', name: 'Radio', description: 'Radio button input' },
|
|
144
|
+
|
|
145
|
+
// UI
|
|
146
|
+
'button': { category: 'UI', name: 'Button', description: 'Button component' },
|
|
147
|
+
'modal': { category: 'UI', name: 'Modal', description: 'Modal dialog' },
|
|
148
|
+
'toast': { category: 'UI', name: 'Toast', description: 'Toast notifications' },
|
|
149
|
+
'loading': { category: 'UI', name: 'LoadingSpinner', description: 'Loading spinner' },
|
|
150
|
+
'skeleton': { category: 'UI', name: 'Skeleton', description: 'Skeleton loader' }
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
async function addComponent(componentKey) {
|
|
154
|
+
const component = COMPONENTS[componentKey.toLowerCase()];
|
|
155
|
+
|
|
156
|
+
if (!component) {
|
|
157
|
+
console.log(chalk.yellow(`\n⚠ Component "${componentKey}" not found.\n`));
|
|
158
|
+
console.log(chalk.gray('Run `simple-site list` to see available components'));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const spinner = ora(`Adding ${component.name}...`).start();
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Check if we're in a project directory
|
|
166
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
167
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
168
|
+
spinner.fail('Not in a project directory');
|
|
169
|
+
console.log(chalk.yellow('\nRun this command from your project root directory'));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Create component usage example
|
|
174
|
+
const examplePath = path.join(process.cwd(), 'examples', `${componentKey}-example.tsx`);
|
|
175
|
+
fs.ensureDirSync(path.dirname(examplePath));
|
|
176
|
+
|
|
177
|
+
const exampleCode = generateComponentExample(component.name, componentKey);
|
|
178
|
+
fs.writeFileSync(examplePath, exampleCode);
|
|
179
|
+
|
|
180
|
+
spinner.succeed(`Added ${component.name}!`);
|
|
181
|
+
console.log(chalk.green(`\n✓ Example created: examples/${componentKey}-example.tsx`));
|
|
182
|
+
console.log(chalk.gray(`\nImport with: import { ${component.name} } from '@zoyth/simple-site-framework/components'\n`));
|
|
183
|
+
|
|
184
|
+
} catch (error) {
|
|
185
|
+
spinner.fail('Failed to add component');
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function generateComponentExample(componentName, key) {
|
|
191
|
+
const examples = {
|
|
192
|
+
'HeroSection': `import { HeroSection } from '@zoyth/simple-site-framework/components'
|
|
193
|
+
|
|
194
|
+
export function HeroExample() {
|
|
195
|
+
return (
|
|
196
|
+
<HeroSection
|
|
197
|
+
title={{ en: "Welcome to Our Site", fr: "Bienvenue sur notre site" }}
|
|
198
|
+
subtitle="Build amazing websites with Simple Site Framework"
|
|
199
|
+
primaryCTA={{
|
|
200
|
+
label: "Get Started",
|
|
201
|
+
href: "/contact"
|
|
202
|
+
}}
|
|
203
|
+
locale="en"
|
|
204
|
+
/>
|
|
205
|
+
)
|
|
206
|
+
}`,
|
|
207
|
+
'PricingTable': `import { PricingTable } from '@zoyth/simple-site-framework/components'
|
|
208
|
+
|
|
209
|
+
export function PricingExample() {
|
|
210
|
+
return (
|
|
211
|
+
<PricingTable
|
|
212
|
+
tiers={[
|
|
213
|
+
{
|
|
214
|
+
name: { en: "Basic", fr: "Basique" },
|
|
215
|
+
price: { monthly: 29, annual: 290 },
|
|
216
|
+
features: [
|
|
217
|
+
{ name: "Feature 1", included: true },
|
|
218
|
+
{ name: "Feature 2", included: true },
|
|
219
|
+
{ name: "Feature 3", included: false }
|
|
220
|
+
],
|
|
221
|
+
cta: { label: "Get Started", href: "/signup" }
|
|
222
|
+
}
|
|
223
|
+
]}
|
|
224
|
+
locale="en"
|
|
225
|
+
/>
|
|
226
|
+
)
|
|
227
|
+
}`
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return examples[componentName] || `import { ${componentName} } from '@zoyth/simple-site-framework/components'
|
|
231
|
+
|
|
232
|
+
export function ${componentName}Example() {
|
|
233
|
+
return (
|
|
234
|
+
<${componentName} />
|
|
235
|
+
)
|
|
236
|
+
}`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function addSection(name) {
|
|
240
|
+
const spinner = ora(`Creating section ${name}...`).start();
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const sectionPath = path.join(process.cwd(), 'app', 'sections', `${name}.tsx`);
|
|
244
|
+
fs.ensureDirSync(path.dirname(sectionPath));
|
|
245
|
+
|
|
246
|
+
const sectionCode = `export function ${capitalize(name)}Section() {
|
|
247
|
+
return (
|
|
248
|
+
<section className="py-16">
|
|
249
|
+
<div className="container mx-auto px-4">
|
|
250
|
+
<h2 className="text-3xl font-bold mb-8">${capitalize(name)}</h2>
|
|
251
|
+
{/* Add your content here */}
|
|
252
|
+
</div>
|
|
253
|
+
</section>
|
|
254
|
+
)
|
|
255
|
+
}`;
|
|
256
|
+
|
|
257
|
+
fs.writeFileSync(sectionPath, sectionCode);
|
|
258
|
+
|
|
259
|
+
spinner.succeed(`Created ${name} section!`);
|
|
260
|
+
console.log(chalk.green(`\n✓ Section created: app/sections/${name}.tsx\n`));
|
|
261
|
+
|
|
262
|
+
} catch (error) {
|
|
263
|
+
spinner.fail('Failed to create section');
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function addPage(pagePath) {
|
|
269
|
+
const spinner = ora(`Creating page ${pagePath}...`).start();
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const fullPath = path.join(process.cwd(), 'app', pagePath, 'page.tsx');
|
|
273
|
+
fs.ensureDirSync(path.dirname(fullPath));
|
|
274
|
+
|
|
275
|
+
const pageCode = `export default function ${capitalize(path.basename(pagePath))}Page() {
|
|
276
|
+
return (
|
|
277
|
+
<main className="min-h-screen">
|
|
278
|
+
<div className="container mx-auto px-4 py-16">
|
|
279
|
+
<h1 className="text-4xl font-bold mb-8">${capitalize(path.basename(pagePath))}</h1>
|
|
280
|
+
{/* Add your content here */}
|
|
281
|
+
</div>
|
|
282
|
+
</main>
|
|
283
|
+
)
|
|
284
|
+
}`;
|
|
285
|
+
|
|
286
|
+
fs.writeFileSync(fullPath, pageCode);
|
|
287
|
+
|
|
288
|
+
spinner.succeed(`Created page!`);
|
|
289
|
+
console.log(chalk.green(`\n✓ Page created: app/${pagePath}/page.tsx\n`));
|
|
290
|
+
|
|
291
|
+
} catch (error) {
|
|
292
|
+
spinner.fail('Failed to create page');
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function listComponents() {
|
|
298
|
+
console.log(chalk.bold.cyan('\n📦 Available Components\n'));
|
|
299
|
+
|
|
300
|
+
const byCategory = {};
|
|
301
|
+
for (const [key, component] of Object.entries(COMPONENTS)) {
|
|
302
|
+
if (!byCategory[component.category]) {
|
|
303
|
+
byCategory[component.category] = [];
|
|
304
|
+
}
|
|
305
|
+
byCategory[component.category].push({ key, ...component });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
for (const [category, components] of Object.entries(byCategory)) {
|
|
309
|
+
console.log(chalk.bold(`\n${category}:`));
|
|
310
|
+
components.forEach(comp => {
|
|
311
|
+
console.log(chalk.gray(` ${comp.key.padEnd(20)} ${comp.description}`));
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
console.log(chalk.gray(`\nUse: simple-site add <component>`));
|
|
316
|
+
console.log(chalk.gray(`Example: simple-site add pricing\n`));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function searchComponents(query) {
|
|
320
|
+
const results = Object.entries(COMPONENTS).filter(([key, comp]) =>
|
|
321
|
+
key.includes(query.toLowerCase()) ||
|
|
322
|
+
comp.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
323
|
+
comp.description.toLowerCase().includes(query.toLowerCase())
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
if (results.length === 0) {
|
|
327
|
+
console.log(chalk.yellow(`\n⚠ No components found matching "${query}"\n`));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log(chalk.bold.cyan(`\n🔍 Search Results for "${query}":\n`));
|
|
332
|
+
results.forEach(([key, comp]) => {
|
|
333
|
+
console.log(chalk.bold(` ${key}`));
|
|
334
|
+
console.log(chalk.gray(` ${comp.description}`));
|
|
335
|
+
console.log(chalk.gray(` Category: ${comp.category}\n`));
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async function configWizard() {
|
|
340
|
+
console.log(chalk.bold.cyan('\n⚙️ Configuration Wizard\n'));
|
|
341
|
+
|
|
342
|
+
const configPath = path.join(process.cwd(), 'config', 'theme.ts');
|
|
343
|
+
|
|
344
|
+
if (!fs.existsSync(configPath)) {
|
|
345
|
+
console.log(chalk.yellow('⚠ No config/theme.ts found'));
|
|
346
|
+
console.log(chalk.gray('Make sure you\'re in a Simple Site project directory\n'));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const answers = await inquirer.prompt([
|
|
351
|
+
{
|
|
352
|
+
type: 'list',
|
|
353
|
+
name: 'action',
|
|
354
|
+
message: 'What would you like to configure?',
|
|
355
|
+
choices: [
|
|
356
|
+
'Update theme colors',
|
|
357
|
+
'Change fonts',
|
|
358
|
+
'Modify company info',
|
|
359
|
+
'Exit'
|
|
360
|
+
]
|
|
361
|
+
}
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
if (answers.action === 'Exit') {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (answers.action === 'Update theme colors') {
|
|
369
|
+
const colorAnswers = await inquirer.prompt([
|
|
370
|
+
{
|
|
371
|
+
type: 'input',
|
|
372
|
+
name: 'primary',
|
|
373
|
+
message: 'Primary color (hex):',
|
|
374
|
+
validate: (input) => /^#[0-9A-F]{6}$/i.test(input) || 'Please enter a valid hex color'
|
|
375
|
+
}
|
|
376
|
+
]);
|
|
377
|
+
|
|
378
|
+
const spinner = ora('Updating theme...').start();
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
let themeContent = fs.readFileSync(configPath, 'utf-8');
|
|
382
|
+
themeContent = themeContent.replace(
|
|
383
|
+
/(primary:\s*['"])#[0-9A-F]{6}/gi,
|
|
384
|
+
`$1${colorAnswers.primary}`
|
|
385
|
+
);
|
|
386
|
+
fs.writeFileSync(configPath, themeContent);
|
|
387
|
+
spinner.succeed('Theme updated!');
|
|
388
|
+
} catch (error) {
|
|
389
|
+
spinner.fail('Failed to update theme');
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
console.log(chalk.green('\n✓ Configuration complete!\n'));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function migrateI18n(options = {}) {
|
|
398
|
+
const isDryRun = options.dryRun;
|
|
399
|
+
|
|
400
|
+
console.log(chalk.bold.cyan('\n🌐 i18n Migration Tool\n'));
|
|
401
|
+
|
|
402
|
+
if (isDryRun) {
|
|
403
|
+
console.log(chalk.yellow('Running in DRY RUN mode - no files will be modified\n'));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Check if we're in a project directory
|
|
407
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
408
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
409
|
+
console.log(chalk.red('✖ Not in a project directory'));
|
|
410
|
+
console.log(chalk.gray('Run this command from your project root directory\n'));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Check if i18n config already exists
|
|
415
|
+
const i18nConfigPath = path.join(process.cwd(), 'src', 'config', 'i18n.ts');
|
|
416
|
+
if (fs.existsSync(i18nConfigPath) && !isDryRun) {
|
|
417
|
+
const { overwrite } = await inquirer.prompt([
|
|
418
|
+
{
|
|
419
|
+
type: 'confirm',
|
|
420
|
+
name: 'overwrite',
|
|
421
|
+
message: 'i18n configuration already exists. Overwrite?',
|
|
422
|
+
default: false
|
|
423
|
+
}
|
|
424
|
+
]);
|
|
425
|
+
|
|
426
|
+
if (!overwrite) {
|
|
427
|
+
console.log(chalk.yellow('\n⚠ Migration cancelled\n'));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Prompt for configuration
|
|
433
|
+
const answers = await inquirer.prompt([
|
|
434
|
+
{
|
|
435
|
+
type: 'checkbox',
|
|
436
|
+
name: 'locales',
|
|
437
|
+
message: 'Select languages to support:',
|
|
438
|
+
choices: [
|
|
439
|
+
{ name: 'English (en)', value: 'en', checked: true },
|
|
440
|
+
{ name: 'French (fr)', value: 'fr', checked: true },
|
|
441
|
+
{ name: 'Spanish (es)', value: 'es' },
|
|
442
|
+
{ name: 'German (de)', value: 'de' },
|
|
443
|
+
{ name: 'Italian (it)', value: 'it' },
|
|
444
|
+
{ name: 'Portuguese (pt)', value: 'pt' },
|
|
445
|
+
{ name: 'Japanese (ja)', value: 'ja' },
|
|
446
|
+
{ name: 'Chinese (zh)', value: 'zh' },
|
|
447
|
+
{ name: 'Arabic (ar)', value: 'ar' },
|
|
448
|
+
{ name: 'Hebrew (he)', value: 'he' }
|
|
449
|
+
],
|
|
450
|
+
validate: (input) => input.length > 0 || 'Select at least one language'
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
type: 'list',
|
|
454
|
+
name: 'defaultLocale',
|
|
455
|
+
message: 'Select default language:',
|
|
456
|
+
choices: (answers) => answers.locales
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
type: 'list',
|
|
460
|
+
name: 'localePrefix',
|
|
461
|
+
message: 'Choose URL prefix mode:',
|
|
462
|
+
choices: [
|
|
463
|
+
{
|
|
464
|
+
name: 'as-needed (default locale has no prefix) - Recommended',
|
|
465
|
+
value: 'as-needed'
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: 'always (all locales have prefix)',
|
|
469
|
+
value: 'always'
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: 'never (no prefixes, cookie/header only)',
|
|
473
|
+
value: 'never'
|
|
474
|
+
}
|
|
475
|
+
],
|
|
476
|
+
default: 'as-needed'
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
type: 'confirm',
|
|
480
|
+
name: 'localeDetection',
|
|
481
|
+
message: 'Enable automatic browser language detection?',
|
|
482
|
+
default: true
|
|
483
|
+
}
|
|
484
|
+
]);
|
|
485
|
+
|
|
486
|
+
// Check if RTL locales were selected
|
|
487
|
+
const rtlLocales = answers.locales.filter(locale => ['ar', 'he', 'fa', 'ur'].includes(locale));
|
|
488
|
+
|
|
489
|
+
const spinner = ora('Creating i18n configuration...').start();
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
const files = [];
|
|
493
|
+
|
|
494
|
+
// 1. Create i18n configuration file
|
|
495
|
+
const i18nConfig = generateI18nConfig(answers, rtlLocales);
|
|
496
|
+
if (!isDryRun) {
|
|
497
|
+
fs.ensureDirSync(path.dirname(i18nConfigPath));
|
|
498
|
+
fs.writeFileSync(i18nConfigPath, i18nConfig);
|
|
499
|
+
}
|
|
500
|
+
files.push({ path: 'src/config/i18n.ts', status: 'created' });
|
|
501
|
+
|
|
502
|
+
// 2. Create middleware file
|
|
503
|
+
const middlewarePath = path.join(process.cwd(), 'src', 'middleware.ts');
|
|
504
|
+
const middlewareExists = fs.existsSync(middlewarePath);
|
|
505
|
+
|
|
506
|
+
if (middlewareExists) {
|
|
507
|
+
files.push({ path: 'src/middleware.ts', status: 'already exists - please update manually' });
|
|
508
|
+
} else {
|
|
509
|
+
const middlewareCode = generateMiddleware();
|
|
510
|
+
if (!isDryRun) {
|
|
511
|
+
fs.writeFileSync(middlewarePath, middlewareCode);
|
|
512
|
+
}
|
|
513
|
+
files.push({ path: 'src/middleware.ts', status: 'created' });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// 3. Check layout file
|
|
517
|
+
const layoutPath = path.join(process.cwd(), 'src', 'app', '[locale]', 'layout.tsx');
|
|
518
|
+
if (fs.existsSync(layoutPath)) {
|
|
519
|
+
files.push({
|
|
520
|
+
path: 'src/app/[locale]/layout.tsx',
|
|
521
|
+
status: 'exists - add setI18nConfig() call manually'
|
|
522
|
+
});
|
|
523
|
+
} else {
|
|
524
|
+
files.push({
|
|
525
|
+
path: 'src/app/[locale]/layout.tsx',
|
|
526
|
+
status: 'not found - create [locale] route directory'
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
spinner.succeed('i18n configuration created!');
|
|
531
|
+
|
|
532
|
+
// Show summary
|
|
533
|
+
console.log(chalk.bold.green('\n✓ Migration complete!\n'));
|
|
534
|
+
console.log(chalk.bold('Files:\n'));
|
|
535
|
+
files.forEach(file => {
|
|
536
|
+
const icon = file.status.includes('created') ? '✓' :
|
|
537
|
+
file.status.includes('exists') ? '●' : '○';
|
|
538
|
+
const color = file.status.includes('created') ? chalk.green :
|
|
539
|
+
file.status.includes('exists') ? chalk.yellow :
|
|
540
|
+
chalk.gray;
|
|
541
|
+
console.log(color(` ${icon} ${file.path}`));
|
|
542
|
+
if (!file.status.includes('created')) {
|
|
543
|
+
console.log(chalk.gray(` ${file.status}`));
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Show next steps
|
|
548
|
+
console.log(chalk.bold('\nNext steps:\n'));
|
|
549
|
+
console.log(chalk.gray('1. Review generated files in src/config/i18n.ts'));
|
|
550
|
+
|
|
551
|
+
if (middlewareExists) {
|
|
552
|
+
console.log(chalk.gray('2. Update src/middleware.ts with:'));
|
|
553
|
+
console.log(chalk.gray(' import { createI18nMiddleware } from \'simple-site-framework/lib/i18n\';'));
|
|
554
|
+
console.log(chalk.gray(' import { i18nConfig } from \'./src/config/i18n\';'));
|
|
555
|
+
console.log(chalk.gray(' export default createI18nMiddleware(i18nConfig);'));
|
|
556
|
+
} else {
|
|
557
|
+
console.log(chalk.gray('2. Middleware created automatically ✓'));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
console.log(chalk.gray('3. Add to src/app/[locale]/layout.tsx:'));
|
|
561
|
+
console.log(chalk.gray(' import { setI18nConfig } from \'simple-site-framework/lib/i18n\';'));
|
|
562
|
+
console.log(chalk.gray(' import { i18nConfig } from \'@/config/i18n\';'));
|
|
563
|
+
console.log(chalk.gray(' setI18nConfig(i18nConfig); // At top level'));
|
|
564
|
+
console.log(chalk.gray('4. See docs/i18n/MIGRATION.md for complete migration guide'));
|
|
565
|
+
console.log('');
|
|
566
|
+
|
|
567
|
+
} catch (error) {
|
|
568
|
+
spinner.fail('Migration failed');
|
|
569
|
+
throw error;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function generateI18nConfig(answers, rtlLocales) {
|
|
574
|
+
const { locales, defaultLocale, localePrefix, localeDetection } = answers;
|
|
575
|
+
|
|
576
|
+
// Generate locale names
|
|
577
|
+
const localeNameMap = {
|
|
578
|
+
en: 'English',
|
|
579
|
+
fr: 'Français',
|
|
580
|
+
es: 'Español',
|
|
581
|
+
de: 'Deutsch',
|
|
582
|
+
it: 'Italiano',
|
|
583
|
+
pt: 'Português',
|
|
584
|
+
ja: '日本語',
|
|
585
|
+
zh: '中文',
|
|
586
|
+
ar: 'العربية',
|
|
587
|
+
he: 'עברית'
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
const localeNames = locales.reduce((acc, locale) => {
|
|
591
|
+
acc[locale] = localeNameMap[locale] || locale.toUpperCase();
|
|
592
|
+
return acc;
|
|
593
|
+
}, {});
|
|
594
|
+
|
|
595
|
+
const localeLabels = locales.reduce((acc, locale) => {
|
|
596
|
+
acc[locale] = locale.toUpperCase();
|
|
597
|
+
return acc;
|
|
598
|
+
}, {});
|
|
599
|
+
|
|
600
|
+
const localesArray = JSON.stringify(locales, null, 2).replace(/"/g, "'");
|
|
601
|
+
const localeNamesStr = JSON.stringify(localeNames, null, 4).replace(/"/g, "'");
|
|
602
|
+
const localeLabelsStr = JSON.stringify(localeLabels, null, 4).replace(/"/g, "'");
|
|
603
|
+
|
|
604
|
+
let configContent = `// ABOUTME: i18n configuration
|
|
605
|
+
// ABOUTME: Internationalization settings for the application
|
|
606
|
+
|
|
607
|
+
import type { I18nConfig } from 'simple-site-framework/lib/i18n';
|
|
608
|
+
|
|
609
|
+
export const i18nConfig: I18nConfig = {
|
|
610
|
+
// Supported languages
|
|
611
|
+
locales: ${localesArray},
|
|
612
|
+
|
|
613
|
+
// Default language
|
|
614
|
+
defaultLocale: '${defaultLocale}',
|
|
615
|
+
|
|
616
|
+
// Locale prefix mode:
|
|
617
|
+
// 'as-needed' - default locale has no prefix, others do (recommended)
|
|
618
|
+
// 'always' - all URLs have locale prefix
|
|
619
|
+
// 'never' - no locale prefixes (cookie/header detection only)
|
|
620
|
+
localePrefix: '${localePrefix}',
|
|
621
|
+
|
|
622
|
+
// Enable browser language detection
|
|
623
|
+
localeDetection: ${localeDetection},
|
|
624
|
+
|
|
625
|
+
// Display names for language selector
|
|
626
|
+
localeNames: ${localeNamesStr},
|
|
627
|
+
|
|
628
|
+
// Short labels for compact display
|
|
629
|
+
localeLabels: ${localeLabelsStr},`;
|
|
630
|
+
|
|
631
|
+
if (rtlLocales.length > 0) {
|
|
632
|
+
const rtlArray = JSON.stringify(rtlLocales, null, 2).replace(/"/g, "'");
|
|
633
|
+
configContent += `
|
|
634
|
+
|
|
635
|
+
// Right-to-left languages
|
|
636
|
+
rtlLocales: ${rtlArray},`;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
configContent += `
|
|
640
|
+
};
|
|
641
|
+
`;
|
|
642
|
+
|
|
643
|
+
return configContent;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function generateMiddleware() {
|
|
647
|
+
return `// ABOUTME: Middleware configuration
|
|
648
|
+
// ABOUTME: Handles i18n routing and locale detection
|
|
649
|
+
|
|
650
|
+
import { createI18nMiddleware } from 'simple-site-framework/lib/i18n';
|
|
651
|
+
import { i18nConfig } from './src/config/i18n';
|
|
652
|
+
|
|
653
|
+
export default createI18nMiddleware(i18nConfig);
|
|
654
|
+
|
|
655
|
+
export const config = {
|
|
656
|
+
// Exclude API routes, static assets, etc.
|
|
657
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
|
658
|
+
};
|
|
659
|
+
`;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function capitalize(str) {
|
|
663
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
664
|
+
}
|