@flexireact/core 3.0.1 → 3.0.2

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 (55) hide show
  1. package/dist/cli/index.js +1514 -0
  2. package/dist/cli/index.js.map +1 -0
  3. package/dist/core/client/index.js +373 -0
  4. package/dist/core/client/index.js.map +1 -0
  5. package/dist/core/index.js +6415 -0
  6. package/dist/core/index.js.map +1 -0
  7. package/dist/core/server/index.js +3094 -0
  8. package/dist/core/server/index.js.map +1 -0
  9. package/package.json +80 -80
  10. package/bin/flexireact.js +0 -23
  11. package/cli/generators.ts +0 -616
  12. package/cli/index.ts +0 -1182
  13. package/core/actions/index.ts +0 -364
  14. package/core/api.ts +0 -143
  15. package/core/build/index.ts +0 -425
  16. package/core/cli/logger.ts +0 -353
  17. package/core/client/Link.tsx +0 -345
  18. package/core/client/hydration.ts +0 -147
  19. package/core/client/index.ts +0 -12
  20. package/core/client/islands.ts +0 -143
  21. package/core/client/navigation.ts +0 -212
  22. package/core/client/runtime.ts +0 -52
  23. package/core/config.ts +0 -116
  24. package/core/context.ts +0 -83
  25. package/core/dev.ts +0 -47
  26. package/core/devtools/index.ts +0 -644
  27. package/core/edge/cache.ts +0 -344
  28. package/core/edge/fetch-polyfill.ts +0 -247
  29. package/core/edge/handler.ts +0 -248
  30. package/core/edge/index.ts +0 -81
  31. package/core/edge/ppr.ts +0 -264
  32. package/core/edge/runtime.ts +0 -161
  33. package/core/font/index.ts +0 -306
  34. package/core/helpers.ts +0 -494
  35. package/core/image/index.ts +0 -413
  36. package/core/index.ts +0 -218
  37. package/core/islands/index.ts +0 -293
  38. package/core/loader.ts +0 -111
  39. package/core/logger.ts +0 -242
  40. package/core/metadata/index.ts +0 -622
  41. package/core/middleware/index.ts +0 -416
  42. package/core/plugins/index.ts +0 -373
  43. package/core/render/index.ts +0 -1243
  44. package/core/render.ts +0 -136
  45. package/core/router/index.ts +0 -551
  46. package/core/router.ts +0 -141
  47. package/core/rsc/index.ts +0 -199
  48. package/core/server/index.ts +0 -779
  49. package/core/server.ts +0 -203
  50. package/core/ssg/index.ts +0 -346
  51. package/core/start-dev.ts +0 -6
  52. package/core/start-prod.ts +0 -6
  53. package/core/tsconfig.json +0 -30
  54. package/core/types.ts +0 -239
  55. package/core/utils.ts +0 -176
@@ -1,293 +0,0 @@
1
- /**
2
- * FlexiReact Islands Architecture
3
- *
4
- * Islands allow partial hydration - only interactive components are hydrated on the client,
5
- * while static content remains as HTML. This dramatically reduces JavaScript bundle size.
6
- *
7
- * Usage:
8
- * - Add 'use island' at the top of a component file
9
- * - The component will be rendered on server and hydrated on client
10
- * - Non-island components are pure HTML (no JS)
11
- */
12
-
13
- import React from 'react';
14
- import { renderToString } from 'react-dom/server';
15
- import crypto from 'crypto';
16
-
17
- // Island registry for tracking all islands in a render
18
- const islandRegistry = new Map();
19
-
20
- /**
21
- * Generates a unique island ID
22
- */
23
- function generateIslandId(componentName) {
24
- const hash = crypto.randomBytes(4).toString('hex');
25
- return `island-${componentName}-${hash}`;
26
- }
27
-
28
- /**
29
- * Island wrapper component for server-side rendering
30
- */
31
- export function Island({ component: Component, props = {}, name, clientPath }) {
32
- const islandId = generateIslandId(name);
33
-
34
- // Register island for hydration
35
- islandRegistry.set(islandId, {
36
- id: islandId,
37
- name,
38
- clientPath,
39
- props
40
- });
41
-
42
- // Render the component
43
- const content = renderToString(React.createElement(Component, props));
44
-
45
- // Return wrapper with hydration marker
46
- return React.createElement('div', {
47
- 'data-island': islandId,
48
- 'data-island-name': name,
49
- 'data-island-props': JSON.stringify(props),
50
- dangerouslySetInnerHTML: { __html: content }
51
- });
52
- }
53
-
54
- /**
55
- * Gets all registered islands and clears the registry
56
- */
57
- export function getRegisteredIslands() {
58
- const islands = Array.from(islandRegistry.values());
59
- islandRegistry.clear();
60
- return islands;
61
- }
62
-
63
- /**
64
- * Creates an island component wrapper
65
- */
66
- interface IslandOptions {
67
- name?: string;
68
- clientPath?: string;
69
- }
70
-
71
- export function createIsland(Component: React.ComponentType<any>, options: IslandOptions = {}) {
72
- const { name = (Component as any).name || 'Island', clientPath } = options;
73
-
74
- function IslandWrapper(props) {
75
- return Island({
76
- component: Component,
77
- props,
78
- name,
79
- clientPath: clientPath || `/_flexi/islands/${name}.js`
80
- });
81
- }
82
-
83
- IslandWrapper.displayName = `Island(${name})`;
84
- IslandWrapper.isIsland = true;
85
- IslandWrapper.originalComponent = Component;
86
-
87
- return IslandWrapper;
88
- }
89
-
90
- /**
91
- * Generates the client-side hydration script
92
- */
93
- export function generateHydrationScript(islands) {
94
- if (!islands.length) return '';
95
-
96
- const islandData = islands.map(island => ({
97
- id: island.id,
98
- name: island.name,
99
- path: island.clientPath,
100
- props: island.props
101
- }));
102
-
103
- return `
104
- <script type="module">
105
- const islands = ${JSON.stringify(islandData)};
106
-
107
- async function hydrateIslands() {
108
- const { hydrateRoot } = await import('/_flexi/react-dom-client.js');
109
- const React = await import('/_flexi/react.js');
110
-
111
- for (const island of islands) {
112
- try {
113
- const element = document.querySelector(\`[data-island="\${island.id}"]\`);
114
- if (!element) continue;
115
-
116
- const module = await import(island.path);
117
- const Component = module.default;
118
-
119
- // Hydrate the island
120
- hydrateRoot(element, React.createElement(Component, island.props));
121
-
122
- // Mark as hydrated
123
- element.setAttribute('data-hydrated', 'true');
124
- } catch (error) {
125
- console.error(\`Failed to hydrate island \${island.name}:\`, error);
126
- }
127
- }
128
- }
129
-
130
- // Hydrate when DOM is ready
131
- if (document.readyState === 'loading') {
132
- document.addEventListener('DOMContentLoaded', hydrateIslands);
133
- } else {
134
- hydrateIslands();
135
- }
136
- </script>`;
137
- }
138
-
139
- /**
140
- * Island loading strategies
141
- */
142
- export const LoadStrategy = {
143
- // Hydrate immediately when page loads
144
- IMMEDIATE: 'immediate',
145
- // Hydrate when island becomes visible
146
- VISIBLE: 'visible',
147
- // Hydrate when user interacts with the page
148
- IDLE: 'idle',
149
- // Hydrate on specific media query
150
- MEDIA: 'media'
151
- };
152
-
153
- /**
154
- * Creates a lazy island that hydrates based on strategy
155
- */
156
- interface LazyIslandOptions {
157
- name?: string;
158
- clientPath?: string;
159
- strategy?: string;
160
- media?: string | null;
161
- }
162
-
163
- export function createLazyIsland(Component: React.ComponentType<any>, options: LazyIslandOptions = {}) {
164
- const {
165
- name = (Component as any).name || 'LazyIsland',
166
- clientPath,
167
- strategy = LoadStrategy.VISIBLE,
168
- media = null
169
- } = options;
170
-
171
- function LazyIslandWrapper(props) {
172
- const islandId = generateIslandId(name);
173
- const content = renderToString(React.createElement(Component, props));
174
-
175
- // Register with loading strategy
176
- islandRegistry.set(islandId, {
177
- id: islandId,
178
- name,
179
- clientPath: clientPath || `/_flexi/islands/${name}.js`,
180
- props,
181
- strategy,
182
- media
183
- });
184
-
185
- return React.createElement('div', {
186
- 'data-island': islandId,
187
- 'data-island-name': name,
188
- 'data-island-strategy': strategy,
189
- 'data-island-media': media,
190
- 'data-island-props': JSON.stringify(props),
191
- dangerouslySetInnerHTML: { __html: content }
192
- });
193
- }
194
-
195
- LazyIslandWrapper.displayName = `LazyIsland(${name})`;
196
- LazyIslandWrapper.isIsland = true;
197
- LazyIslandWrapper.isLazy = true;
198
-
199
- return LazyIslandWrapper;
200
- }
201
-
202
- /**
203
- * Generates advanced hydration script with loading strategies
204
- */
205
- export function generateAdvancedHydrationScript(islands) {
206
- if (!islands.length) return '';
207
-
208
- const islandData = islands.map(island => ({
209
- id: island.id,
210
- name: island.name,
211
- path: island.clientPath,
212
- props: island.props,
213
- strategy: island.strategy || LoadStrategy.IMMEDIATE,
214
- media: island.media
215
- }));
216
-
217
- return `
218
- <script type="module">
219
- const islands = ${JSON.stringify(islandData)};
220
-
221
- async function hydrateIsland(island) {
222
- const element = document.querySelector(\`[data-island="\${island.id}"]\`);
223
- if (!element || element.hasAttribute('data-hydrated')) return;
224
-
225
- try {
226
- const { hydrateRoot } = await import('/_flexi/react-dom-client.js');
227
- const React = await import('/_flexi/react.js');
228
- const module = await import(island.path);
229
- const Component = module.default;
230
-
231
- hydrateRoot(element, React.createElement(Component, island.props));
232
- element.setAttribute('data-hydrated', 'true');
233
- } catch (error) {
234
- console.error(\`Failed to hydrate island \${island.name}:\`, error);
235
- }
236
- }
237
-
238
- // Intersection Observer for visible strategy
239
- const visibleObserver = new IntersectionObserver((entries) => {
240
- entries.forEach(entry => {
241
- if (entry.isIntersecting) {
242
- const id = entry.target.getAttribute('data-island');
243
- const island = islands.find(i => i.id === id);
244
- if (island) {
245
- hydrateIsland(island);
246
- visibleObserver.unobserve(entry.target);
247
- }
248
- }
249
- });
250
- }, { rootMargin: '50px' });
251
-
252
- // Process islands based on strategy
253
- function processIslands() {
254
- for (const island of islands) {
255
- const element = document.querySelector(\`[data-island="\${island.id}"]\`);
256
- if (!element) continue;
257
-
258
- switch (island.strategy) {
259
- case 'immediate':
260
- hydrateIsland(island);
261
- break;
262
- case 'visible':
263
- visibleObserver.observe(element);
264
- break;
265
- case 'idle':
266
- requestIdleCallback(() => hydrateIsland(island));
267
- break;
268
- case 'media':
269
- if (island.media && window.matchMedia(island.media).matches) {
270
- hydrateIsland(island);
271
- }
272
- break;
273
- }
274
- }
275
- }
276
-
277
- if (document.readyState === 'loading') {
278
- document.addEventListener('DOMContentLoaded', processIslands);
279
- } else {
280
- processIslands();
281
- }
282
- </script>`;
283
- }
284
-
285
- export default {
286
- Island,
287
- createIsland,
288
- createLazyIsland,
289
- getRegisteredIslands,
290
- generateHydrationScript,
291
- generateAdvancedHydrationScript,
292
- LoadStrategy
293
- };
package/core/loader.ts DELETED
@@ -1,111 +0,0 @@
1
- import { transformSync } from 'esbuild';
2
- import { readFileSync } from 'fs';
3
- import { fileURLToPath, pathToFileURL } from 'url';
4
- import path from 'path';
5
- import { createRequire } from 'module';
6
-
7
- const require = createRequire(import.meta.url);
8
-
9
- // Get the framework's React path to ensure single instance
10
- const frameworkReactPath = require.resolve('react');
11
- const frameworkReactDir = path.dirname(frameworkReactPath);
12
-
13
- /**
14
- * Custom ESM loader for JSX files
15
- * This allows Node.js to import .jsx files directly
16
- */
17
-
18
- export async function load(url, context, nextLoad) {
19
- // Handle .jsx, .tsx, and .ts files
20
- const isJsx = url.endsWith('.jsx') || url.includes('.jsx?');
21
- const isTsx = url.endsWith('.tsx') || url.includes('.tsx?');
22
- const isTs = url.endsWith('.ts') || url.includes('.ts?');
23
-
24
- if (isJsx || isTsx || isTs) {
25
- // Remove query string for file reading
26
- const cleanUrl = url.split('?')[0];
27
- const filePath = fileURLToPath(cleanUrl);
28
-
29
- // Read the source file
30
- let source = readFileSync(filePath, 'utf-8');
31
-
32
- // Remove 'use client', 'use server', 'use island' directives
33
- // These are handled at build time, not runtime
34
- source = source.replace(/^['"]use (client|server|island)['"];?\s*/m, '');
35
-
36
- // Determine the loader based on file extension
37
- const loader = isTsx ? 'tsx' : isTs ? 'ts' : 'jsx';
38
-
39
- // Transform to JS using esbuild
40
- // Use classic JSX transform to avoid jsx-runtime issues
41
- const result = transformSync(source, {
42
- loader,
43
- format: 'esm',
44
- jsx: 'transform',
45
- jsxFactory: 'React.createElement',
46
- jsxFragment: 'React.Fragment',
47
- target: 'node18'
48
- });
49
-
50
- return {
51
- format: 'module',
52
- source: result.code,
53
- shortCircuit: true
54
- };
55
- }
56
-
57
- // Let Node.js handle other file types
58
- return nextLoad(url, context);
59
- }
60
-
61
- /**
62
- * Resolve hook to ensure single React instance and handle TS/TSX imports
63
- */
64
- export async function resolve(specifier, context, nextResolve) {
65
- // Redirect react imports to framework's React
66
- if (specifier === 'react' || specifier === 'react-dom' || specifier.startsWith('react/') || specifier.startsWith('react-dom/')) {
67
- try {
68
- const resolved = require.resolve(specifier);
69
- return {
70
- url: pathToFileURL(resolved).href,
71
- shortCircuit: true
72
- };
73
- } catch {
74
- // Fall through to default resolution
75
- }
76
- }
77
-
78
- // Handle TypeScript file imports (resolve .ts/.tsx extensions)
79
- if (context.parentURL && !specifier.startsWith('node:') && !specifier.startsWith('data:')) {
80
- // Try to resolve with .tsx, .ts, .jsx extensions
81
- const extensions = ['.tsx', '.ts', '.jsx', '.js'];
82
- const parentPath = fileURLToPath(context.parentURL);
83
- const parentDir = path.dirname(parentPath);
84
-
85
- for (const ext of extensions) {
86
- try {
87
- const possiblePath = path.resolve(parentDir, specifier + ext);
88
- if (require('fs').existsSync(possiblePath)) {
89
- return {
90
- url: pathToFileURL(possiblePath).href,
91
- shortCircuit: true
92
- };
93
- }
94
-
95
- // Also try index files
96
- const indexPath = path.resolve(parentDir, specifier, 'index' + ext);
97
- if (require('fs').existsSync(indexPath)) {
98
- return {
99
- url: pathToFileURL(indexPath).href,
100
- shortCircuit: true
101
- };
102
- }
103
- } catch {
104
- // Continue to next extension
105
- }
106
- }
107
- }
108
-
109
- return nextResolve(specifier, context);
110
- }
111
-
package/core/logger.ts DELETED
@@ -1,242 +0,0 @@
1
- /**
2
- * FlexiReact Logger
3
- * Premium console output inspired by Next.js, Vite, and Bun
4
- */
5
-
6
- // ANSI color codes
7
- const colors = {
8
- reset: '\x1b[0m',
9
- bold: '\x1b[1m',
10
- dim: '\x1b[2m',
11
- italic: '\x1b[3m',
12
- underline: '\x1b[4m',
13
-
14
- // Text colors
15
- black: '\x1b[30m',
16
- red: '\x1b[31m',
17
- green: '\x1b[32m',
18
- yellow: '\x1b[33m',
19
- blue: '\x1b[34m',
20
- magenta: '\x1b[35m',
21
- cyan: '\x1b[36m',
22
- white: '\x1b[37m',
23
- gray: '\x1b[90m',
24
-
25
- // Bright colors
26
- brightRed: '\x1b[91m',
27
- brightGreen: '\x1b[92m',
28
- brightYellow: '\x1b[93m',
29
- brightBlue: '\x1b[94m',
30
- brightMagenta: '\x1b[95m',
31
- brightCyan: '\x1b[96m',
32
- brightWhite: '\x1b[97m',
33
-
34
- // Background colors
35
- bgRed: '\x1b[41m',
36
- bgGreen: '\x1b[42m',
37
- bgYellow: '\x1b[43m',
38
- bgBlue: '\x1b[44m',
39
- bgMagenta: '\x1b[45m',
40
- bgCyan: '\x1b[46m',
41
- };
42
-
43
- const c = colors;
44
-
45
- // Get current time formatted
46
- function getTime() {
47
- const now = new Date();
48
- return `${c.dim}${now.toLocaleTimeString('en-US', { hour12: false })}${c.reset}`;
49
- }
50
-
51
- // Status code colors
52
- function getStatusColor(status) {
53
- if (status >= 500) return c.red;
54
- if (status >= 400) return c.yellow;
55
- if (status >= 300) return c.cyan;
56
- if (status >= 200) return c.green;
57
- return c.white;
58
- }
59
-
60
- // Method colors
61
- function getMethodColor(method) {
62
- const methodColors = {
63
- GET: c.brightGreen,
64
- POST: c.brightBlue,
65
- PUT: c.brightYellow,
66
- PATCH: c.brightMagenta,
67
- DELETE: c.brightRed,
68
- OPTIONS: c.gray,
69
- HEAD: c.gray,
70
- };
71
- return methodColors[method] || c.white;
72
- }
73
-
74
- // Format time
75
- function formatTime(ms) {
76
- if (ms < 1) return `${c.gray}<1ms${c.reset}`;
77
- if (ms < 100) return `${c.green}${ms}ms${c.reset}`;
78
- if (ms < 500) return `${c.yellow}${ms}ms${c.reset}`;
79
- return `${c.red}${ms}ms${c.reset}`;
80
- }
81
-
82
- // FlexiReact ASCII Logo - Premium Design
83
- const LOGO = `
84
- ${c.green} ╭─────────────────────────────────────────────╮${c.reset}
85
- ${c.green} │${c.reset} ${c.green}│${c.reset}
86
- ${c.green} │${c.reset} ${c.brightGreen}⚡${c.reset} ${c.bold}${c.white}F L E X I R E A C T${c.reset} ${c.dim}v1.0.0${c.reset} ${c.green}│${c.reset}
87
- ${c.green} │${c.reset} ${c.green}│${c.reset}
88
- ${c.green} │${c.reset} ${c.dim}The Modern React Framework${c.reset} ${c.green}│${c.reset}
89
- ${c.green} │${c.reset} ${c.green}│${c.reset}
90
- ${c.green} ╰─────────────────────────────────────────────╯${c.reset}
91
- `;
92
-
93
- const MINI_LOGO = `${c.brightGreen}⚡${c.reset} ${c.bold}FlexiReact${c.reset}`;
94
-
95
- // Compact ready message like Next.js
96
- const READY_MSG = ` ${c.green}▲${c.reset} ${c.bold}Ready${c.reset} in`;
97
-
98
- export const logger = {
99
- // Show startup logo
100
- logo() {
101
- console.log(LOGO);
102
- },
103
-
104
- // Server started - Next.js style
105
- serverStart(config, startTime = Date.now()) {
106
- const { port, host, mode, pagesDir, islands, rsc } = config;
107
- const elapsed = Date.now() - startTime;
108
-
109
- console.log('');
110
- console.log(` ${c.green}▲${c.reset} ${c.bold}Ready${c.reset} in ${c.cyan}${elapsed}ms${c.reset}`);
111
- console.log('');
112
- console.log(` ${c.dim}┌${c.reset} ${c.bold}Local:${c.reset} ${c.cyan}http://${host}:${port}${c.reset}`);
113
- console.log(` ${c.dim}├${c.reset} ${c.bold}Environment:${c.reset} ${mode === 'development' ? `${c.yellow}development${c.reset}` : `${c.green}production${c.reset}`}`);
114
- if (islands) {
115
- console.log(` ${c.dim}├${c.reset} ${c.bold}Islands:${c.reset} ${c.green}enabled${c.reset}`);
116
- }
117
- if (rsc) {
118
- console.log(` ${c.dim}├${c.reset} ${c.bold}RSC:${c.reset} ${c.green}enabled${c.reset}`);
119
- }
120
- console.log(` ${c.dim}└${c.reset} ${c.bold}Pages:${c.reset} ${c.dim}${pagesDir}${c.reset}`);
121
- console.log('');
122
- },
123
-
124
- // HTTP request log - Compact single line like Next.js
125
- request(method: string, path: string, status: number, time: number, extra: { type?: string } = {}) {
126
- const methodColor = getMethodColor(method);
127
- const statusColor = getStatusColor(status);
128
- const timeStr = formatTime(time);
129
-
130
- // Route type badge
131
- let badge = '';
132
- if (extra.type === 'static' || extra.type === 'ssg') {
133
- badge = `${c.dim}○${c.reset}`; // Static
134
- } else if (extra.type === 'dynamic' || extra.type === 'ssr') {
135
- badge = `${c.magenta}ƒ${c.reset}`; // Function/SSR
136
- } else if (extra.type === 'api') {
137
- badge = `${c.blue}λ${c.reset}`; // API
138
- } else if (extra.type === 'asset') {
139
- badge = `${c.dim}◦${c.reset}`; // Asset
140
- } else {
141
- badge = `${c.magenta}ƒ${c.reset}`;
142
- }
143
-
144
- const statusStr = `${statusColor}${status}${c.reset}`;
145
- const methodStr = `${methodColor}${method}${c.reset}`;
146
-
147
- // Single line format like Next.js
148
- console.log(` ${badge} ${methodStr} ${path} ${statusStr} ${c.dim}in${c.reset} ${timeStr}`);
149
- },
150
-
151
- // Info message
152
- info(msg) {
153
- console.log(` ${c.cyan}ℹ${c.reset} ${msg}`);
154
- },
155
-
156
- // Success message
157
- success(msg) {
158
- console.log(` ${c.green}✓${c.reset} ${msg}`);
159
- },
160
-
161
- // Warning message
162
- warn(msg) {
163
- console.log(` ${c.yellow}⚠${c.reset} ${c.yellow}${msg}${c.reset}`);
164
- },
165
-
166
- // Error message
167
- error(msg, err = null) {
168
- console.log(` ${c.red}✗${c.reset} ${c.red}${msg}${c.reset}`);
169
- if (err && err.stack) {
170
- const stack = err.stack.split('\n').slice(1, 4).join('\n');
171
- console.log(`${c.dim}${stack}${c.reset}`);
172
- }
173
- },
174
-
175
- // Compilation message
176
- compile(file, time) {
177
- console.log(` ${c.magenta}◉${c.reset} Compiled ${c.cyan}${file}${c.reset} ${c.dim}(${time}ms)${c.reset}`);
178
- },
179
-
180
- // Hot reload
181
- hmr(file) {
182
- console.log(` ${c.yellow}↻${c.reset} HMR update: ${c.cyan}${file}${c.reset}`);
183
- },
184
-
185
- // Plugin loaded
186
- plugin(name) {
187
- console.log(` ${c.blue}⬡${c.reset} Plugin: ${c.cyan}${name}${c.reset}`);
188
- },
189
-
190
- // Route info
191
- route(path, type) {
192
- const typeColors = {
193
- static: c.green,
194
- dynamic: c.yellow,
195
- api: c.blue,
196
- };
197
- const color = typeColors[type] || c.white;
198
- console.log(` ${c.dim}├─${c.reset} ${path} ${color}[${type}]${c.reset}`);
199
- },
200
-
201
- // Divider
202
- divider() {
203
- console.log(`${c.dim} ─────────────────────────────────────────${c.reset}`);
204
- },
205
-
206
- // Blank line
207
- blank() {
208
- console.log('');
209
- },
210
-
211
- // Port in use error with solution
212
- portInUse(port) {
213
- console.log(`
214
- ${c.red} ✗ Port ${port} is already in use${c.reset}
215
-
216
- ${c.dim}Try one of these solutions:${c.reset}
217
-
218
- ${c.yellow}1.${c.reset} Kill the process using the port:
219
- ${c.cyan}npx kill-port ${port}${c.reset}
220
-
221
- ${c.yellow}2.${c.reset} Use a different port in ${c.cyan}flexireact.config.js${c.reset}:
222
- ${c.dim}server: { port: 3001 }${c.reset}
223
-
224
- ${c.yellow}3.${c.reset} Set PORT environment variable:
225
- ${c.cyan}PORT=3001 npm run dev${c.reset}
226
- `);
227
- },
228
-
229
- // Build info
230
- build(stats) {
231
- console.log(`
232
- ${c.green}✓${c.reset} Build complete!
233
-
234
- ${c.dim}├─${c.reset} Pages: ${c.cyan}${stats.pages}${c.reset}
235
- ${c.dim}├─${c.reset} API: ${c.cyan}${stats.api}${c.reset}
236
- ${c.dim}├─${c.reset} Assets: ${c.cyan}${stats.assets}${c.reset}
237
- ${c.dim}└─${c.reset} Time: ${c.green}${stats.time}ms${c.reset}
238
- `);
239
- },
240
- };
241
-
242
- export default logger;