@stellisoft/stellify-mcp 0.1.34 → 0.1.35
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/README.md +42 -0
- package/dist/index.js +74 -17
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/wordpress-import.md +292 -0
package/README.md
CHANGED
|
@@ -352,6 +352,20 @@ Search for routes/pages in the project by name.
|
|
|
352
352
|
|
|
353
353
|
---
|
|
354
354
|
|
|
355
|
+
### Views & Blade Templates
|
|
356
|
+
|
|
357
|
+
Stellify stores Blade views as elements instead of files. The root element's `name` field maps to the view name:
|
|
358
|
+
|
|
359
|
+
- Element with `name="notes.index"` → `view('notes.index', $data)`
|
|
360
|
+
- Element with `name="layouts.app"` → `@extends('layouts.app')`
|
|
361
|
+
- Element with `name="components.card"` → `<x-card>`
|
|
362
|
+
|
|
363
|
+
Use `update_element` to set the `name` on a root element after creating it with `html_to_elements`.
|
|
364
|
+
|
|
365
|
+
**Convention for reusable templates:** Attach layouts, components, and partials to a template route (e.g., `/template/app-layout`, `/template/card`) to keep them organized and editable.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
355
369
|
### Element Tools (UI Components)
|
|
356
370
|
|
|
357
371
|
#### `create_element`
|
|
@@ -441,8 +455,36 @@ Convert HTML to Stellify elements in ONE operation. This is the fastest way to b
|
|
|
441
455
|
- `elements` (required): HTML string to convert
|
|
442
456
|
- `page` (optional): Route UUID to attach elements to. Omit for Vue components.
|
|
443
457
|
- `selection` (optional): Parent element UUID to attach to (alternative to page)
|
|
458
|
+
- `file` (optional): Vue component file UUID. Pass this to auto-wire @click handlers to method UUIDs.
|
|
444
459
|
- `test` (optional): If true, returns structure without creating elements
|
|
445
460
|
|
|
461
|
+
**⚠️ CRITICAL: Multiple Root Elements**
|
|
462
|
+
|
|
463
|
+
When passing HTML with **multiple root-level elements** (e.g., `<header>`, `<main>`, `<footer>`), only the **FIRST root element** gets attached to the route via `routeParent`. Other elements are created but become **orphaned** (not attached to the route).
|
|
464
|
+
|
|
465
|
+
**Wrong approach (causes orphaned elements):**
|
|
466
|
+
```
|
|
467
|
+
html_to_elements(page: routeUUID, elements: "<header>...</header><main>...</main><footer>...</footer>")
|
|
468
|
+
// Result: Only <header> is attached to the route. <main> and <footer> are orphaned!
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Correct approach (make separate calls for each root element):**
|
|
472
|
+
```
|
|
473
|
+
// Call 1: Header
|
|
474
|
+
html_to_elements(page: routeUUID, elements: "<header>...</header>")
|
|
475
|
+
|
|
476
|
+
// Call 2: Main content
|
|
477
|
+
html_to_elements(page: routeUUID, elements: "<main>...</main>")
|
|
478
|
+
|
|
479
|
+
// Call 3: Footer
|
|
480
|
+
html_to_elements(page: routeUUID, elements: "<footer>...</footer>")
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
**Alternative approach (wrap in a single container):**
|
|
484
|
+
```
|
|
485
|
+
html_to_elements(page: routeUUID, elements: "<div class='min-h-screen flex flex-col'><header>...</header><main>...</main><footer>...</footer></div>")
|
|
486
|
+
```
|
|
487
|
+
|
|
446
488
|
**Features:**
|
|
447
489
|
- Parses HTML structure
|
|
448
490
|
- Creates all elements with proper nesting
|
package/dist/index.js
CHANGED
|
@@ -596,7 +596,7 @@ Use the returned UUID with html_to_elements (page parameter) or get_route for fu
|
|
|
596
596
|
},
|
|
597
597
|
{
|
|
598
598
|
name: 'update_element',
|
|
599
|
-
description: `Update a UI element. Data object: tag, classes, text, event handlers (method UUIDs), classBindings.
|
|
599
|
+
description: `Update a UI element. Data object: tag, classes, text, event handlers (method UUIDs), classBindings. Set 'name' on root elements to create Blade views (e.g., name="notes.index" for view('notes.index')).`,
|
|
600
600
|
inputSchema: {
|
|
601
601
|
type: 'object',
|
|
602
602
|
properties: {
|
|
@@ -685,12 +685,46 @@ Note: To reorder elements, use update_element to modify the parent element's 'da
|
|
|
685
685
|
},
|
|
686
686
|
{
|
|
687
687
|
name: 'html_to_elements',
|
|
688
|
-
description: `Convert HTML to Stellify elements.
|
|
688
|
+
description: `Convert HTML to Stellify elements.
|
|
689
689
|
|
|
690
|
-
|
|
690
|
+
**IMPORTANT - Choose the right approach:**
|
|
691
|
+
|
|
692
|
+
**For SSR/Blade Pages (WordPress imports, static content, layouts):**
|
|
693
|
+
- MUST pass 'page' (route UUID) - elements attach to the route for server-side rendering
|
|
694
|
+
- This is the most common use case
|
|
695
|
+
|
|
696
|
+
**For Vue Components (client-side interactivity):**
|
|
697
|
+
- Omit 'page' - elements are standalone, referenced by file's template array
|
|
698
|
+
- Returns UUIDs to use in save_file's template array
|
|
699
|
+
|
|
700
|
+
**Where elements go:**
|
|
701
|
+
- Pass 'page' (route UUID): Elements attached to the route for SSR rendering
|
|
702
|
+
- Pass 'selection' (element UUID): Elements attached as children of existing element
|
|
703
|
+
- Omit both: Elements are standalone (Vue components only) - use returned UUIDs in save_file's template array
|
|
704
|
+
|
|
705
|
+
**⚠️ CRITICAL: Multiple Root Elements Limitation**
|
|
706
|
+
When HTML contains multiple root-level elements (e.g., <header>, <main>, <footer>), only the FIRST root element gets attached to the route. Other elements become orphaned!
|
|
707
|
+
|
|
708
|
+
**WRONG:** \`html_to_elements(page: routeUUID, elements: "<header>...</header><main>...</main><footer>...</footer>")\`
|
|
709
|
+
→ Only <header> attaches to route. <main> and <footer> are orphaned!
|
|
710
|
+
|
|
711
|
+
**CORRECT:** Make separate calls for each root element:
|
|
712
|
+
1. \`html_to_elements(page: routeUUID, elements: "<header>...</header>")\`
|
|
713
|
+
2. \`html_to_elements(page: routeUUID, elements: "<main>...</main>")\`
|
|
714
|
+
3. \`html_to_elements(page: routeUUID, elements: "<footer>...</footer>")\`
|
|
715
|
+
|
|
716
|
+
**OR** wrap all elements in a single container div.
|
|
691
717
|
|
|
692
718
|
**@click auto-wiring:** Pass 'file' UUID to auto-resolve @click="methodName" handlers. Methods must exist in the file first.
|
|
693
719
|
|
|
720
|
+
**Blade Syntax Handling:**
|
|
721
|
+
When element text or attributes contain Blade syntax like \`{{ $post->title }}\` or \`{!! $post->content !!}\`, do NOT store them as literal text. Instead:
|
|
722
|
+
- Parse the Blade expression and create a statement/clause for it
|
|
723
|
+
- Store the statement UUID in the element's \`statements\` array
|
|
724
|
+
- The SSR assembler will evaluate these at render time
|
|
725
|
+
|
|
726
|
+
For example, \`{{ $post->title }}\` should become a statement that references the \`$post\` variable and accesses its \`title\` property.
|
|
727
|
+
|
|
694
728
|
Prefer SVG icons over emoji (encoding issues).`,
|
|
695
729
|
inputSchema: {
|
|
696
730
|
type: 'object',
|
|
@@ -701,11 +735,11 @@ Prefer SVG icons over emoji (encoding issues).`,
|
|
|
701
735
|
},
|
|
702
736
|
page: {
|
|
703
737
|
type: 'string',
|
|
704
|
-
description: 'Route UUID to attach elements to.
|
|
738
|
+
description: 'Route UUID to attach elements to. REQUIRED for SSR/Blade pages (WordPress imports, static content, layouts). Only omit for Vue component templates.',
|
|
705
739
|
},
|
|
706
740
|
selection: {
|
|
707
741
|
type: 'string',
|
|
708
|
-
description: 'Parent element UUID to attach to (alternative to page)',
|
|
742
|
+
description: 'Parent element UUID to attach to (alternative to page). Use when adding children to existing elements.',
|
|
709
743
|
},
|
|
710
744
|
file: {
|
|
711
745
|
type: 'string',
|
|
@@ -1154,7 +1188,7 @@ Changes are EPHEMERAL (not saved). For persistent changes, use update_element or
|
|
|
1154
1188
|
},
|
|
1155
1189
|
api: {
|
|
1156
1190
|
type: 'boolean',
|
|
1157
|
-
description: 'Generate API-style responses (default: true)',
|
|
1191
|
+
description: 'Generate API-style responses (default: true). Set to FALSE for SSR/Blade pages (controllers return views). Set to TRUE for API endpoints (controllers return JSON).',
|
|
1158
1192
|
},
|
|
1159
1193
|
},
|
|
1160
1194
|
required: ['name'],
|
|
@@ -1405,22 +1439,45 @@ const SERVER_INSTRUCTIONS = `Stellify is a coding platform where code is stored
|
|
|
1405
1439
|
|
|
1406
1440
|
- Files are stored as json. The json has a data key that references its methods (using uuids), and each method has a data key that references statements, and each statement has a data key that references clauses.
|
|
1407
1441
|
|
|
1408
|
-
##
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1442
|
+
## Choosing Between SSR Pages and Vue Components
|
|
1443
|
+
|
|
1444
|
+
**Use SSR/Blade Pages (routes + elements) when:**
|
|
1445
|
+
- Building static pages, layouts, or content pages
|
|
1446
|
+
- Importing from WordPress, HTML templates, or static sites
|
|
1447
|
+
- No client-side interactivity needed beyond links/forms
|
|
1448
|
+
- SEO is important
|
|
1449
|
+
|
|
1450
|
+
**Use Vue Components (files with type='js', extension='vue') when:**
|
|
1451
|
+
- Client-side interactivity is required (counters, toggles, dynamic forms)
|
|
1452
|
+
- Real-time updates needed
|
|
1453
|
+
- Complex state management
|
|
1454
|
+
|
|
1455
|
+
**CRITICAL DIFFERENCE:**
|
|
1456
|
+
- SSR Pages: Elements attach directly to routes. Use \`html_to_elements\` with \`page\` parameter (route UUID). Elements render via Blade.
|
|
1457
|
+
- Vue Components: Elements are standalone, referenced by file's \`template\` array. Use \`html_to_elements\` WITHOUT \`page\` parameter.
|
|
1458
|
+
|
|
1459
|
+
## Workflow for SSR Pages (most common)
|
|
1460
|
+
1. Research: Call get_project to understand current structure
|
|
1461
|
+
2. Create route: \`create_route\` for each page
|
|
1462
|
+
3. Add elements: \`html_to_elements\` with \`page\` parameter set to route UUID
|
|
1463
|
+
4. Wire controller (if needed): \`save_route\` with controller and controller_method UUIDs
|
|
1464
|
+
|
|
1465
|
+
## Workflow for Vue Components (client-side interactivity)
|
|
1466
|
+
1. Research: Call get_project to understand current structure
|
|
1467
|
+
2. **Call \`get_stellify_framework_api\`** to check for composables before writing custom code
|
|
1468
|
+
3. Create file: \`create_file\` with type='js', extension='vue'
|
|
1469
|
+
4. Create methods: \`create_method\` (with body parameter)
|
|
1470
|
+
5. Create state: \`create_statement_with_code\` for refs/reactive
|
|
1471
|
+
6. Create template: \`html_to_elements\` WITHOUT \`page\` parameter (returns UUIDs)
|
|
1472
|
+
7. **Handle mount file**: Check \`appJs\` in create_file response. If \`action_required\` exists, ask user about mount file
|
|
1473
|
+
8. Finalize: \`save_file\` with template/data/statements arrays (include frameworkImports for composables)
|
|
1474
|
+
9. Verify: Call \`get_assembled_code\` to check the output
|
|
1418
1475
|
`;
|
|
1419
1476
|
// Legacy detailed instructions preserved as comments for reference if needed
|
|
1420
1477
|
// Create MCP server
|
|
1421
1478
|
const server = new Server({
|
|
1422
1479
|
name: 'stellify-mcp',
|
|
1423
|
-
version: '0.1.
|
|
1480
|
+
version: '0.1.35',
|
|
1424
1481
|
}, {
|
|
1425
1482
|
capabilities: {
|
|
1426
1483
|
tools: {},
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/Stellify-Software-Ltd/stellify-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.35",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@stellisoft/stellify-mcp",
|
|
14
|
-
"version": "0.1.
|
|
14
|
+
"version": "0.1.35",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wordpress-import
|
|
3
|
+
description: "Use this skill when importing WordPress sites into Stellify. Invoke with /wordpress-import to start the guided workflow. Covers: analyzing WordPress exports, converting post types to Eloquent models, transforming PHP templates to Vue components, migrating forms and plugins, and creating routes and API endpoints."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: stellify
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# WordPress Import Skill
|
|
10
|
+
|
|
11
|
+
You are importing a WordPress site into Stellify by reading its PHP theme files and recreating the site as a structured Laravel application using the Stellify MCP tools.
|
|
12
|
+
|
|
13
|
+
**Match the rendering approach of the WordPress site.** If content is server-rendered in WordPress (which most of it will be — posts, pages, archives, navigation), create Blade templates. If a feature uses JavaScript for client-side interactivity (AJAX forms, dynamic filtering, live search, infinite scroll, modals, sliders), create a Vue component for that specific piece. The default is Blade — only reach for Vue when the WordPress source is doing something that genuinely requires JS.
|
|
14
|
+
|
|
15
|
+
## Step 0 — Discover Tools
|
|
16
|
+
|
|
17
|
+
List all available Stellify MCP tools. Understand what each tool does, what parameters it requires, and any dependencies between them (e.g. you need a file before you can add methods to it, you need a method before you can add statements). Print a summary of the tools and their creation order.
|
|
18
|
+
|
|
19
|
+
## Step 1 — Identify the Active Theme
|
|
20
|
+
|
|
21
|
+
Look in `./wp-content/themes/` to find the active theme. If there are multiple themes, check for the most recently modified one, or the one with the most template files. If unsure, ask which theme to import.
|
|
22
|
+
|
|
23
|
+
Before proceeding, ask the user:
|
|
24
|
+
|
|
25
|
+
> **How do you want to handle the database?**
|
|
26
|
+
>
|
|
27
|
+
> **A) Connect to existing WordPress database** — Models map directly to the WordPress tables (`wp_posts`, `wp_users`, etc.) using the existing column names. No migrations, no data copying. Your Laravel app reads and writes to the same database WordPress uses. This is the fastest path.
|
|
28
|
+
>
|
|
29
|
+
> **B) Fresh database** — Create new Laravel migrations with clean column names (`title` instead of `post_title`, etc.). You start with an empty database and can optionally migrate content from WordPress later.
|
|
30
|
+
|
|
31
|
+
These are the only questions you may ask. Do not ask anything else.
|
|
32
|
+
|
|
33
|
+
## Step 2 — Inventory the Theme
|
|
34
|
+
|
|
35
|
+
Read the full theme directory. Categorise every file:
|
|
36
|
+
|
|
37
|
+
- **Core templates**: index.php, single.php, page.php, archive.php, search.php, 404.php, front-page.php, home.php
|
|
38
|
+
- **Structural partials**: header.php, footer.php, sidebar.php
|
|
39
|
+
- **Template parts**: anything in template-parts/ or patterns/
|
|
40
|
+
- **Functions**: functions.php (and any files it includes/requires)
|
|
41
|
+
- **Styles**: style.css, any CSS/SCSS files
|
|
42
|
+
- **Config**: theme.json if present (block theme configuration)
|
|
43
|
+
|
|
44
|
+
Print the inventory before proceeding.
|
|
45
|
+
|
|
46
|
+
## Step 3 — Analyse functions.php
|
|
47
|
+
|
|
48
|
+
Read functions.php thoroughly. Extract:
|
|
49
|
+
|
|
50
|
+
- **Custom post types** registered via `register_post_type()` → these become Laravel models
|
|
51
|
+
- **Taxonomies** registered via `register_taxonomy()` → these become models or enums
|
|
52
|
+
- **Navigation menus** registered via `register_nav_menus()` → these define nav structure
|
|
53
|
+
- **Widget areas** registered via `register_sidebar()` → note for layout
|
|
54
|
+
- **Enqueued scripts/styles** via `wp_enqueue_script/style()` → note any JS dependencies
|
|
55
|
+
- **Custom image sizes** via `add_image_size()` → note for media handling
|
|
56
|
+
- **Theme supports** via `add_theme_support()` → note post formats, thumbnails, etc.
|
|
57
|
+
- **Shortcodes** via `add_shortcode()` → these become Blade includes or components
|
|
58
|
+
- **AJAX handlers** via `wp_ajax_*` → these become API routes
|
|
59
|
+
- **Any included files** → read those too
|
|
60
|
+
|
|
61
|
+
## Step 4 — Plan the Laravel Structure
|
|
62
|
+
|
|
63
|
+
Before calling any Stellify tools, write out the full plan:
|
|
64
|
+
|
|
65
|
+
### Models
|
|
66
|
+
- Post (always — WordPress core)
|
|
67
|
+
- Page (always — WordPress core)
|
|
68
|
+
- Category, Tag (if used)
|
|
69
|
+
- Any custom post types found in Step 3
|
|
70
|
+
- User (if the theme has author pages)
|
|
71
|
+
|
|
72
|
+
### Controllers
|
|
73
|
+
- PostController (index, show) — handles blog listing and single posts
|
|
74
|
+
- PageController (show) — handles static pages
|
|
75
|
+
- One controller per custom post type
|
|
76
|
+
- HomeController — if front-page.php or a static homepage exists
|
|
77
|
+
- SearchController — if search.php exists
|
|
78
|
+
|
|
79
|
+
### Routes (web.php)
|
|
80
|
+
Map the WordPress URL structure:
|
|
81
|
+
- `/` → HomeController or PostController@index
|
|
82
|
+
- `/blog` or `/posts` → PostController@index (if static homepage)
|
|
83
|
+
- `/{post-slug}` → PostController@show
|
|
84
|
+
- `/{page-slug}` → PageController@show
|
|
85
|
+
- `/category/{slug}` → PostController@index (filtered)
|
|
86
|
+
- `/tag/{slug}` → PostController@index (filtered)
|
|
87
|
+
- `/search` → SearchController@index
|
|
88
|
+
- Custom post type archives and singles
|
|
89
|
+
|
|
90
|
+
### Blade Views
|
|
91
|
+
Everything is Blade. The directory structure should be:
|
|
92
|
+
```
|
|
93
|
+
layouts/
|
|
94
|
+
app.blade.php ← from header.php + footer.php
|
|
95
|
+
posts/
|
|
96
|
+
index.blade.php ← from archive.php or index.php
|
|
97
|
+
show.blade.php ← from single.php
|
|
98
|
+
pages/
|
|
99
|
+
show.blade.php ← from page.php
|
|
100
|
+
partials/
|
|
101
|
+
nav.blade.php ← from wp_nav_menu output
|
|
102
|
+
sidebar.blade.php ← from sidebar.php
|
|
103
|
+
post-card.blade.php ← from template-parts/content.php
|
|
104
|
+
comments.blade.php ← from comments.php
|
|
105
|
+
search-form.blade.php ← from searchform.php
|
|
106
|
+
errors/
|
|
107
|
+
404.blade.php ← from 404.php
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Migrations (Mode B only)
|
|
111
|
+
If the user chose Mode B (fresh database), plan one migration per model with fields derived from:
|
|
112
|
+
- WordPress core fields: title, slug, content, excerpt, featured_image, status, published_at
|
|
113
|
+
- Custom fields from `get_post_meta()` calls found in templates
|
|
114
|
+
- Any ACF or custom meta boxes registered in functions.php
|
|
115
|
+
|
|
116
|
+
If the user chose Mode A (existing database), skip migrations entirely — the tables already exist.
|
|
117
|
+
|
|
118
|
+
## Step 5 — Build in Stellify (Order of Operations)
|
|
119
|
+
|
|
120
|
+
Execute the plan using Stellify MCP tools in this order. This order matters — parent records must exist before children.
|
|
121
|
+
|
|
122
|
+
### 5a. Create Models, Migrations & Controllers
|
|
123
|
+
|
|
124
|
+
Use the `create_resources` tool with `api: false` to generate models and controllers together. The `api: false` flag ensures controller methods return data arrays (for Blade views) rather than JSON responses.
|
|
125
|
+
|
|
126
|
+
**If Mode A (connect to existing WordPress database):**
|
|
127
|
+
|
|
128
|
+
Create resources that map to the existing WordPress tables. No migrations. Set the model's table and primary key to match WordPress.
|
|
129
|
+
|
|
130
|
+
```php
|
|
131
|
+
// Example Post model
|
|
132
|
+
class Post extends Model {
|
|
133
|
+
protected $table = 'wp_posts';
|
|
134
|
+
protected $primaryKey = 'ID';
|
|
135
|
+
|
|
136
|
+
// Scope to only published posts (not revisions, drafts, etc.)
|
|
137
|
+
public function scopePublished($query) {
|
|
138
|
+
return $query->where('post_status', 'publish')
|
|
139
|
+
->where('post_type', 'post');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Key WordPress table mappings:
|
|
145
|
+
- Posts & Pages → `wp_posts` (distinguished by `post_type` column: 'post', 'page', or custom types)
|
|
146
|
+
- Categories & Tags → `wp_terms` + `wp_term_taxonomy` + `wp_term_relationships`
|
|
147
|
+
- Users → `wp_users`
|
|
148
|
+
- Post meta / custom fields → `wp_postmeta`
|
|
149
|
+
- Comments → `wp_comments`
|
|
150
|
+
- Navigation menus → `wp_posts` with `post_type = 'nav_menu_item'` + `wp_terms` with taxonomy `nav_menu`
|
|
151
|
+
|
|
152
|
+
In Blade templates, use WordPress column names:
|
|
153
|
+
- `$post->post_title` (not `$post->title`)
|
|
154
|
+
- `$post->post_content` (not `$post->content`)
|
|
155
|
+
- `$post->post_excerpt` (not `$post->excerpt`)
|
|
156
|
+
- `$post->post_name` (this is the slug)
|
|
157
|
+
- `$post->post_date` (not `$post->published_at`)
|
|
158
|
+
- `$post->post_status` (not `$post->status`)
|
|
159
|
+
|
|
160
|
+
**If Mode B (fresh database):**
|
|
161
|
+
|
|
162
|
+
Create new resources with clean Laravel migrations for each model identified in Step 4. Use clean column names (`title`, `slug`, `content`, `excerpt`, `status`, `published_at`, etc.). After the import is complete, suggest a data migration SQL query or artisan command that maps data from the WordPress tables to the new schema.
|
|
163
|
+
|
|
164
|
+
### 5b. Refine Controller Methods
|
|
165
|
+
|
|
166
|
+
`create_resources` (Step 5a) scaffolds controllers automatically. Review and refine the generated methods to ensure they query the right data. Controller methods in Stellify return data arrays — Stellify automatically merges these into the Blade view context. For example:
|
|
167
|
+
|
|
168
|
+
```php
|
|
169
|
+
// CORRECT for Stellify — returns a data array, Stellify handles the view binding
|
|
170
|
+
public function index(): array
|
|
171
|
+
{
|
|
172
|
+
return ['posts' => Post::where('status', 'published')->latest('published_at')->paginate(10)];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public function show(Post $post): array
|
|
176
|
+
{
|
|
177
|
+
return ['post' => $post];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// WRONG — do not return JSON
|
|
181
|
+
public function index()
|
|
182
|
+
{
|
|
183
|
+
return Post::where('status', 'published')->get();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// WRONG — do not call view() directly, Stellify handles this
|
|
187
|
+
public function index()
|
|
188
|
+
{
|
|
189
|
+
return view('posts.index', compact('posts'));
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Mode A note:** WordPress stores posts, pages, and custom post types all in `wp_posts`, distinguished by the `post_type` column. Controllers must scope queries accordingly — e.g. `Post::where('post_type', 'post')->where('post_status', 'publish')`. It also stores revisions and auto-drafts in the same table, so always filter by `post_status`.
|
|
194
|
+
|
|
195
|
+
### 5c. Create Routes
|
|
196
|
+
Create route entries mapping URLs to controller methods.
|
|
197
|
+
|
|
198
|
+
### 5d. Create the Layout (layouts/app.blade.php)
|
|
199
|
+
Read header.php and footer.php. Create a single Blade layout that combines:
|
|
200
|
+
- The HTML structure from header.php (doctype, head, opening body, nav)
|
|
201
|
+
- `@yield('content')` for page content
|
|
202
|
+
- The structure from footer.php (closing elements, footer content)
|
|
203
|
+
|
|
204
|
+
Translate WordPress functions to Blade:
|
|
205
|
+
- `wp_head()` → `@vite(['resources/css/app.css', 'resources/js/app.js'])` and `<meta>` tags
|
|
206
|
+
- `wp_footer()` → nothing needed (Vite handles it)
|
|
207
|
+
- `wp_nav_menu()` → `@include('partials.nav')`
|
|
208
|
+
- `bloginfo('name')` → `{{ config('app.name') }}`
|
|
209
|
+
- `bloginfo('description')` → `{{ config('app.description', '') }}`
|
|
210
|
+
- `body_class()` → appropriate Tailwind classes
|
|
211
|
+
- `language_attributes()` → `lang="{{ str_replace('_', '-', app()->getLocale()) }}"`
|
|
212
|
+
|
|
213
|
+
### 5e. Create Blade Views
|
|
214
|
+
For each WordPress template file, read the PHP and create the equivalent Blade view. **All views use `@extends('layouts.app')` and `@section('content')`.**
|
|
215
|
+
|
|
216
|
+
**WordPress → Blade translation guide:**
|
|
217
|
+
|
|
218
|
+
The Blade column depends on which database mode was chosen. Mode A uses WordPress column names directly; Mode B uses clean Laravel names.
|
|
219
|
+
|
|
220
|
+
| WordPress | Blade (Mode A — existing DB) | Blade (Mode B — fresh DB) |
|
|
221
|
+
|-----------|------------------------------|---------------------------|
|
|
222
|
+
| `the_title()` | `{{ $post->post_title }}` | `{{ $post->title }}` |
|
|
223
|
+
| `the_content()` | `{!! $post->post_content !!}` | `{!! $post->content !!}` |
|
|
224
|
+
| `the_excerpt()` | `{{ $post->post_excerpt }}` | `{{ $post->excerpt }}` |
|
|
225
|
+
| `the_permalink()` | `{{ route('posts.show', $post->post_name) }}` | `{{ route('posts.show', $post->slug) }}` |
|
|
226
|
+
| `the_post_thumbnail()` | via `wp_postmeta` lookup | `<img src="{{ asset('storage/' . $post->featured_image) }}" />` |
|
|
227
|
+
| `the_date()` / `the_time()` | `{{ \Carbon\Carbon::parse($post->post_date)->format('d M Y') }}` | `{{ $post->published_at->format('d M Y') }}` |
|
|
228
|
+
| `the_author()` | `{{ $post->author->display_name }}` | `{{ $post->author->name }}` |
|
|
229
|
+
| `comments_template()` | `@include('partials.comments', ['post' => $post])` | same |
|
|
230
|
+
| `get_template_part('content')` | `@include('partials.post-card', ['post' => $post])` | same |
|
|
231
|
+
| `have_posts() / the_post()` | Use an `s-loop` element (see below) | same |
|
|
232
|
+
| `wp_link_pages()` | `{{ $posts->links() }}` | same |
|
|
233
|
+
| `get_search_form()` | `@include('partials.search-form')` | same |
|
|
234
|
+
| `wp_nav_menu()` | `@include('partials.nav')` | same |
|
|
235
|
+
| `get_header()` / `get_footer()` | handled by `@extends('layouts.app')` | same |
|
|
236
|
+
| `esc_html()` / `esc_attr()` | `{{ }}` (Blade auto-escapes) | same |
|
|
237
|
+
| `wp_kses_post()` | `{!! !!}` (for trusted HTML content) | same |
|
|
238
|
+
|
|
239
|
+
**Stellify `s-loop` elements for iteration:**
|
|
240
|
+
|
|
241
|
+
WordPress's "The Loop" (`have_posts() / the_post()`) requires two things in Stellify:
|
|
242
|
+
|
|
243
|
+
1. **Set the element type to `s-loop`** using `update_element` with `type: "s-loop"` and `variable` set to the collection name from the controller's return array. This tells Stellify to iterate over that variable and pass each item as `$item` to child elements.
|
|
244
|
+
|
|
245
|
+
2. **Write the `@foreach` in the Blade template as well.** The Blade content inside the element still needs the `@foreach` loop.
|
|
246
|
+
|
|
247
|
+
Example: If the controller returns `['posts' => Post::paginate(10)]`, the element that lists posts should be updated to:
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"uuid": "<element-uuid>",
|
|
251
|
+
"data": {
|
|
252
|
+
"type": "s-loop",
|
|
253
|
+
"variable": "posts"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
And the Blade content inside uses `@foreach($posts as $post)` ... `@endforeach` as normal.
|
|
258
|
+
|
|
259
|
+
This applies to any listing — post archives, category pages, search results, related posts, comment lists, navigation menu items, etc.
|
|
260
|
+
|
|
261
|
+
### 5f. Create Partials & Components
|
|
262
|
+
Convert template-parts/ files into Blade partials using `@include`. Static partials receive data via the second argument: `@include('partials.post-card', ['post' => $post])`. If a template part relies on JavaScript for interactivity (e.g. a slider, a filterable gallery, a live search form), create a Vue component instead and ensure it is registered and mounted in `app.js`.
|
|
263
|
+
|
|
264
|
+
### 5g. Style with Tailwind
|
|
265
|
+
Do not try to port WordPress CSS. Read the visual intent from the theme's CSS/theme.json and apply Tailwind utility classes directly in the Blade templates. For example:
|
|
266
|
+
- WordPress container → `<div class="max-w-4xl mx-auto px-4">`
|
|
267
|
+
- WordPress navigation → `<nav class="flex items-center gap-6">`
|
|
268
|
+
- WordPress post grid → `<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">`
|
|
269
|
+
|
|
270
|
+
## Step 6 — Review
|
|
271
|
+
|
|
272
|
+
After building everything, print a summary:
|
|
273
|
+
- Database mode chosen (A or B)
|
|
274
|
+
- Total models created (and which WordPress tables they map to, if Mode A)
|
|
275
|
+
- Total controllers and methods
|
|
276
|
+
- Total routes
|
|
277
|
+
- Total Blade views and partials
|
|
278
|
+
- Total Vue components (if any) and why each was needed
|
|
279
|
+
- Any WordPress features that could NOT be mapped (e.g. specific plugins, shortcodes with no equivalent)
|
|
280
|
+
- If Mode B: provide a SQL migration query or artisan command to copy content from the WordPress database to the new schema
|
|
281
|
+
- Suggestions for manual follow-up
|
|
282
|
+
|
|
283
|
+
## Important Rules
|
|
284
|
+
|
|
285
|
+
- **Blade for SSR, Vue for interactivity.** If the WordPress source is server-rendered PHP (posts, pages, archives, menus, layouts), create Blade templates using `@extends`, `@section`, `@include`, `@foreach`, `{{ }}` and `{!! !!}`. If the WordPress source uses JavaScript for interactivity (AJAX, dynamic filtering, sliders, modals, live search, infinite scroll), create a Vue component. When creating Vue components, ensure they are registered and mounted in `app.js` via `createApp` — do not just import them.
|
|
286
|
+
- **Do not ask questions** except the two permitted in Step 1 (which theme, and which database mode). Make reasonable decisions and document them.
|
|
287
|
+
- **Work file by file** — read a WordPress file, create the Stellify equivalent, move to the next.
|
|
288
|
+
- **Show progress** — print what you're reading and what you're creating as you go.
|
|
289
|
+
- **Handle block themes** — if theme.json exists and templates are in HTML with block markup (`<!-- wp:xxx -->`), parse the block structure rather than traditional PHP templates. The output is still Blade (or Vue where the block is interactive).
|
|
290
|
+
- **Style with Tailwind** — do not port WordPress CSS. Interpret the visual intent and use Tailwind utilities.
|
|
291
|
+
- **Be pragmatic** — focus on the core templates that define the site's main pages. Skip hyper-specific template variations unless they're clearly important.
|
|
292
|
+
- **Content handling depends on database mode** — In Mode A (existing DB), the content is already there via the WordPress tables. In Mode B (fresh DB), content is separate and can be migrated later.
|