react-layout-sdk 1.1.3 → 1.1.5
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/init.js +145 -151
- package/package.json +1 -1
package/bin/init.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const command = args[0] || 'init';
|
|
7
|
+
|
|
5
8
|
const projectRoot = process.cwd();
|
|
6
9
|
|
|
7
10
|
// Detect project structure
|
|
@@ -25,166 +28,157 @@ function ensureDirSync(dirPath) {
|
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// 1. Create factory.ts
|
|
31
|
-
const factoryPath = path.join(componentsDir, 'factory.ts');
|
|
32
|
-
const factoryContent = `import Header from './Header';
|
|
33
|
-
import Footer from './Footer';
|
|
34
|
-
|
|
35
|
-
export const componentMap = {
|
|
36
|
-
'velox.header': Header,
|
|
37
|
-
'velox.footer': Footer,
|
|
38
|
-
};
|
|
39
|
-
`;
|
|
40
|
-
if (!fs.existsSync(factoryPath)) {
|
|
41
|
-
fs.writeFileSync(factoryPath, factoryContent);
|
|
42
|
-
console.log('✅ Created components/factory.ts');
|
|
31
|
+
function toPascalCase(str) {
|
|
32
|
+
return str.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
|
|
43
33
|
}
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (!fs.existsSync(
|
|
66
|
-
|
|
67
|
-
|
|
35
|
+
if (command === 'generate' || command === 'g') {
|
|
36
|
+
const type = args[1];
|
|
37
|
+
const uid = args[2];
|
|
38
|
+
|
|
39
|
+
if (type === 'component' && uid) {
|
|
40
|
+
ensureDirSync(componentsDir);
|
|
41
|
+
|
|
42
|
+
// Parse UID
|
|
43
|
+
let category = 'core';
|
|
44
|
+
let compName = uid;
|
|
45
|
+
if (uid.includes('.')) {
|
|
46
|
+
const parts = uid.split('.');
|
|
47
|
+
category = parts[0];
|
|
48
|
+
compName = parts[1];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const componentNamePascal = toPascalCase(compName);
|
|
52
|
+
|
|
53
|
+
// 1. Create Component File
|
|
54
|
+
const compPath = path.join(componentsDir, `${componentNamePascal}.tsx`);
|
|
55
|
+
if (!fs.existsSync(compPath)) {
|
|
56
|
+
const compContent = `import React from 'react';\n\nexport default function ${componentNamePascal}(props: any) {\n return (\n <div style={{ padding: '20px', border: '1px solid #ddd', margin: '10px 0' }}>\n <h2>${componentNamePascal} Component</h2>\n <pre style={{ background: '#f4f4f4', padding: '10px' }}>{JSON.stringify(props, null, 2)}</pre>\n </div>\n );\n}\n`;
|
|
57
|
+
fs.writeFileSync(compPath, compContent);
|
|
58
|
+
console.log(`✅ Created component: components/${componentNamePascal}.tsx`);
|
|
59
|
+
} else {
|
|
60
|
+
console.log(`⚠️ Component components/${componentNamePascal}.tsx already exists. Skipping creation.`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 2. Update factory.ts / factory.tsx
|
|
64
|
+
let factoryPath = path.join(componentsDir, 'factory.ts');
|
|
65
|
+
if (!fs.existsSync(factoryPath)) {
|
|
66
|
+
if (fs.existsSync(path.join(componentsDir, 'factory.tsx'))) {
|
|
67
|
+
factoryPath = path.join(componentsDir, 'factory.tsx');
|
|
68
|
+
} else if (fs.existsSync(path.join(componentsDir, 'factory.js'))) {
|
|
69
|
+
factoryPath = path.join(componentsDir, 'factory.js');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (fs.existsSync(factoryPath)) {
|
|
74
|
+
let factoryContent = fs.readFileSync(factoryPath, 'utf8');
|
|
75
|
+
|
|
76
|
+
// Inject Import
|
|
77
|
+
const importStatement = `import ${componentNamePascal} from './${componentNamePascal}';`;
|
|
78
|
+
if (!factoryContent.includes(importStatement)) {
|
|
79
|
+
// Find last import
|
|
80
|
+
const lines = factoryContent.split('\n');
|
|
81
|
+
let lastImportIndex = -1;
|
|
82
|
+
for (let i = 0; i < lines.length; i++) {
|
|
83
|
+
if (lines[i].startsWith('import ')) {
|
|
84
|
+
lastImportIndex = i;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (lastImportIndex !== -1) {
|
|
89
|
+
lines.splice(lastImportIndex + 1, 0, importStatement);
|
|
90
|
+
} else {
|
|
91
|
+
lines.unshift(importStatement);
|
|
92
|
+
}
|
|
93
|
+
factoryContent = lines.join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Inject Mapping
|
|
97
|
+
const mappingStatement = `'${uid}': ${componentNamePascal},`;
|
|
98
|
+
if (!factoryContent.includes(`'${uid}':`) && !factoryContent.includes(`"${uid}":`)) {
|
|
99
|
+
factoryContent = factoryContent.replace(
|
|
100
|
+
/(?:export\s+)?const\s+componentMap.*=\s*{/,
|
|
101
|
+
`$& \n ${mappingStatement}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fs.writeFileSync(factoryPath, factoryContent);
|
|
106
|
+
console.log(`✅ Registered '${uid}' in components/factory.ts`);
|
|
107
|
+
} else {
|
|
108
|
+
console.error(`❌ components/factory.ts not found. Please run 'npx react-layout-sdk init' first.`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
process.exit(0);
|
|
112
|
+
} else {
|
|
113
|
+
console.error("❌ Usage: npx react-layout-sdk generate component <component.uid>");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
68
116
|
}
|
|
69
117
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const footerContent = `import React from 'react';
|
|
73
|
-
|
|
74
|
-
export default function Footer(props: any) {
|
|
75
|
-
const text = props?.text || '© 2026 Velox Layout';
|
|
76
|
-
return (
|
|
77
|
-
<footer style={{ padding: '20px', background: '#333', color: '#fff', textAlign: 'center', marginTop: '40px' }}>
|
|
78
|
-
<p style={{ margin: 0 }}>{text}</p>
|
|
79
|
-
</footer>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
`;
|
|
83
|
-
if (!fs.existsSync(footerPath)) {
|
|
84
|
-
fs.writeFileSync(footerPath, footerContent);
|
|
85
|
-
console.log('✅ Created components/Footer.tsx');
|
|
86
|
-
}
|
|
118
|
+
if (command === 'init') {
|
|
119
|
+
ensureDirSync(componentsDir);
|
|
87
120
|
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (fs.existsSync(existingAppPage)) {
|
|
95
|
-
const backupPath = path.join(basePath, 'app', 'page.tsx.bak');
|
|
96
|
-
fs.renameSync(existingAppPage, backupPath);
|
|
97
|
-
console.log('⚠️ Renamed existing app/page.tsx to app/page.tsx.bak to avoid catch-all route conflict.');
|
|
121
|
+
// 1. Create factory.ts
|
|
122
|
+
const factoryPath = path.join(componentsDir, 'factory.ts');
|
|
123
|
+
const factoryContent = `import Header from './Header';\nimport Footer from './Footer';\n\nexport const componentMap = {\n 'core.header': Header,\n 'core.footer': Footer,\n};\n`;
|
|
124
|
+
if (!fs.existsSync(factoryPath)) {
|
|
125
|
+
fs.writeFileSync(factoryPath, factoryContent);
|
|
126
|
+
console.log('✅ Created components/factory.ts');
|
|
98
127
|
}
|
|
99
128
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
import { componentMap } from '@/components/factory';
|
|
107
|
-
|
|
108
|
-
export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {
|
|
109
|
-
const resolvedParams = await params;
|
|
110
|
-
const slugArray = resolvedParams?.slug || [];
|
|
111
|
-
const path = slugArray.join('/') || '/';
|
|
112
|
-
const STRAPI_URL = process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337';
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const layoutData = await fetchVeloxLayout(STRAPI_URL, path, 'en');
|
|
116
|
-
if (!layoutData || !layoutData.strapi) return <h1>404 - Not Found</h1>;
|
|
117
|
-
|
|
118
|
-
const { route } = layoutData.strapi;
|
|
119
|
-
|
|
120
|
-
return (
|
|
121
|
-
<div className="layout-wrapper">
|
|
122
|
-
<Placeholder name="header" rendering={route.placeholders.header || []} componentMap={componentMap} />
|
|
123
|
-
<main style={{ minHeight: '60vh', padding: '20px' }}>
|
|
124
|
-
<Placeholder name="main" rendering={route.placeholders.main || []} componentMap={componentMap} />
|
|
125
|
-
</main>
|
|
126
|
-
<Placeholder name="footer" rendering={route.placeholders.footer || []} componentMap={componentMap} />
|
|
127
|
-
</div>
|
|
128
|
-
);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
return <h1>Error Loading Layout</h1>;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
`;
|
|
134
|
-
if (!fs.existsSync(appPagePath)) {
|
|
135
|
-
fs.writeFileSync(appPagePath, appPageContent);
|
|
136
|
-
console.log('✅ Created app/[[...slug]]/page.tsx');
|
|
137
|
-
}
|
|
138
|
-
} else if (hasPagesRouter) {
|
|
139
|
-
console.log('🔍 Detected Next.js Pages Router');
|
|
140
|
-
|
|
141
|
-
// Prevent Next.js route conflict by renaming existing pages/index.tsx
|
|
142
|
-
const existingPagesIndex = path.join(basePath, 'pages', 'index.tsx');
|
|
143
|
-
if (fs.existsSync(existingPagesIndex)) {
|
|
144
|
-
const backupPath = path.join(basePath, 'pages', 'index.tsx.bak');
|
|
145
|
-
fs.renameSync(existingPagesIndex, backupPath);
|
|
146
|
-
console.log('⚠️ Renamed existing pages/index.tsx to pages/index.tsx.bak to avoid catch-all route conflict.');
|
|
129
|
+
// 2. Create Header.tsx
|
|
130
|
+
const headerPath = path.join(componentsDir, 'Header.tsx');
|
|
131
|
+
const headerContent = `import React from 'react';\n\nexport default function Header(props: any) {\n const title = props?.title || 'Velox Header';\n const logoUrl = props?.logoUrl;\n\n return (\n <header style={{ padding: '20px', background: '#f5f5f5', borderBottom: '1px solid #ddd' }}>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>\n {logoUrl && <img src={logoUrl} alt="Logo" width="40" />}\n <h1 style={{ margin: 0, fontSize: '1.5rem' }}>{title}</h1>\n </div>\n </div>\n </header>\n );\n}\n`;
|
|
132
|
+
if (!fs.existsSync(headerPath)) {
|
|
133
|
+
fs.writeFileSync(headerPath, headerContent);
|
|
134
|
+
console.log('✅ Created components/Header.tsx');
|
|
147
135
|
}
|
|
148
136
|
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
import {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (error || !layoutData?.strapi) return <h1>404 - Layout Not Found</h1>;
|
|
156
|
-
|
|
157
|
-
const { route } = layoutData.strapi;
|
|
158
|
-
return (
|
|
159
|
-
<div className="layout-wrapper">
|
|
160
|
-
<Placeholder name="header" rendering={route.placeholders.header || []} componentMap={componentMap} />
|
|
161
|
-
<main style={{ minHeight: '60vh', padding: '20px' }}>
|
|
162
|
-
<Placeholder name="main" rendering={route.placeholders.main || []} componentMap={componentMap} />
|
|
163
|
-
</main>
|
|
164
|
-
<Placeholder name="footer" rendering={route.placeholders.footer || []} componentMap={componentMap} />
|
|
165
|
-
</div>
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export async function getServerSideProps(context: any) {
|
|
170
|
-
const slugArray = context.params?.slug || [];
|
|
171
|
-
const path = slugArray.join('/') || '/';
|
|
172
|
-
const STRAPI_URL = process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337';
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
const layoutData = await fetchVeloxLayout(STRAPI_URL, path, 'en');
|
|
176
|
-
if (!layoutData) return { notFound: true };
|
|
177
|
-
|
|
178
|
-
return { props: { layoutData } };
|
|
179
|
-
} catch (error) {
|
|
180
|
-
return { props: { error: true } };
|
|
137
|
+
// 3. Create Footer.tsx
|
|
138
|
+
const footerPath = path.join(componentsDir, 'Footer.tsx');
|
|
139
|
+
const footerContent = `import React from 'react';\n\nexport default function Footer(props: any) {\n const text = props?.text || '© 2026 Velox Layout';\n return (\n <footer style={{ padding: '20px', background: '#333', color: '#fff', textAlign: 'center', marginTop: '40px' }}>\n <p style={{ margin: 0 }}>{text}</p>\n </footer>\n );\n}\n`;
|
|
140
|
+
if (!fs.existsSync(footerPath)) {
|
|
141
|
+
fs.writeFileSync(footerPath, footerContent);
|
|
142
|
+
console.log('✅ Created components/Footer.tsx');
|
|
181
143
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
|
|
144
|
+
|
|
145
|
+
// 4. Generate Page routing
|
|
146
|
+
if (hasAppRouter) {
|
|
147
|
+
console.log('🔍 Detected Next.js App Router');
|
|
148
|
+
|
|
149
|
+
const existingAppPage = path.join(basePath, 'app', 'page.tsx');
|
|
150
|
+
if (fs.existsSync(existingAppPage)) {
|
|
151
|
+
const backupPath = path.join(basePath, 'app', 'page.tsx.bak');
|
|
152
|
+
fs.renameSync(existingAppPage, backupPath);
|
|
153
|
+
console.log('⚠️ Renamed existing app/page.tsx to app/page.tsx.bak to avoid catch-all route conflict.');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const pageDir = path.join(basePath, 'app', '[[...slug]]');
|
|
157
|
+
ensureDirSync(pageDir);
|
|
158
|
+
|
|
159
|
+
const appPagePath = path.join(pageDir, 'page.tsx');
|
|
160
|
+
const appPageContent = `import React from 'react';\nimport { fetchVeloxLayout, Placeholder } from 'react-layout-sdk';\nimport { componentMap } from '@/components/factory';\n\nexport default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {\n const resolvedParams = await params;\n const slugArray = resolvedParams?.slug || [];\n const path = slugArray.join('/') || '/';\n const STRAPI_URL = process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337';\n \n try {\n const layoutData = await fetchVeloxLayout(STRAPI_URL, path, 'en');\n if (!layoutData || !layoutData.strapi) return <h1>404 - Not Found</h1>;\n\n const { route } = layoutData.strapi;\n\n return (\n <div className="layout-wrapper">\n <Placeholder name="header" rendering={route.placeholders.header || []} componentMap={componentMap} />\n <main style={{ minHeight: '60vh', padding: '20px' }}>\n <Placeholder name="main" rendering={route.placeholders.main || []} componentMap={componentMap} />\n </main>\n <Placeholder name="footer" rendering={route.placeholders.footer || []} componentMap={componentMap} />\n </div>\n );\n } catch (error) {\n return <h1>Error Loading Layout</h1>;\n }\n}\n`;
|
|
161
|
+
if (!fs.existsSync(appPagePath)) {
|
|
162
|
+
fs.writeFileSync(appPagePath, appPageContent);
|
|
163
|
+
console.log('✅ Created app/[[...slug]]/page.tsx');
|
|
164
|
+
}
|
|
165
|
+
} else if (hasPagesRouter) {
|
|
166
|
+
console.log('🔍 Detected Next.js Pages Router');
|
|
167
|
+
|
|
168
|
+
const existingPagesIndex = path.join(basePath, 'pages', 'index.tsx');
|
|
169
|
+
if (fs.existsSync(existingPagesIndex)) {
|
|
170
|
+
const backupPath = path.join(basePath, 'pages', 'index.tsx.bak');
|
|
171
|
+
fs.renameSync(existingPagesIndex, backupPath);
|
|
172
|
+
console.log('⚠️ Renamed existing pages/index.tsx to pages/index.tsx.bak to avoid catch-all route conflict.');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const pagePath = path.join(basePath, 'pages', '[[...slug]].tsx');
|
|
176
|
+
const pagesContent = `import React from 'react';\nimport { fetchVeloxLayout, Placeholder } from 'react-layout-sdk';\nimport { componentMap } from '@/components/factory';\n\nexport default function LayoutPage({ layoutData, error }: any) {\n if (error || !layoutData?.strapi) return <h1>404 - Layout Not Found</h1>;\n\n const { route } = layoutData.strapi;\n return (\n <div className="layout-wrapper">\n <Placeholder name="header" rendering={route.placeholders.header || []} componentMap={componentMap} />\n <main style={{ minHeight: '60vh', padding: '20px' }}>\n <Placeholder name="main" rendering={route.placeholders.main || []} componentMap={componentMap} />\n </main>\n <Placeholder name="footer" rendering={route.placeholders.footer || []} componentMap={componentMap} />\n </div>\n );\n}\n\nexport async function getServerSideProps(context: any) {\n const slugArray = context.params?.slug || [];\n const path = slugArray.join('/') || '/';\n const STRAPI_URL = process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337';\n\n try {\n const layoutData = await fetchVeloxLayout(STRAPI_URL, path, 'en');\n if (!layoutData) return { notFound: true };\n\n return { props: { layoutData } };\n } catch (error) {\n return { props: { error: true } };\n }\n}\n`;
|
|
177
|
+
if (!fs.existsSync(pagePath)) {
|
|
178
|
+
fs.writeFileSync(pagePath, pagesContent);
|
|
179
|
+
console.log('✅ Created pages/[[...slug]].tsx');
|
|
180
|
+
}
|
|
187
181
|
}
|
|
188
|
-
}
|
|
189
182
|
|
|
190
|
-
console.log('\n🚀 Layout setup complete! Please verify your Strapi URL in your routing page.');
|
|
183
|
+
console.log('\n🚀 Layout setup complete! Please verify your Strapi URL in your routing page.');
|
|
184
|
+
}
|