react-layout-sdk 1.1.11 → 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.
Files changed (2) hide show
  1. package/bin/init.js +140 -126
  2. package/package.json +1 -1
package/bin/init.js CHANGED
@@ -32,153 +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
- if (command === 'generate' || command === 'g') {
36
- const type = args[1];
37
- const uid = args[2];
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
- 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.`);
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
- // 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');
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
- // 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
- }
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
- if (lastImportIndex !== -1) {
89
- lines.splice(lastImportIndex + 1, 0, importStatement);
90
- } else {
91
- lines.unshift(importStatement);
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
- // 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
- );
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
- fs.writeFileSync(factoryPath, factoryContent);
106
- console.log(`✅ Registered '${uid}' in components/factory.ts`);
127
+ process.exit(0);
107
128
  } else {
108
- console.error(`❌ components/factory.ts not found. Please run 'npx react-layout-sdk init' first.`);
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
- ensureDirSync(componentsDir);
134
+ if (command === 'init') {
135
+ ensureDirSync(componentsDir);
120
136
 
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');
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
- // 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 || '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`;
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
- // 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 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`;
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
- // 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
- }
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)');
155
153
 
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 { JDPage, generateJDMetadata } from 'react-layout-sdk';\nimport { componentMap } from '@/components/factory';\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} />;\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
- }
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
+ }
166
+
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
+ }
174
186
 
175
- const pagePath = path.join(basePath, 'pages', '[[...slug]].tsx');
176
- const pagesContent = `import React from 'react';\nimport { fetchJDLayout, JDLayout, 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 CustomPlaceholder = (props: any) => <Placeholder {...props} componentMap={componentMap} />;\n\n return <JDLayout 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`;
177
- if (!fs.existsSync(pagePath)) {
178
- fs.writeFileSync(pagePath, pagesContent);
179
- 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');
180
191
  }
181
- }
182
192
 
183
- console.log('\n🚀 Layout setup complete! Please verify your Strapi URL in your routing page.');
193
+ console.log('\n🚀 Layout setup complete! Please verify your Strapi URL in your routing page.');
194
+ process.exit(0);
195
+ }
184
196
  }
197
+
198
+ main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-layout-sdk",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "description": "React components for JD SDK (Sitecore-like routing)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",