@l4yercak3/cli 1.2.20 → 1.3.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/bin/cli.js +25 -0
- package/docs/CLI_PAGE_DETECTION_REQUIREMENTS.md +519 -0
- package/package.json +1 -1
- package/src/api/backend-client.js +149 -0
- package/src/commands/connect.js +243 -0
- package/src/commands/pages.js +317 -0
- package/src/commands/scaffold.js +409 -0
- package/src/commands/spread.js +68 -164
- package/src/commands/sync.js +169 -0
- package/src/detectors/index.js +13 -0
- package/src/detectors/mapping-suggestor.js +119 -0
- package/src/detectors/model-detector.js +318 -0
- package/src/detectors/page-detector.js +480 -0
- package/src/detectors/registry.js +25 -3
- package/src/generators/manifest-generator.js +154 -0
- package/src/generators/quickstart/index.js +6 -0
- package/src/utils/init-helpers.js +243 -0
- package/tests/page-detector.test.js +371 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pages Command
|
|
3
|
+
* Detect and sync application pages/screens with L4YERCAK3
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const configManager = require('../config/config-manager');
|
|
7
|
+
const backendClient = require('../api/backend-client');
|
|
8
|
+
const projectDetector = require('../detectors');
|
|
9
|
+
const pageDetector = require('../detectors/page-detector');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const inquirer = require('inquirer');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get framework display name
|
|
15
|
+
*/
|
|
16
|
+
function getFrameworkName(frameworkType) {
|
|
17
|
+
const names = {
|
|
18
|
+
'nextjs': 'Next.js',
|
|
19
|
+
'expo': 'Expo',
|
|
20
|
+
'react-native': 'React Native',
|
|
21
|
+
};
|
|
22
|
+
return names[frameworkType] || frameworkType;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Handle pages sync command
|
|
27
|
+
*/
|
|
28
|
+
async function handlePagesSync() {
|
|
29
|
+
// Check if logged in
|
|
30
|
+
if (!configManager.isLoggedIn()) {
|
|
31
|
+
console.log(chalk.yellow(' ⚠️ You must be logged in first'));
|
|
32
|
+
console.log(chalk.gray('\n Run "l4yercak3 login" to authenticate\n'));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(chalk.cyan(' 🔍 Scanning for pages...\n'));
|
|
37
|
+
|
|
38
|
+
// Detect project
|
|
39
|
+
const detection = projectDetector.detect();
|
|
40
|
+
|
|
41
|
+
if (!detection.framework.type) {
|
|
42
|
+
console.log(chalk.yellow(' ⚠️ Could not detect project type'));
|
|
43
|
+
console.log(chalk.gray(' Supported frameworks: Next.js, Expo, React Native\n'));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const frameworkName = getFrameworkName(detection.framework.type);
|
|
48
|
+
console.log(chalk.gray(` Framework: ${frameworkName}`));
|
|
49
|
+
|
|
50
|
+
// Check for project configuration
|
|
51
|
+
const projectConfig = configManager.getProjectConfig(detection.projectPath);
|
|
52
|
+
|
|
53
|
+
if (!projectConfig || !projectConfig.applicationId) {
|
|
54
|
+
console.log(chalk.yellow('\n ⚠️ This project is not connected to L4YERCAK3'));
|
|
55
|
+
console.log(chalk.gray(' Run "l4yercak3 spread" first to set up the connection\n'));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Detect pages
|
|
60
|
+
const pages = pageDetector.detect(
|
|
61
|
+
detection.projectPath,
|
|
62
|
+
detection.framework.type,
|
|
63
|
+
detection.framework.metadata
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (pages.length === 0) {
|
|
67
|
+
console.log(chalk.yellow('\n ⚠️ No pages found'));
|
|
68
|
+
console.log(chalk.gray(' Make sure your project has page files in the expected locations\n'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Categorize pages
|
|
73
|
+
const staticPages = pages.filter(p => p.pageType === 'static');
|
|
74
|
+
const dynamicPages = pages.filter(p => p.pageType === 'dynamic');
|
|
75
|
+
const apiRoutes = pages.filter(p => p.pageType === 'api_route');
|
|
76
|
+
|
|
77
|
+
console.log(chalk.green(`\n ✅ Found ${pages.length} pages/routes\n`));
|
|
78
|
+
|
|
79
|
+
if (staticPages.length > 0) {
|
|
80
|
+
console.log(chalk.gray(` ${staticPages.length} static page(s)`));
|
|
81
|
+
}
|
|
82
|
+
if (dynamicPages.length > 0) {
|
|
83
|
+
console.log(chalk.gray(` ${dynamicPages.length} dynamic page(s)`));
|
|
84
|
+
}
|
|
85
|
+
if (apiRoutes.length > 0) {
|
|
86
|
+
console.log(chalk.gray(` ${apiRoutes.length} API route(s)`));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Show preview
|
|
90
|
+
console.log(chalk.cyan('\n 📋 Pages to sync:\n'));
|
|
91
|
+
|
|
92
|
+
const maxPathLength = Math.max(...pages.map(p => p.path.length), 20);
|
|
93
|
+
|
|
94
|
+
for (const page of pages.slice(0, 15)) {
|
|
95
|
+
const icon = page.pageType === 'api_route' ? '⚡' :
|
|
96
|
+
page.pageType === 'dynamic' ? '🔀' : '📄';
|
|
97
|
+
const paddedPath = page.path.padEnd(maxPathLength);
|
|
98
|
+
console.log(chalk.gray(` ${icon} ${paddedPath} → ${page.name}`));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (pages.length > 15) {
|
|
102
|
+
console.log(chalk.gray(` ... and ${pages.length - 15} more`));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Confirm sync
|
|
106
|
+
console.log('');
|
|
107
|
+
const { confirmSync } = await inquirer.prompt([
|
|
108
|
+
{
|
|
109
|
+
type: 'confirm',
|
|
110
|
+
name: 'confirmSync',
|
|
111
|
+
message: `Sync ${pages.length} pages to L4YERCAK3?`,
|
|
112
|
+
default: true,
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
if (!confirmSync) {
|
|
117
|
+
console.log(chalk.gray('\n Sync cancelled.\n'));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Sync pages
|
|
122
|
+
console.log(chalk.cyan('\n 🔄 Syncing pages...\n'));
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const result = await backendClient.bulkRegisterPages(
|
|
126
|
+
projectConfig.applicationId,
|
|
127
|
+
pages.map(p => ({
|
|
128
|
+
path: p.path,
|
|
129
|
+
name: p.name,
|
|
130
|
+
pageType: p.pageType,
|
|
131
|
+
}))
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
console.log(chalk.green(' ✅ Pages synced successfully!\n'));
|
|
135
|
+
console.log(chalk.gray(` Total: ${result.total || pages.length}`));
|
|
136
|
+
if (result.created !== undefined) {
|
|
137
|
+
console.log(chalk.gray(` Created: ${result.created}`));
|
|
138
|
+
}
|
|
139
|
+
if (result.updated !== undefined) {
|
|
140
|
+
console.log(chalk.gray(` Updated: ${result.updated}`));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log(chalk.cyan('\n 📊 View your pages in the L4YERCAK3 dashboard:'));
|
|
144
|
+
console.log(chalk.gray(' Web Publishing → Applications → Pages & Bindings\n'));
|
|
145
|
+
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error(chalk.red(`\n ❌ Failed to sync pages: ${error.message}\n`));
|
|
148
|
+
if (error.code) {
|
|
149
|
+
console.log(chalk.gray(` Error code: ${error.code}`));
|
|
150
|
+
}
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Handle pages list command
|
|
157
|
+
*/
|
|
158
|
+
async function handlePagesList() {
|
|
159
|
+
// Check if logged in
|
|
160
|
+
if (!configManager.isLoggedIn()) {
|
|
161
|
+
console.log(chalk.yellow(' ⚠️ You must be logged in first'));
|
|
162
|
+
console.log(chalk.gray('\n Run "l4yercak3 login" to authenticate\n'));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Detect project
|
|
167
|
+
const detection = projectDetector.detect();
|
|
168
|
+
const projectConfig = configManager.getProjectConfig(detection.projectPath);
|
|
169
|
+
|
|
170
|
+
if (!projectConfig || !projectConfig.applicationId) {
|
|
171
|
+
console.log(chalk.yellow(' ⚠️ This project is not connected to L4YERCAK3'));
|
|
172
|
+
console.log(chalk.gray(' Run "l4yercak3 spread" first to set up the connection\n'));
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log(chalk.cyan(' 📋 Fetching registered pages...\n'));
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const result = await backendClient.getApplicationPages(projectConfig.applicationId);
|
|
180
|
+
|
|
181
|
+
if (!result.pages || result.pages.length === 0) {
|
|
182
|
+
console.log(chalk.yellow(' No pages registered yet'));
|
|
183
|
+
console.log(chalk.gray(' Run "l4yercak3 pages sync" to detect and sync pages\n'));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log(chalk.green(` ✅ ${result.pages.length} pages registered\n`));
|
|
188
|
+
|
|
189
|
+
const maxPathLength = Math.max(...result.pages.map(p => p.path.length), 20);
|
|
190
|
+
|
|
191
|
+
for (const page of result.pages) {
|
|
192
|
+
const icon = page.pageType === 'api_route' ? '⚡' :
|
|
193
|
+
page.pageType === 'dynamic' ? '🔀' : '📄';
|
|
194
|
+
const paddedPath = page.path.padEnd(maxPathLength);
|
|
195
|
+
console.log(chalk.gray(` ${icon} ${paddedPath} → ${page.name}`));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log('');
|
|
199
|
+
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error(chalk.red(`\n ❌ Failed to fetch pages: ${error.message}\n`));
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Handle pages detect command (local only, no sync)
|
|
208
|
+
*/
|
|
209
|
+
async function handlePagesDetect() {
|
|
210
|
+
console.log(chalk.cyan(' 🔍 Detecting pages...\n'));
|
|
211
|
+
|
|
212
|
+
// Detect project
|
|
213
|
+
const detection = projectDetector.detect();
|
|
214
|
+
|
|
215
|
+
if (!detection.framework.type) {
|
|
216
|
+
console.log(chalk.yellow(' ⚠️ Could not detect project type'));
|
|
217
|
+
console.log(chalk.gray(' Supported frameworks: Next.js, Expo, React Native\n'));
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const frameworkName = getFrameworkName(detection.framework.type);
|
|
222
|
+
console.log(chalk.gray(` Framework: ${frameworkName}`));
|
|
223
|
+
|
|
224
|
+
if (detection.framework.metadata?.routerType) {
|
|
225
|
+
const routerName = detection.framework.type === 'nextjs'
|
|
226
|
+
? (detection.framework.metadata.routerType === 'app' ? 'App Router' : 'Pages Router')
|
|
227
|
+
: (detection.framework.metadata.routerType === 'expo-router' ? 'Expo Router' : 'React Navigation');
|
|
228
|
+
console.log(chalk.gray(` Router: ${routerName}`));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Detect pages
|
|
232
|
+
const pages = pageDetector.detect(
|
|
233
|
+
detection.projectPath,
|
|
234
|
+
detection.framework.type,
|
|
235
|
+
detection.framework.metadata
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
if (pages.length === 0) {
|
|
239
|
+
console.log(chalk.yellow('\n ⚠️ No pages found'));
|
|
240
|
+
console.log(chalk.gray(' Make sure your project has page files in the expected locations\n'));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
console.log(chalk.green(`\n ✅ Found ${pages.length} pages/routes\n`));
|
|
245
|
+
|
|
246
|
+
// Group by type
|
|
247
|
+
const staticPages = pages.filter(p => p.pageType === 'static');
|
|
248
|
+
const dynamicPages = pages.filter(p => p.pageType === 'dynamic');
|
|
249
|
+
const apiRoutes = pages.filter(p => p.pageType === 'api_route');
|
|
250
|
+
|
|
251
|
+
const maxPathLength = Math.max(...pages.map(p => p.path.length), 20);
|
|
252
|
+
|
|
253
|
+
if (staticPages.length > 0) {
|
|
254
|
+
console.log(chalk.cyan(' 📄 Static Pages:\n'));
|
|
255
|
+
for (const page of staticPages) {
|
|
256
|
+
const paddedPath = page.path.padEnd(maxPathLength);
|
|
257
|
+
console.log(chalk.gray(` ${paddedPath} → ${page.name}`));
|
|
258
|
+
console.log(chalk.gray(` └─ ${page.filePath}`));
|
|
259
|
+
}
|
|
260
|
+
console.log('');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (dynamicPages.length > 0) {
|
|
264
|
+
console.log(chalk.cyan(' 🔀 Dynamic Pages:\n'));
|
|
265
|
+
for (const page of dynamicPages) {
|
|
266
|
+
const paddedPath = page.path.padEnd(maxPathLength);
|
|
267
|
+
console.log(chalk.gray(` ${paddedPath} → ${page.name}`));
|
|
268
|
+
console.log(chalk.gray(` └─ ${page.filePath}`));
|
|
269
|
+
}
|
|
270
|
+
console.log('');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (apiRoutes.length > 0) {
|
|
274
|
+
console.log(chalk.cyan(' ⚡ API Routes:\n'));
|
|
275
|
+
for (const page of apiRoutes) {
|
|
276
|
+
const paddedPath = page.path.padEnd(maxPathLength);
|
|
277
|
+
console.log(chalk.gray(` ${paddedPath} → ${page.name}`));
|
|
278
|
+
console.log(chalk.gray(` └─ ${page.filePath}`));
|
|
279
|
+
}
|
|
280
|
+
console.log('');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
console.log(chalk.gray(' Run "l4yercak3 pages sync" to sync these pages to L4YERCAK3\n'));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Main pages command handler
|
|
288
|
+
*/
|
|
289
|
+
async function handlePages(args) {
|
|
290
|
+
const subcommand = args[0] || 'sync';
|
|
291
|
+
|
|
292
|
+
switch (subcommand) {
|
|
293
|
+
case 'sync':
|
|
294
|
+
await handlePagesSync();
|
|
295
|
+
break;
|
|
296
|
+
case 'list':
|
|
297
|
+
await handlePagesList();
|
|
298
|
+
break;
|
|
299
|
+
case 'detect':
|
|
300
|
+
await handlePagesDetect();
|
|
301
|
+
break;
|
|
302
|
+
default:
|
|
303
|
+
console.log(chalk.cyan(' 📄 Pages Command\n'));
|
|
304
|
+
console.log(chalk.gray(' Usage: l4yercak3 pages <subcommand>\n'));
|
|
305
|
+
console.log(chalk.gray(' Subcommands:'));
|
|
306
|
+
console.log(chalk.gray(' sync Detect and sync pages to L4YERCAK3 (default)'));
|
|
307
|
+
console.log(chalk.gray(' list List pages registered with L4YERCAK3'));
|
|
308
|
+
console.log(chalk.gray(' detect Detect pages locally (no sync)\n'));
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
module.exports = {
|
|
314
|
+
command: 'pages',
|
|
315
|
+
description: 'Detect and sync application pages',
|
|
316
|
+
handler: handlePages,
|
|
317
|
+
};
|