react-layout-sdk 1.1.12 → 1.1.13
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 +139 -133
- package/package.json +1 -1
package/bin/init.js
CHANGED
|
@@ -32,161 +32,167 @@ function toPascalCase(str) {
|
|
|
32
32
|
return str.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const readline = require('readline');
|
|
36
|
+
|
|
37
|
+
function prompt(query) {
|
|
38
|
+
const rl = readline.createInterface({
|
|
39
|
+
input: process.stdin,
|
|
40
|
+
output: process.stdout,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return new Promise(resolve => rl.question(query, ans => {
|
|
44
|
+
rl.close();
|
|
45
|
+
resolve(ans);
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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.`);
|
|
49
|
+
async function writeWithPrompt(filePath, content, description) {
|
|
50
|
+
if (fs.existsSync(filePath)) {
|
|
51
|
+
const answer = await prompt(`⚠️ File ${filePath} already exists. Do you want to overwrite it? (y/N) `);
|
|
52
|
+
if (answer.toLowerCase() !== 'y') {
|
|
53
|
+
console.log(`⏭️ Skipped ${description}.`);
|
|
54
|
+
return;
|
|
61
55
|
}
|
|
56
|
+
}
|
|
57
|
+
fs.writeFileSync(filePath, content);
|
|
58
|
+
console.log(`✅ Created ${description}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function main() {
|
|
62
|
+
if (command === 'generate' || command === 'g') {
|
|
63
|
+
const type = args[1];
|
|
64
|
+
const uid = args[2];
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
if (type === 'component' && uid) {
|
|
67
|
+
ensureDirSync(componentsDir);
|
|
68
|
+
|
|
69
|
+
let category = 'core';
|
|
70
|
+
let compName = uid;
|
|
71
|
+
if (uid.includes('.')) {
|
|
72
|
+
const parts = uid.split('.');
|
|
73
|
+
category = parts[0];
|
|
74
|
+
compName = parts[1];
|
|
70
75
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (fs.existsSync(factoryPath)) {
|
|
74
|
-
let factoryContent = fs.readFileSync(factoryPath, 'utf8');
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
const componentNamePascal = toPascalCase(compName);
|
|
78
|
+
|
|
79
|
+
const compPath = path.join(componentsDir, `${componentNamePascal}.tsx`);
|
|
80
|
+
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`;
|
|
81
|
+
await writeWithPrompt(compPath, compContent, `components/${componentNamePascal}.tsx`);
|
|
82
|
+
|
|
83
|
+
let factoryPath = path.join(componentsDir, 'factory.ts');
|
|
84
|
+
if (!fs.existsSync(factoryPath)) {
|
|
85
|
+
if (fs.existsSync(path.join(componentsDir, 'factory.tsx'))) {
|
|
86
|
+
factoryPath = path.join(componentsDir, 'factory.tsx');
|
|
87
|
+
} else if (fs.existsSync(path.join(componentsDir, 'factory.js'))) {
|
|
88
|
+
factoryPath = path.join(componentsDir, 'factory.js');
|
|
86
89
|
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (fs.existsSync(factoryPath)) {
|
|
93
|
+
let factoryContent = fs.readFileSync(factoryPath, 'utf8');
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
const importStatement = `import ${componentNamePascal} from './${componentNamePascal}';`;
|
|
96
|
+
if (!factoryContent.includes(importStatement)) {
|
|
97
|
+
const lines = factoryContent.split('\n');
|
|
98
|
+
let lastImportIndex = -1;
|
|
99
|
+
for (let i = 0; i < lines.length; i++) {
|
|
100
|
+
if (lines[i].startsWith('import ')) {
|
|
101
|
+
lastImportIndex = i;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (lastImportIndex !== -1) {
|
|
106
|
+
lines.splice(lastImportIndex + 1, 0, importStatement);
|
|
107
|
+
} else {
|
|
108
|
+
lines.unshift(importStatement);
|
|
109
|
+
}
|
|
110
|
+
factoryContent = lines.join('\n');
|
|
92
111
|
}
|
|
93
|
-
factoryContent = lines.join('\n');
|
|
94
|
-
}
|
|
95
112
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
const mappingStatement = `'${uid}': ${componentNamePascal},`;
|
|
114
|
+
if (!factoryContent.includes(`'${uid}':`) && !factoryContent.includes(`"${uid}":`)) {
|
|
115
|
+
factoryContent = factoryContent.replace(
|
|
116
|
+
/(?:export\s+)?const\s+componentMap.*=\s*{/,
|
|
117
|
+
`$& \n ${mappingStatement}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fs.writeFileSync(factoryPath, factoryContent);
|
|
122
|
+
console.log(`✅ Registered '${uid}' in components/factory.ts`);
|
|
123
|
+
} else {
|
|
124
|
+
console.error(`❌ components/factory.ts not found. Please run 'npx react-layout-sdk init' first.`);
|
|
103
125
|
}
|
|
104
126
|
|
|
105
|
-
|
|
106
|
-
console.log(`✅ Registered '${uid}' in components/factory.ts`);
|
|
127
|
+
process.exit(0);
|
|
107
128
|
} else {
|
|
108
|
-
console.error(
|
|
129
|
+
console.error("❌ Usage: npx react-layout-sdk generate component <component.uid>");
|
|
130
|
+
process.exit(1);
|
|
109
131
|
}
|
|
110
|
-
|
|
111
|
-
process.exit(0);
|
|
112
|
-
} else {
|
|
113
|
-
console.error("❌ Usage: npx react-layout-sdk generate component <component.uid>");
|
|
114
|
-
process.exit(1);
|
|
115
132
|
}
|
|
116
|
-
}
|
|
117
133
|
|
|
118
|
-
if (command === 'init') {
|
|
119
|
-
|
|
134
|
+
if (command === 'init') {
|
|
135
|
+
ensureDirSync(componentsDir);
|
|
120
136
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (!fs.existsSync(factoryPath)) {
|
|
125
|
-
fs.writeFileSync(factoryPath, factoryContent);
|
|
126
|
-
console.log('✅ Created components/factory.ts');
|
|
127
|
-
}
|
|
137
|
+
const factoryPath = path.join(componentsDir, 'factory.ts');
|
|
138
|
+
const factoryContent = `import Header from './Header';\nimport Footer from './Footer';\n\nexport const componentMap = {\n 'core.header': Header,\n 'core.footer': Footer,\n};\n`;
|
|
139
|
+
await writeWithPrompt(factoryPath, factoryContent, 'components/factory.ts');
|
|
128
140
|
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
if (!fs.existsSync(headerPath)) {
|
|
133
|
-
fs.writeFileSync(headerPath, headerContent);
|
|
134
|
-
console.log('✅ Created components/Header.tsx');
|
|
135
|
-
}
|
|
141
|
+
const headerPath = path.join(componentsDir, 'Header.tsx');
|
|
142
|
+
const headerContent = `import React from 'react';\n\nexport default function Header(props: any) {\n const title = props?.title || 'JD 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`;
|
|
143
|
+
await writeWithPrompt(headerPath, headerContent, 'components/Header.tsx');
|
|
136
144
|
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
if (!fs.existsSync(footerPath)) {
|
|
141
|
-
fs.writeFileSync(footerPath, footerContent);
|
|
142
|
-
console.log('✅ Created components/Footer.tsx');
|
|
143
|
-
}
|
|
145
|
+
const footerPath = path.join(componentsDir, 'Footer.tsx');
|
|
146
|
+
const footerContent = `import React from 'react';\n\nexport default function Footer(props: any) {\n const text = props?.text || '© 2026 JD 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`;
|
|
147
|
+
await writeWithPrompt(footerPath, footerContent, 'components/Footer.tsx');
|
|
144
148
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
fs.writeFileSync(layoutPath, layoutContent);
|
|
150
|
-
console.log('✅ Created components/Layout.tsx');
|
|
151
|
-
}
|
|
149
|
+
// Layout in the main default path (basePath: e.g. src/Layout.tsx)
|
|
150
|
+
const layoutPath = path.join(basePath, 'Layout.tsx');
|
|
151
|
+
const layoutContent = `import React from 'react';\n\nexport default function Layout({ layoutData, placeholderComponent: Placeholder }: any) {\n const { route } = layoutData.strapi;\n return (\n <div className="layout-wrapper" style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>\n <header>\n <Placeholder name="header" rendering={route.placeholders.header || []} />\n </header>\n \n <main style={{ flexGrow: 1 }}>\n <div id="content" className="pageContent">\n <Placeholder name="main" rendering={route.placeholders.main || []} />\n </div>\n </main>\n \n <footer>\n <Placeholder name="footer" rendering={route.placeholders.footer || []} />\n </footer>\n </div>\n );\n}\n`;
|
|
152
|
+
await writeWithPrompt(layoutPath, layoutContent, 'Layout.tsx (Root/src level)');
|
|
152
153
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
154
|
+
if (hasAppRouter) {
|
|
155
|
+
console.log('🔍 Detected Next.js App Router');
|
|
156
|
+
|
|
157
|
+
const existingAppPage = path.join(basePath, 'app', 'page.tsx');
|
|
158
|
+
if (fs.existsSync(existingAppPage)) {
|
|
159
|
+
const answer = await prompt(`⚠️ File app/page.tsx exists. Should we rename it to avoid conflicts with catch-all route? (Y/n) `);
|
|
160
|
+
if (answer.toLowerCase() !== 'n') {
|
|
161
|
+
const backupPath = path.join(basePath, 'app', 'page.tsx.bak');
|
|
162
|
+
fs.renameSync(existingAppPage, backupPath);
|
|
163
|
+
console.log('⚠️ Renamed existing app/page.tsx to app/page.tsx.bak');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
163
166
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
167
|
+
const pageDir = path.join(basePath, 'app', '[[...slug]]');
|
|
168
|
+
ensureDirSync(pageDir);
|
|
169
|
+
|
|
170
|
+
const appPagePath = path.join(pageDir, 'page.tsx');
|
|
171
|
+
const importLayoutPath = hasSrc ? '@/Layout' : '../Layout';
|
|
172
|
+
const appPageContent = `import React from 'react';\nimport { JDPage, generateJDMetadata } from 'react-layout-sdk';\nimport { componentMap } from '@/components/factory';\nimport Layout from '${importLayoutPath}';\n\nconst STRAPI_URL = process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337';\n\nexport const generateMetadata = ({ params }: { params: Promise<{ slug?: string[] }> }) => {\n return generateJDMetadata(params, STRAPI_URL);\n}\n\nexport default function Page({ params }: { params: Promise<{ slug?: string[] }> }) {\n return <JDPage params={params} apiUrl={STRAPI_URL} componentMap={componentMap} layoutComponent={Layout} />;\n}\n`;
|
|
173
|
+
await writeWithPrompt(appPagePath, appPageContent, 'app/[[...slug]]/page.tsx');
|
|
174
|
+
} else if (hasPagesRouter) {
|
|
175
|
+
console.log('🔍 Detected Next.js Pages Router');
|
|
176
|
+
|
|
177
|
+
const existingPagesIndex = path.join(basePath, 'pages', 'index.tsx');
|
|
178
|
+
if (fs.existsSync(existingPagesIndex)) {
|
|
179
|
+
const answer = await prompt(`⚠️ File pages/index.tsx exists. Should we rename it to avoid conflicts with catch-all route? (Y/n) `);
|
|
180
|
+
if (answer.toLowerCase() !== 'n') {
|
|
181
|
+
const backupPath = path.join(basePath, 'pages', 'index.tsx.bak');
|
|
182
|
+
fs.renameSync(existingPagesIndex, backupPath);
|
|
183
|
+
console.log('⚠️ Renamed existing pages/index.tsx to pages/index.tsx.bak');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
182
186
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
console.log('✅ Created pages/[[...slug]].tsx');
|
|
187
|
+
const pagePath = path.join(basePath, 'pages', '[[...slug]].tsx');
|
|
188
|
+
const importLayoutPath = hasSrc ? '@/Layout' : '../Layout';
|
|
189
|
+
const pagesContent = `import React from 'react';\nimport { fetchJDLayout, Placeholder } from 'react-layout-sdk';\nimport { componentMap } from '@/components/factory';\nimport Layout from '${importLayoutPath}';\n\nexport default function LayoutPage({ layoutData, error }: any) {\n if (error || !layoutData?.strapi) return <h1>404 - Layout Not Found</h1>;\n\n const CustomPlaceholder = (props: any) => <Placeholder {...props} componentMap={componentMap} />;\n\n return <Layout layoutData={layoutData} placeholderComponent={CustomPlaceholder} />;\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 fetchJDLayout(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`;
|
|
190
|
+
await writeWithPrompt(pagePath, pagesContent, 'pages/[[...slug]].tsx');
|
|
188
191
|
}
|
|
189
|
-
}
|
|
190
192
|
|
|
191
|
-
|
|
193
|
+
console.log('\n🚀 Layout setup complete! Please verify your Strapi URL in your routing page.');
|
|
194
|
+
process.exit(0);
|
|
195
|
+
}
|
|
192
196
|
}
|
|
197
|
+
|
|
198
|
+
main().catch(console.error);
|