fiboui 2.6.4 → 2.7.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/README.md +16 -6
- package/dist/cli/bin.js +3 -4
- package/dist/cli/commands/generate/component.js +21 -33
- package/dist/cli/commands/generate/index.js +3 -3
- package/dist/cli/commands/generate/module.js +21 -24
- package/dist/cli/commands/generate/plugin.js +18 -34
- package/dist/cli/commands/init.js +5 -25
- package/dist/cli/config.js +118 -0
- package/dist/cli/names.js +35 -0
- package/dist/cli/templates.js +76 -0
- package/dist/client-CGjHCx8F.js +17621 -0
- package/dist/content.d.ts +1 -0
- package/dist/content.js +14 -14
- package/dist/core.d.ts +2 -0
- package/dist/core.js +65 -59
- package/dist/effects-BxvduC-G.js +14 -0
- package/dist/icons-GEqYd2M9.js +383 -0
- package/dist/index-7bieEGAP.js +944 -0
- package/dist/index-C4qvvr03.js +1474 -0
- package/dist/index-D-ljLuf8.js +839 -0
- package/dist/index-DVApKYWt.js +217 -0
- package/dist/index-Db8lVjta.js +150 -0
- package/dist/index.d.ts +73156 -505
- package/dist/index.js +4419 -2238
- package/dist/interactive.d.ts +2 -0
- package/dist/interactive.js +22 -9
- package/dist/layout.d.ts +1 -0
- package/dist/layout.js +14 -13
- package/dist/metadata-3YYH1uiS.js +147 -0
- package/dist/navigation.d.ts +1 -0
- package/dist/navigation.js +15 -9
- package/dist/page-CYqQOLXe.js +266 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +5001 -1777
- package/dist/structure.d.ts +1 -0
- package/dist/structure.js +13 -9
- package/dist/style.css +1 -1
- package/dist/theme.d.ts +2 -0
- package/dist/theme.js +8 -8
- package/package.json +9 -2
- package/dist/Link-YjcbA4af.js +0 -50
- package/dist/PluginManager-BRjgOLVa.js +0 -110
- package/dist/client-DzQ13khL.js +0 -10605
- package/dist/effects-D16XdB_d.js +0 -10
- package/dist/index-B-cCNvV4.js +0 -455
- package/dist/index-Bj-IMHC8.js +0 -119
- package/dist/index-CWyBsVAL.js +0 -608
- package/dist/index-CYisDyI8.js +0 -602
- package/dist/index-Dil2ZG0X.js +0 -1028
- package/dist/mount-CDwiTTXM.js +0 -135
package/README.md
CHANGED
|
@@ -26,12 +26,12 @@ There is no JSX — the function return **is** the view.
|
|
|
26
26
|
|
|
27
27
|
### Live product
|
|
28
28
|
|
|
29
|
-
| Marketing site | Store
|
|
29
|
+
| Marketing site | Store (in-app `/shop`) |
|
|
30
30
|
| :---: | :---: |
|
|
31
|
-
| [](https://fiboui.vercel.app) | [](https://fiboui
|
|
32
|
-
| [**fiboui.vercel.app**](https://fiboui.vercel.app) | [**fiboui
|
|
31
|
+
| [](https://fiboui.vercel.app) | [](https://fiboui.vercel.app/shop) |
|
|
32
|
+
| [**fiboui.vercel.app**](https://fiboui.vercel.app) | [**fiboui.vercel.app/shop**](https://fiboui.vercel.app/shop) |
|
|
33
33
|
|
|
34
|
-
Both ships are built **only** with FiboUI fluent builders — no JSX in the product UI.
|
|
34
|
+
Both ships are built **only** with FiboUI fluent builders — no JSX in the product UI. The store is `/shop` (alias `/store`). The playground is [`/play`](https://fiboui.vercel.app/play) (alias `/playground`). Same Vercel app, not extra deployments.
|
|
35
35
|
|
|
36
36
|
</div>
|
|
37
37
|
|
|
@@ -71,7 +71,16 @@ function App() {
|
|
|
71
71
|
mount('#root', App)
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Tree-shakeable entries (each has its own `types`): `fiboui/core`, `fiboui/layout`, `fiboui/content`, `fiboui/interactive`, `fiboui/structure`, `fiboui/navigation`, `fiboui/theme`, `fiboui/server`.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { signal } from 'fiboui/core'
|
|
78
|
+
import { Column } from 'fiboui/layout'
|
|
79
|
+
import { Button } from 'fiboui/interactive'
|
|
80
|
+
import { page } from 'fiboui/server'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
ESM-only · React **18 / 19** · Next App Router: static builders + `page()` work in Server Components (`fiboui/server` / the `react-server` export); signals / Router still need `'use client'`.
|
|
75
84
|
|
|
76
85
|
---
|
|
77
86
|
|
|
@@ -247,6 +256,7 @@ Column().crossAxisAlignment('stretch')
|
|
|
247
256
|
|
|
248
257
|
| | |
|
|
249
258
|
| :--- | :--- |
|
|
259
|
+
| Getting started | [`docs/guide/getting-started.md`](./docs/guide/getting-started.md) |
|
|
250
260
|
| Migration from 1.x | [MIGRATION.md](./MIGRATION.md) |
|
|
251
261
|
| Contributing & release | [CONTRIBUTING.md](./CONTRIBUTING.md) |
|
|
252
262
|
| Animation guide | [`docs/guide/animation.md`](./docs/guide/animation.md) |
|
|
@@ -266,6 +276,6 @@ Publishing is automated on **`main`** (GitHub Actions + `NPM_TOKEN`). Prefer not
|
|
|
266
276
|
|
|
267
277
|
MIT © 2025–2026 FiboUI
|
|
268
278
|
|
|
269
|
-
[Site](https://fiboui.vercel.app) · [Store](https://fiboui
|
|
279
|
+
[Site](https://fiboui.vercel.app) · [Store](https://fiboui.vercel.app/shop) · [Play](https://fiboui.vercel.app/play) · [npm](https://www.npmjs.com/package/fiboui) · [GitHub](https://github.com/felipeoliveiradev/fiboui)
|
|
270
280
|
|
|
271
281
|
</div>
|
package/dist/cli/bin.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
-
import { generateCommand } from './commands/generate';
|
|
5
|
-
import { initCommand } from './commands/init';
|
|
4
|
+
import { generateCommand } from './commands/generate/index.js';
|
|
5
|
+
import { initCommand } from './commands/init.js';
|
|
6
6
|
const require = createRequire(import.meta.url);
|
|
7
7
|
const pkg = require('../../package.json');
|
|
8
8
|
const version = pkg.version;
|
|
@@ -11,10 +11,9 @@ program
|
|
|
11
11
|
.name('fibo')
|
|
12
12
|
.description('FiboUI CLI - Supercharge your development')
|
|
13
13
|
.version(version);
|
|
14
|
-
// ... (version import)
|
|
15
14
|
program.addCommand(initCommand);
|
|
16
15
|
program.addCommand(generateCommand);
|
|
17
|
-
program.
|
|
16
|
+
await program.parseAsync(process.argv);
|
|
18
17
|
if (!process.argv.slice(2).length) {
|
|
19
18
|
program.outputHelp();
|
|
20
19
|
}
|
|
@@ -4,64 +4,52 @@ import chalk from 'chalk';
|
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import ora from 'ora';
|
|
7
|
+
import { joinBasename, loadFiboConfig, resolveGenerateDir } from '../../config.js';
|
|
8
|
+
import { toPascalCase } from '../../names.js';
|
|
9
|
+
import { componentTemplate } from '../../templates.js';
|
|
7
10
|
export const componentCommand = new Command('component')
|
|
8
11
|
.alias('c')
|
|
9
12
|
.description('Generate a new FiboUI functional component')
|
|
10
13
|
.argument('[name]', 'Name of the component')
|
|
11
14
|
.action(async (name) => {
|
|
12
|
-
let
|
|
13
|
-
if (!
|
|
15
|
+
let rawName = name;
|
|
16
|
+
if (!rawName) {
|
|
14
17
|
const response = await prompts({
|
|
15
18
|
type: 'text',
|
|
16
19
|
name: 'value',
|
|
17
20
|
message: 'What is the name of your Component?',
|
|
18
|
-
validate: value => value.length < 3 ? 'Component name must be at least 3 characters' : true
|
|
21
|
+
validate: (value) => (value.length < 3 ? 'Component name must be at least 3 characters' : true),
|
|
19
22
|
});
|
|
20
|
-
|
|
23
|
+
rawName = response.value;
|
|
21
24
|
}
|
|
25
|
+
if (!rawName)
|
|
26
|
+
process.exit(1);
|
|
27
|
+
const componentName = toPascalCase(rawName);
|
|
22
28
|
if (!componentName)
|
|
23
29
|
process.exit(1);
|
|
24
30
|
const spinner = ora(`Scaffolding component ${chalk.bold(componentName)}...`).start();
|
|
25
|
-
// Try to guess location: src/components or current dir
|
|
26
|
-
const srcComponents = path.resolve(process.cwd(), 'src/components');
|
|
27
|
-
const targetDir = (await fs.pathExists(srcComponents)) ? srcComponents : process.cwd();
|
|
28
|
-
// Create folder for component? Or just file? Let's do file for simplicity or folder if preferred.
|
|
29
|
-
// Standard: src/components/MyComponent/index.tsx
|
|
30
|
-
const componentDir = path.join(targetDir, componentName);
|
|
31
|
-
const targetFile = path.join(componentDir, 'index.tsx');
|
|
32
31
|
try {
|
|
32
|
+
const cwd = process.cwd();
|
|
33
|
+
const config = await loadFiboConfig(cwd);
|
|
34
|
+
const srcComponents = path.resolve(cwd, 'src/components');
|
|
35
|
+
const targetDir = resolveGenerateDir('component', cwd, config, undefined, {
|
|
36
|
+
componentsDirExists: await fs.pathExists(srcComponents),
|
|
37
|
+
});
|
|
38
|
+
const componentDir = joinBasename(targetDir, componentName);
|
|
39
|
+
const targetFile = joinBasename(componentDir, 'index.tsx');
|
|
33
40
|
if (await fs.pathExists(componentDir)) {
|
|
34
41
|
spinner.fail(`Component ${componentName} already exists at ${componentDir}`);
|
|
42
|
+
process.exitCode = 1;
|
|
35
43
|
return;
|
|
36
44
|
}
|
|
37
45
|
await fs.ensureDir(componentDir);
|
|
38
|
-
|
|
39
|
-
import { Component, Text, Row, FiboBuilder } from 'fiboui';
|
|
40
|
-
|
|
41
|
-
export interface ${componentName}Props {
|
|
42
|
-
title?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const ${componentName} = Component<${componentName}Props>()(
|
|
46
|
-
({ title = '${componentName}' }) => {
|
|
47
|
-
return Row()
|
|
48
|
-
.padding(16)
|
|
49
|
-
.bg('white')
|
|
50
|
-
.rounded('lg')
|
|
51
|
-
.shadow('sm')
|
|
52
|
-
.children([
|
|
53
|
-
Text(title).size(18).weight('bold').build()
|
|
54
|
-
])
|
|
55
|
-
.build();
|
|
56
|
-
}
|
|
57
|
-
);
|
|
58
|
-
`;
|
|
59
|
-
await fs.writeFile(targetFile, content);
|
|
46
|
+
await fs.writeFile(targetFile, componentTemplate(componentName));
|
|
60
47
|
spinner.succeed(`Component ${chalk.green(componentName)} created successfully!`);
|
|
61
48
|
console.log(chalk.dim(`\nLocated at: ${targetFile}`));
|
|
62
49
|
}
|
|
63
50
|
catch (error) {
|
|
64
51
|
spinner.fail('Failed to create component');
|
|
65
52
|
console.error(error);
|
|
53
|
+
process.exitCode = 1;
|
|
66
54
|
}
|
|
67
55
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { moduleCommand } from './module';
|
|
3
|
-
import { pluginCommand } from './plugin';
|
|
4
|
-
import { componentCommand } from './component';
|
|
2
|
+
import { moduleCommand } from './module.js';
|
|
3
|
+
import { pluginCommand } from './plugin.js';
|
|
4
|
+
import { componentCommand } from './component.js';
|
|
5
5
|
export const generateCommand = new Command('generate')
|
|
6
6
|
.alias('g')
|
|
7
7
|
.description('Generate new architectural components (modules, plugins, etc.)')
|
|
@@ -4,55 +4,52 @@ import chalk from 'chalk';
|
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import ora from 'ora';
|
|
7
|
+
import { loadFiboConfig, resolveGenerateDir, resolveUnderRoot } from '../../config.js';
|
|
8
|
+
import { toPascalCase } from '../../names.js';
|
|
9
|
+
import { moduleFiles } from '../../templates.js';
|
|
7
10
|
export const moduleCommand = new Command('module')
|
|
8
11
|
.alias('m')
|
|
9
12
|
.description('Generate a new clean architecture module')
|
|
10
13
|
.argument('[name]', 'Name of the module')
|
|
11
14
|
.action(async (name) => {
|
|
12
|
-
let
|
|
13
|
-
if (!
|
|
15
|
+
let rawName = name;
|
|
16
|
+
if (!rawName) {
|
|
14
17
|
const response = await prompts({
|
|
15
18
|
type: 'text',
|
|
16
19
|
name: 'value',
|
|
17
20
|
message: 'What is the name of your module?',
|
|
18
|
-
validate: value => value.length < 3 ? 'Module name must be at least 3 characters' : true
|
|
21
|
+
validate: (value) => (value.length < 3 ? 'Module name must be at least 3 characters' : true),
|
|
19
22
|
});
|
|
20
|
-
|
|
23
|
+
rawName = response.value;
|
|
21
24
|
}
|
|
25
|
+
if (!rawName)
|
|
26
|
+
process.exit(1);
|
|
27
|
+
const moduleName = toPascalCase(rawName);
|
|
22
28
|
if (!moduleName)
|
|
23
29
|
process.exit(1);
|
|
24
30
|
const spinner = ora(`Scaffolding module ${chalk.bold(moduleName)}...`).start();
|
|
25
|
-
// Normalize path: assume we are in project root or src
|
|
26
|
-
const targetDir = path.resolve(process.cwd(), 'src/modules', moduleName);
|
|
27
31
|
try {
|
|
32
|
+
const cwd = process.cwd();
|
|
33
|
+
const config = await loadFiboConfig(cwd);
|
|
34
|
+
const targetDir = resolveGenerateDir('module', cwd, config, moduleName);
|
|
28
35
|
if (await fs.pathExists(targetDir)) {
|
|
29
36
|
spinner.fail(`Module ${moduleName} already exists at ${targetDir}`);
|
|
37
|
+
process.exitCode = 1;
|
|
30
38
|
return;
|
|
31
39
|
}
|
|
32
40
|
await fs.ensureDir(targetDir);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
await fs.ensureDir(path.join(targetDir, 'application/usecases'));
|
|
40
|
-
await fs.writeFile(path.join(targetDir, 'application/usecases', `Get${capitalize(moduleName)}.usecase.ts`), `import { I${capitalize(moduleName)}Repository } from '../../domain/repositories/${moduleName}.repository';\n\nexport class Get${capitalize(moduleName)}UseCase {\n constructor(private repo: I${capitalize(moduleName)}Repository) {}\n\n async execute() {\n return this.repo.getAll();\n }\n}\n`);
|
|
41
|
-
// 3. Infrastructure Layer
|
|
42
|
-
await fs.ensureDir(path.join(targetDir, 'infrastructure/api'));
|
|
43
|
-
await fs.writeFile(path.join(targetDir, 'infrastructure/api', `${moduleName}.api.ts`), `import { I${capitalize(moduleName)}Repository } from '../../domain/repositories/${moduleName}.repository';\nimport { ${capitalize(moduleName)} } from '../../domain/entities/${moduleName}.entity';\n\nexport class ${capitalize(moduleName)}ApiRepository implements I${capitalize(moduleName)}Repository {\n async getAll(): Promise<${capitalize(moduleName)}[]> {\n // Implement API call\n return [];\n }\n\n async getById(id: string): Promise<${capitalize(moduleName)} | null> {\n return null;\n }\n}\n`);
|
|
44
|
-
// 4. Presentation Layer (UI)
|
|
45
|
-
await fs.ensureDir(path.join(targetDir, 'presentation/components'));
|
|
46
|
-
await fs.ensureDir(path.join(targetDir, 'presentation/pages'));
|
|
47
|
-
await fs.writeFile(path.join(targetDir, 'presentation/pages', `${capitalize(moduleName)}Page.tsx`), `import React from 'react';\nimport { Column, Text, FiboBuilder } from 'fiboui';\n\nexport const ${capitalize(moduleName)}Page = () => \n Column()\n .center()\n .gap(20)\n .children([\n Text('${capitalize(moduleName)} Module').size(24).weight('bold').build()\n ])\n .build();\n`);
|
|
41
|
+
const files = moduleFiles(moduleName);
|
|
42
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
43
|
+
const full = resolveUnderRoot(targetDir, rel);
|
|
44
|
+
await fs.ensureDir(path.dirname(full));
|
|
45
|
+
await fs.writeFile(full, content);
|
|
46
|
+
}
|
|
48
47
|
spinner.succeed(`Module ${chalk.green(moduleName)} created successfully!`);
|
|
49
48
|
console.log(chalk.dim(`\nLocated at: ${targetDir}`));
|
|
50
49
|
}
|
|
51
50
|
catch (error) {
|
|
52
51
|
spinner.fail('Failed to create module');
|
|
53
52
|
console.error(error);
|
|
53
|
+
process.exitCode = 1;
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
|
-
function capitalize(str) {
|
|
57
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
58
|
-
}
|
|
@@ -2,60 +2,43 @@ import { Command } from 'commander';
|
|
|
2
2
|
import prompts from 'prompts';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
|
-
import path from 'path';
|
|
6
5
|
import ora from 'ora';
|
|
6
|
+
import { joinBasename, loadFiboConfig, resolveGenerateDir } from '../../config.js';
|
|
7
|
+
import { pluginNames } from '../../names.js';
|
|
8
|
+
import { pluginTemplate } from '../../templates.js';
|
|
7
9
|
export const pluginCommand = new Command('plugin')
|
|
8
10
|
.alias('p')
|
|
9
11
|
.description('Generate a new FiboUI plugin')
|
|
10
12
|
.argument('[name]', 'Name of the plugin')
|
|
11
13
|
.action(async (name) => {
|
|
12
|
-
let
|
|
13
|
-
if (!
|
|
14
|
+
let rawName = name;
|
|
15
|
+
if (!rawName) {
|
|
14
16
|
const response = await prompts({
|
|
15
17
|
type: 'text',
|
|
16
18
|
name: 'value',
|
|
17
19
|
message: 'What is the name of your Plugin?',
|
|
18
|
-
validate: value => value.length < 3 ? 'Plugin name must be at least 3 characters' : true
|
|
20
|
+
validate: (value) => (value.length < 3 ? 'Plugin name must be at least 3 characters' : true),
|
|
19
21
|
});
|
|
20
|
-
|
|
22
|
+
rawName = response.value;
|
|
21
23
|
}
|
|
22
|
-
if (!
|
|
24
|
+
if (!rawName)
|
|
25
|
+
process.exit(1);
|
|
26
|
+
const { safeName } = pluginNames(rawName);
|
|
27
|
+
if (!safeName)
|
|
23
28
|
process.exit(1);
|
|
24
|
-
const safeName = pluginName.endsWith('Plugin') ? pluginName : `${pluginName}Plugin`;
|
|
25
29
|
const spinner = ora(`Scaffolding plugin ${chalk.bold(safeName)}...`).start();
|
|
26
|
-
const targetDir = path.resolve(process.cwd(), 'src/plugins');
|
|
27
|
-
const targetFile = path.resolve(targetDir, `${safeName}.ts`);
|
|
28
30
|
try {
|
|
31
|
+
const cwd = process.cwd();
|
|
32
|
+
const config = await loadFiboConfig(cwd);
|
|
33
|
+
const targetDir = resolveGenerateDir('plugin', cwd, config);
|
|
34
|
+
const targetFile = joinBasename(targetDir, `${safeName}.ts`);
|
|
29
35
|
await fs.ensureDir(targetDir);
|
|
30
36
|
if (await fs.pathExists(targetFile)) {
|
|
31
37
|
spinner.fail(`Plugin ${safeName} already exists at ${targetFile}`);
|
|
38
|
+
process.exitCode = 1;
|
|
32
39
|
return;
|
|
33
40
|
}
|
|
34
|
-
|
|
35
|
-
import { Settings } from 'lucide-react';
|
|
36
|
-
|
|
37
|
-
export const ${safeName}: FiboPlugin = {
|
|
38
|
-
name: '${safeName}',
|
|
39
|
-
version: '1.0.0',
|
|
40
|
-
setup: (ctx: FiboContext) => {
|
|
41
|
-
console.log('Initializing ${safeName}...');
|
|
42
|
-
|
|
43
|
-
// Example: Register Sidebar Item
|
|
44
|
-
ctx.registerSidebarItem({
|
|
45
|
-
label: '${pluginName.replace('Plugin', '')}',
|
|
46
|
-
path: '/${pluginName.toLowerCase().replace('plugin', '')}',
|
|
47
|
-
icon: Settings, // Example icon
|
|
48
|
-
order: 10
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// Example: Register an Overlay
|
|
52
|
-
// ctx.registerOverlay(() =>
|
|
53
|
-
// Card().padding(10).children([ Text('Hello from ${safeName}').build() ]).build()
|
|
54
|
-
// );
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
`;
|
|
58
|
-
await fs.writeFile(targetFile, content);
|
|
41
|
+
await fs.writeFile(targetFile, pluginTemplate(rawName));
|
|
59
42
|
spinner.succeed(`Plugin ${chalk.green(safeName)} created successfully!`);
|
|
60
43
|
console.log(chalk.dim(`\nLocated at: ${targetFile}`));
|
|
61
44
|
console.log(chalk.blue(`\nNext step: Register it in your main.tsx using .plugins([${safeName}])`));
|
|
@@ -63,5 +46,6 @@ export const ${safeName}: FiboPlugin = {
|
|
|
63
46
|
catch (error) {
|
|
64
47
|
spinner.fail('Failed to create plugin');
|
|
65
48
|
console.error(error);
|
|
49
|
+
process.exitCode = 1;
|
|
66
50
|
}
|
|
67
51
|
});
|
|
@@ -3,49 +3,28 @@ import chalk from 'chalk';
|
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import ora from 'ora';
|
|
6
|
+
import { initConfigSource } from '../templates.js';
|
|
6
7
|
export const initCommand = new Command('init')
|
|
7
8
|
.description('Initialize FiboUI configuration')
|
|
8
9
|
.action(async () => {
|
|
9
10
|
const spinner = ora('Checking project structure...').start();
|
|
10
|
-
const configPath = path.resolve(process.cwd(), 'fibo.config.js');
|
|
11
|
+
const configPath = path.resolve(process.cwd(), 'fibo.config.js');
|
|
11
12
|
if (await fs.pathExists(configPath)) {
|
|
12
13
|
spinner.fail('fibo.config.js already exists.');
|
|
14
|
+
process.exitCode = 1;
|
|
13
15
|
return;
|
|
14
16
|
}
|
|
15
|
-
// Heuristics for standard architecture
|
|
16
17
|
const hasModules = await fs.pathExists(path.resolve(process.cwd(), 'src/modules'));
|
|
17
18
|
const hasPlugins = await fs.pathExists(path.resolve(process.cwd(), 'src/plugins'));
|
|
18
19
|
const hasComponents = await fs.pathExists(path.resolve(process.cwd(), 'src/components'));
|
|
19
20
|
const isStandard = hasModules && hasPlugins && hasComponents;
|
|
20
|
-
|
|
21
|
+
const configContent = initConfigSource(isStandard);
|
|
21
22
|
if (isStandard) {
|
|
22
23
|
spinner.succeed('Standard FiboUI architecture detected.');
|
|
23
|
-
configContent = `/** @type {import('fiboui').FiboConfig} */
|
|
24
|
-
module.exports = {
|
|
25
|
-
paths: {
|
|
26
|
-
modules: 'src/modules',
|
|
27
|
-
plugins: 'src/plugins',
|
|
28
|
-
components: 'src/components',
|
|
29
|
-
core: 'src/core'
|
|
30
|
-
},
|
|
31
|
-
architecture: 'clean'
|
|
32
|
-
};
|
|
33
|
-
`;
|
|
34
24
|
}
|
|
35
25
|
else {
|
|
36
26
|
spinner.warn('Standard architecture NOT detected.');
|
|
37
27
|
console.log(chalk.yellow('Creating configuration with default/empty values. Please update "fibo.config.js" with your project paths.'));
|
|
38
|
-
configContent = `/** @type {import('fiboui').FiboConfig} */
|
|
39
|
-
module.exports = {
|
|
40
|
-
paths: {
|
|
41
|
-
modules: 'src/modules', // Update this
|
|
42
|
-
plugins: 'src/plugins', // Update this
|
|
43
|
-
components: 'src/components', // Update this
|
|
44
|
-
core: 'src/core' // Update this
|
|
45
|
-
},
|
|
46
|
-
architecture: 'custom'
|
|
47
|
-
};
|
|
48
|
-
`;
|
|
49
28
|
}
|
|
50
29
|
try {
|
|
51
30
|
await fs.writeFile(configPath, configContent);
|
|
@@ -54,5 +33,6 @@ module.exports = {
|
|
|
54
33
|
}
|
|
55
34
|
catch (error) {
|
|
56
35
|
console.error(chalk.red('Failed to write config file.'), error);
|
|
36
|
+
process.exitCode = 1;
|
|
57
37
|
}
|
|
58
38
|
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const CONFIG_EXPORT_MARK = '__FIBO_CLI_CONFIG__';
|
|
8
|
+
const CONFIG_FILES = ['fibo.config.js', 'fibo.config.mjs', 'fibo.config.cjs'];
|
|
9
|
+
/** Parse `export default { … }` or `module.exports = { … }` data configs. */
|
|
10
|
+
export function parseFiboConfigSource(source) {
|
|
11
|
+
const trimmed = source.trim();
|
|
12
|
+
const body = trimmed.match(/export\s+default\s+(\{[\s\S]*\})\s*;?\s*$/)?.[1] ??
|
|
13
|
+
trimmed.match(/module\.exports\s*=\s*(\{[\s\S]*\})\s*;?\s*$/)?.[1];
|
|
14
|
+
if (!body)
|
|
15
|
+
return null;
|
|
16
|
+
const jsonish = body
|
|
17
|
+
.replace(/\/\/[^\n]*/g, '')
|
|
18
|
+
.replace(/'([^'\\]*)'/g, '"$1"')
|
|
19
|
+
.replace(/([,{]\s*)([A-Za-z_][\w]*)\s*:/g, '$1"$2":')
|
|
20
|
+
.replace(/,(\s*[}\]])/g, '$1');
|
|
21
|
+
try {
|
|
22
|
+
const cfg = JSON.parse(jsonish);
|
|
23
|
+
if (!cfg || typeof cfg !== 'object')
|
|
24
|
+
return null;
|
|
25
|
+
return { paths: cfg.paths ?? {}, architecture: cfg.architecture };
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function normalizeFiboConfig(raw) {
|
|
32
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
33
|
+
return null;
|
|
34
|
+
const cfg = raw;
|
|
35
|
+
if (cfg.paths != null && (typeof cfg.paths !== 'object' || Array.isArray(cfg.paths)))
|
|
36
|
+
return null;
|
|
37
|
+
return { paths: cfg.paths ?? {}, architecture: cfg.architecture };
|
|
38
|
+
}
|
|
39
|
+
/** Load `fibo.config.*` as a real Node module (computed ESM/CJS), not a JSON-ish regex. */
|
|
40
|
+
async function importConfigModule(full) {
|
|
41
|
+
const href = pathToFileURL(full).href;
|
|
42
|
+
const script = `import(${JSON.stringify(href)}).then((m)=>{process.stdout.write(${JSON.stringify(CONFIG_EXPORT_MARK)}+JSON.stringify(m.default??null))})`;
|
|
43
|
+
const { stdout } = await execFileAsync(process.execPath, ['--input-type=module', '-e', script], {
|
|
44
|
+
timeout: 8000,
|
|
45
|
+
maxBuffer: 256 * 1024,
|
|
46
|
+
windowsHide: true,
|
|
47
|
+
encoding: 'utf8',
|
|
48
|
+
});
|
|
49
|
+
const idx = stdout.lastIndexOf(CONFIG_EXPORT_MARK);
|
|
50
|
+
if (idx === -1)
|
|
51
|
+
return undefined;
|
|
52
|
+
return JSON.parse(stdout.slice(idx + CONFIG_EXPORT_MARK.length));
|
|
53
|
+
}
|
|
54
|
+
export async function loadFiboConfig(cwd) {
|
|
55
|
+
for (const name of CONFIG_FILES) {
|
|
56
|
+
const full = path.resolve(cwd, name);
|
|
57
|
+
if (!(await fs.pathExists(full)))
|
|
58
|
+
continue;
|
|
59
|
+
let loaded = null;
|
|
60
|
+
try {
|
|
61
|
+
loaded = normalizeFiboConfig(await importConfigModule(full));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
loaded = null;
|
|
65
|
+
}
|
|
66
|
+
if (!loaded) {
|
|
67
|
+
const source = await fs.readFile(full, 'utf8');
|
|
68
|
+
loaded = parseFiboConfigSource(source);
|
|
69
|
+
}
|
|
70
|
+
if (!loaded) {
|
|
71
|
+
throw new Error(`Invalid ${name}: could not load FiboUI config`);
|
|
72
|
+
}
|
|
73
|
+
return loaded;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
export function isInsideRoot(root, target) {
|
|
78
|
+
const resolvedRoot = path.resolve(root);
|
|
79
|
+
const resolvedTarget = path.resolve(target);
|
|
80
|
+
const rel = path.relative(resolvedRoot, resolvedTarget);
|
|
81
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel));
|
|
82
|
+
}
|
|
83
|
+
/** Join `dir` with a single path segment. Rejects `..`, separators, and empty names. */
|
|
84
|
+
export function joinBasename(dir, name) {
|
|
85
|
+
if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..' || name.includes('\0')) {
|
|
86
|
+
throw new Error(`Invalid path segment: ${name}`);
|
|
87
|
+
}
|
|
88
|
+
if (/[/\\]/.test(name) || path.basename(name) !== name) {
|
|
89
|
+
throw new Error(`Invalid path segment: ${name}`);
|
|
90
|
+
}
|
|
91
|
+
const resolved = path.resolve(dir, name);
|
|
92
|
+
if (!isInsideRoot(dir, resolved)) {
|
|
93
|
+
throw new Error(`Invalid path segment: ${name}`);
|
|
94
|
+
}
|
|
95
|
+
return resolved;
|
|
96
|
+
}
|
|
97
|
+
export function resolveUnderRoot(root, rel) {
|
|
98
|
+
if (typeof rel !== 'string' || rel.trim() === '') {
|
|
99
|
+
throw new Error(`Path escapes project: ${rel}`);
|
|
100
|
+
}
|
|
101
|
+
const resolved = path.resolve(root, rel);
|
|
102
|
+
if (!isInsideRoot(root, resolved)) {
|
|
103
|
+
throw new Error(`Path escapes project: ${rel}`);
|
|
104
|
+
}
|
|
105
|
+
return resolved;
|
|
106
|
+
}
|
|
107
|
+
export function resolveGenerateDir(kind, cwd, config, extra, opts) {
|
|
108
|
+
const paths = config?.paths ?? {};
|
|
109
|
+
if (kind === 'component') {
|
|
110
|
+
const rel = paths.components ?? (opts?.componentsDirExists ? 'src/components' : null);
|
|
111
|
+
return rel ? resolveUnderRoot(cwd, rel) : path.resolve(cwd);
|
|
112
|
+
}
|
|
113
|
+
if (kind === 'plugin') {
|
|
114
|
+
return resolveUnderRoot(cwd, paths.plugins ?? 'src/plugins');
|
|
115
|
+
}
|
|
116
|
+
const modulesRoot = resolveUnderRoot(cwd, paths.modules ?? 'src/modules');
|
|
117
|
+
return extra ? joinBasename(modulesRoot, extra) : modulesRoot;
|
|
118
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Turn `my-card` / `user_profile` / `Hero` into a TS identifier. */
|
|
2
|
+
export function toPascalCase(name) {
|
|
3
|
+
if (name == null)
|
|
4
|
+
return '';
|
|
5
|
+
const parts = String(name)
|
|
6
|
+
.replace(/[^a-zA-Z0-9]+/g, ' ')
|
|
7
|
+
.trim()
|
|
8
|
+
.split(/\s+/)
|
|
9
|
+
.filter(Boolean);
|
|
10
|
+
if (parts.length === 0)
|
|
11
|
+
return '';
|
|
12
|
+
const ident = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('');
|
|
13
|
+
return /^[0-9]/.test(ident) ? `N${ident}` : ident;
|
|
14
|
+
}
|
|
15
|
+
export function toKebabCase(name) {
|
|
16
|
+
const pascal = toPascalCase(name);
|
|
17
|
+
return pascal
|
|
18
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
19
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
20
|
+
.toLowerCase();
|
|
21
|
+
}
|
|
22
|
+
export function pluginNames(raw) {
|
|
23
|
+
const pascal = toPascalCase(raw);
|
|
24
|
+
if (!pascal)
|
|
25
|
+
return { safeName: '', label: '', path: '' };
|
|
26
|
+
// Strip a redundant "Plugin" suffix, but keep the stem when the name is only "Plugin"
|
|
27
|
+
// (otherwise label is empty and the sidebar path becomes "/").
|
|
28
|
+
const stripped = pascal.endsWith('Plugin') ? pascal.slice(0, -'Plugin'.length) : pascal;
|
|
29
|
+
const base = stripped || pascal;
|
|
30
|
+
return {
|
|
31
|
+
safeName: `${stripped}Plugin`,
|
|
32
|
+
label: base,
|
|
33
|
+
path: `/${toKebabCase(base)}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { pluginNames, toPascalCase } from './names.js';
|
|
2
|
+
/** 2.0 playground style: builders are React nodes, 2-space indent, no `.build()`. */
|
|
3
|
+
export function componentTemplate(name) {
|
|
4
|
+
const componentName = toPascalCase(name);
|
|
5
|
+
return `import { Row, Text } from 'fiboui';
|
|
6
|
+
|
|
7
|
+
export interface ${componentName}Props {
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function ${componentName}({ title = '${componentName}' }: ${componentName}Props) {
|
|
12
|
+
return Row()
|
|
13
|
+
.padding(16)
|
|
14
|
+
.bg('white')
|
|
15
|
+
.rounded('lg')
|
|
16
|
+
.shadow('sm')
|
|
17
|
+
.children([
|
|
18
|
+
Text(title).fontSize(18).fontWeight('bold'),
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
export function pluginTemplate(raw) {
|
|
24
|
+
const { safeName, label, path } = pluginNames(raw);
|
|
25
|
+
return `import type { FiboPlugin, FiboContext } from 'fiboui';
|
|
26
|
+
|
|
27
|
+
export const ${safeName}: FiboPlugin = {
|
|
28
|
+
name: '${safeName}',
|
|
29
|
+
version: '1.0.0',
|
|
30
|
+
setup: (ctx: FiboContext) => {
|
|
31
|
+
ctx.registerSidebarItem({
|
|
32
|
+
label: '${label}',
|
|
33
|
+
path: '${path}',
|
|
34
|
+
order: 10,
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
`;
|
|
39
|
+
}
|
|
40
|
+
export function modulePageTemplate(name) {
|
|
41
|
+
const moduleName = toPascalCase(name);
|
|
42
|
+
return `import { Column, Text } from 'fiboui';
|
|
43
|
+
|
|
44
|
+
export function ${moduleName}Page() {
|
|
45
|
+
return Column()
|
|
46
|
+
.center()
|
|
47
|
+
.gap(20)
|
|
48
|
+
.children([
|
|
49
|
+
Text('${moduleName} Module').variant('h2'),
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
export function moduleFiles(name) {
|
|
55
|
+
const moduleName = toPascalCase(name);
|
|
56
|
+
return {
|
|
57
|
+
[`domain/entities/${moduleName}.entity.ts`]: `export interface ${moduleName} {\n id: string;\n}\n`,
|
|
58
|
+
[`domain/repositories/${moduleName}.repository.ts`]: `import { ${moduleName} } from '../entities/${moduleName}.entity';\n\nexport interface I${moduleName}Repository {\n getAll(): Promise<${moduleName}[]>;\n getById(id: string): Promise<${moduleName} | null>;\n}\n`,
|
|
59
|
+
[`application/usecases/Get${moduleName}.usecase.ts`]: `import { I${moduleName}Repository } from '../../domain/repositories/${moduleName}.repository';\n\nexport class Get${moduleName}UseCase {\n constructor(private repo: I${moduleName}Repository) {}\n\n async execute() {\n return this.repo.getAll();\n }\n}\n`,
|
|
60
|
+
[`infrastructure/api/${moduleName}.api.ts`]: `import { I${moduleName}Repository } from '../../domain/repositories/${moduleName}.repository';\nimport { ${moduleName} } from '../../domain/entities/${moduleName}.entity';\n\nexport class ${moduleName}ApiRepository implements I${moduleName}Repository {\n async getAll(): Promise<${moduleName}[]> {\n return [];\n }\n\n async getById(id: string): Promise<${moduleName} | null> {\n return null;\n }\n}\n`,
|
|
61
|
+
[`presentation/pages/${moduleName}Page.tsx`]: modulePageTemplate(moduleName),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function initConfigSource(isStandard) {
|
|
65
|
+
const note = isStandard ? '' : ' // Update this';
|
|
66
|
+
return `export default {
|
|
67
|
+
paths: {
|
|
68
|
+
modules: 'src/modules',${note}
|
|
69
|
+
plugins: 'src/plugins',${note}
|
|
70
|
+
components: 'src/components',${note}
|
|
71
|
+
core: 'src/core',${note}
|
|
72
|
+
},
|
|
73
|
+
architecture: '${isStandard ? 'clean' : 'custom'}',
|
|
74
|
+
};
|
|
75
|
+
`;
|
|
76
|
+
}
|