lbr-web-toolkit 1.0.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 ADDED
@@ -0,0 +1,38 @@
1
+ ## @lbr/web-toolkit (skeleton inicial)
2
+
3
+ Toolkit de linha de comando para padronizar a criação de páginas, componentes e estruturas de UI em projetos web LBR (por exemplo, Farm4All Sementeira).
4
+
5
+ ### O que já foi feito neste skeleton
6
+
7
+ - **Projeto Node/TypeScript** isolado em `web-toolkit/` com:
8
+ - `package.json`, `tsconfig.json` e binário `lbr-web`.
9
+ - CLI baseada em `commander` + `inquirer`.
10
+ - **Detecção de projeto**:
11
+ - Lê `package.json` e `angular.json` na raiz do workspace.
12
+ - Identifica nome, versão do projeto e se é Angular (incluindo versão de `@angular/core` e `sourceRoot`).
13
+ - **Comandos iniciais**:
14
+ - `lbr-web info`: mostra as informações detectadas do projeto atual.
15
+ - `lbr-web init`: cria `.lbr-web-toolkit.json` com caminhos de `pages`, `shared/components`, `layout` e arquivo de rotas.
16
+
17
+ ### Como usar localmente neste repo
18
+
19
+ ```bash
20
+ cd web-toolkit
21
+ npm install
22
+ npm run build
23
+
24
+ # Executar CLI apontando para a raiz do frontend
25
+ node dist/cli.cjs info
26
+ node dist/cli.cjs init
27
+ ```
28
+
29
+ ### Próximos passos (não implementados ainda)
30
+
31
+ - Implementar motor de geração (core) para templates Angular.
32
+ - Adicionar comandos:
33
+ - `lbr-web generate page`
34
+ - `lbr-web generate component`
35
+ - `lbr-web generate crud`
36
+ - Criar pacote separado para adaptações específicas de Angular (rotas, sidebar, header) usando AST/Schematics.
37
+
38
+
package/dist/cli.js ADDED
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const commander_1 = require("commander");
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const inquirer_1 = __importDefault(require("inquirer"));
43
+ const fs_extra_1 = __importDefault(require("fs-extra"));
44
+ const path_1 = __importDefault(require("path"));
45
+ const project_detector_1 = require("./project-detector");
46
+ const config_1 = require("./config");
47
+ const generator_1 = require("./generator");
48
+ const program = new commander_1.Command();
49
+ program
50
+ .name('lbr-web')
51
+ .description('LBR Web Toolkit - CLI de geração e integração front-end reutilizável')
52
+ .version('1.0.0', '-v, --version', 'Mostra a versão do @lbr/web-toolkit');
53
+ program
54
+ .command('info')
55
+ .description('Mostra informações sobre o projeto atual e a detecção do toolkit')
56
+ .action(async () => {
57
+ const detected = await (0, project_detector_1.detectProject)();
58
+ console.log(chalk_1.default.cyan('\n🔍 Projeto detectado:\n'));
59
+ console.log(` • Root: ${chalk_1.default.green(detected.root)}`);
60
+ console.log(` • Nome: ${chalk_1.default.green(detected.name ?? 'desconhecido')}`);
61
+ console.log(` • Versão: ${chalk_1.default.green(detected.version ?? 'desconhecida')}`);
62
+ console.log(` • Framework: ${chalk_1.default.green(detected.framework)}`);
63
+ if (detected.angular) {
64
+ console.log(chalk_1.default.cyan('\n Angular:'));
65
+ console.log(` - Projeto: ${chalk_1.default.green(detected.angular.projectName ?? 'desconhecido')}`);
66
+ console.log(` - Versão: ${chalk_1.default.green(detected.angular.version ?? 'desconhecida')}`);
67
+ console.log(` - sourceRoot: ${chalk_1.default.green(detected.angular.sourceRoot ?? 'desconhecido')}`);
68
+ }
69
+ console.log();
70
+ });
71
+ program
72
+ .command('init')
73
+ .description('Inicializa configuração do LBR Web Toolkit no projeto atual')
74
+ .action(async () => {
75
+ const detected = await (0, project_detector_1.detectProject)();
76
+ console.log(chalk_1.default.cyan('\n🚀 Iniciando configuração do LBR Web Toolkit...\n'));
77
+ if (detected.framework !== 'angular') {
78
+ console.log(chalk_1.default.yellow(' Aviso: framework não identificado como Angular. A configuração será criada em modo genérico.'));
79
+ }
80
+ const answers = await inquirer_1.default.prompt([
81
+ {
82
+ name: 'pagesDir',
83
+ type: 'input',
84
+ message: 'Diretório de páginas (relativo à raiz do projeto):',
85
+ default: detected.angular?.sourceRoot ? `${detected.angular.sourceRoot}/app/pages` : 'src/app/pages',
86
+ },
87
+ {
88
+ name: 'sharedComponentsDir',
89
+ type: 'input',
90
+ message: 'Diretório de componentes compartilhados:',
91
+ default: detected.angular?.sourceRoot
92
+ ? `${detected.angular.sourceRoot}/app/shared/components`
93
+ : 'src/app/shared/components',
94
+ },
95
+ {
96
+ name: 'layoutDir',
97
+ type: 'input',
98
+ message: 'Diretório de layout (header/sidebar/etc):',
99
+ default: detected.angular?.sourceRoot
100
+ ? `${detected.angular.sourceRoot}/app/shared/layout`
101
+ : 'src/app/shared/layout',
102
+ },
103
+ {
104
+ name: 'routingFile',
105
+ type: 'input',
106
+ message: 'Arquivo principal de rotas:',
107
+ default: detected.angular?.sourceRoot ? `${detected.angular.sourceRoot}/app/app.routes.ts` : 'src/app/app.routes.ts',
108
+ },
109
+ ]);
110
+ const config = {
111
+ framework: detected.framework,
112
+ project: {
113
+ name: detected.name,
114
+ version: detected.version,
115
+ },
116
+ angular: detected.angular,
117
+ paths: {
118
+ pages: answers.pagesDir,
119
+ sharedComponents: answers.sharedComponentsDir,
120
+ layout: answers.layoutDir,
121
+ },
122
+ routing: {
123
+ file: answers.routingFile,
124
+ },
125
+ };
126
+ const configPath = path_1.default.join(process.cwd(), '.lbr-web-toolkit.json');
127
+ await fs_extra_1.default.writeJson(configPath, config, { spaces: 2 });
128
+ console.log(chalk_1.default.green(`\n✅ Configuração criada em ${configPath}\n`));
129
+ });
130
+ // Comandos de geração
131
+ const generate = program
132
+ .command('generate')
133
+ .aliases(['g', 'add'])
134
+ .description('Comandos de geração (páginas, componentes, CRUD, etc.)');
135
+ generate
136
+ .command('page')
137
+ .description('Gera uma nova página Angular standalone usando o template padrão LBR')
138
+ .action(async () => {
139
+ const cwd = process.cwd();
140
+ const cfg = await (0, config_1.loadToolkitConfig)(cwd);
141
+ if (!cfg) {
142
+ console.log(chalk_1.default.red('\n❌ Arquivo .lbr-web-toolkit.json não encontrado.\n Rode "lbr-web init" na raiz do projeto antes de gerar páginas.\n'));
143
+ process.exitCode = 1;
144
+ return;
145
+ }
146
+ // Descobrir categorias atuais do sidebar
147
+ const sidebarFile = path_1.default.join(cwd, cfg.paths.layout, 'app-sidebar', 'app-sidebar.component.ts');
148
+ let sidebarCategories = [];
149
+ try {
150
+ if (await fs_extra_1.default.pathExists(sidebarFile)) {
151
+ const sidebarContent = await fs_extra_1.default.readFile(sidebarFile, 'utf-8');
152
+ const catRegex = /\{\s*icon:\s*'([^']+)',\s*name:\s*"([^"]+)",\s*subItems:\s*\[/g;
153
+ let m;
154
+ while ((m = catRegex.exec(sidebarContent)) !== null) {
155
+ sidebarCategories.push({ icon: m[1], name: m[2] });
156
+ }
157
+ }
158
+ }
159
+ catch {
160
+ // ignore parsing errors
161
+ }
162
+ const defaultTitleBase = (name) => name
163
+ .split(/[-_\s]/)
164
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
165
+ .join(' ');
166
+ const answers = await inquirer_1.default.prompt([
167
+ {
168
+ name: 'name',
169
+ type: 'input',
170
+ message: 'Nome da página (kebab-case, ex: usuarios-list):',
171
+ validate: (val) => (!!val && /^[a-z0-9\-]+$/.test(val)) || 'Use apenas letras minúsculas, números e "-".',
172
+ },
173
+ {
174
+ name: 'subfolder',
175
+ type: 'input',
176
+ message: 'Subpasta dentro de pages (opcional, ex: admin):',
177
+ default: '',
178
+ },
179
+ {
180
+ name: 'headerTitle',
181
+ type: 'input',
182
+ message: 'Título para o header (opcional, enter para usar padrão):',
183
+ default: (ans) => defaultTitleBase(ans.name),
184
+ },
185
+ {
186
+ name: 'headerSubtitle',
187
+ type: 'input',
188
+ message: 'Descrição/subtítulo do header (opcional, enter para padrão):',
189
+ default: (ans) => `Gerencie ${defaultTitleBase(ans.name).toLowerCase()}`,
190
+ },
191
+ {
192
+ name: 'sidebarCategory',
193
+ type: 'list',
194
+ message: 'Onde adicionar no sidebar?',
195
+ choices: [
196
+ ...sidebarCategories.map((c) => ({
197
+ name: `${c.name} (${c.icon})`,
198
+ value: c.name,
199
+ })),
200
+ { name: 'Criar nova categoria...', value: '__NEW__' },
201
+ { name: 'Não adicionar ao sidebar', value: '__NONE__' },
202
+ ],
203
+ when: () => sidebarCategories.length > 0,
204
+ default: sidebarCategories[0]?.name,
205
+ },
206
+ {
207
+ name: 'newSidebarCategory',
208
+ type: 'input',
209
+ message: 'Nome da nova categoria do sidebar:',
210
+ when: (ans) => !sidebarCategories.length || ans.sidebarCategory === '__NEW__',
211
+ },
212
+ {
213
+ name: 'sidebarItemName',
214
+ type: 'input',
215
+ message: 'Nome do item no sidebar (como aparece no menu):',
216
+ default: (ans) => ans.headerTitle || defaultTitleBase(ans.name),
217
+ when: (ans) => ans.sidebarCategory !== '__NONE__',
218
+ },
219
+ {
220
+ name: 'sidebarIcon',
221
+ type: 'input',
222
+ message: 'Ícone do item no sidebar (ex: file-text, settings, users):',
223
+ default: 'file-text',
224
+ when: (ans) => ans.sidebarCategory !== '__NONE__',
225
+ },
226
+ ]);
227
+ const sidebarCategory = answers.sidebarCategory === '__NEW__' || !answers.sidebarCategory
228
+ ? answers.newSidebarCategory
229
+ : answers.sidebarCategory === '__NONE__'
230
+ ? undefined
231
+ : answers.sidebarCategory;
232
+ await (0, generator_1.generatePage)({
233
+ name: answers.name,
234
+ subfolder: answers.subfolder || undefined,
235
+ headerTitle: answers.headerTitle || undefined,
236
+ headerSubtitle: answers.headerSubtitle || undefined,
237
+ sidebarCategory: sidebarCategory || undefined,
238
+ sidebarItemName: answers.sidebarItemName || undefined,
239
+ sidebarIcon: answers.sidebarIcon || undefined,
240
+ }, cwd);
241
+ });
242
+ program
243
+ .command('version')
244
+ .alias('v')
245
+ .description('Mostra a versão do CLI e do projeto atual, e compara com a última publicada')
246
+ .action(async () => {
247
+ // Versão do CLI (package.json do próprio toolkit)
248
+ const cliPkgPath = path_1.default.join(__dirname, '..', 'package.json');
249
+ let cliVersion = 'desconhecida';
250
+ try {
251
+ const cliPkg = await fs_extra_1.default.readJson(cliPkgPath);
252
+ cliVersion = cliPkg.version ?? cliVersion;
253
+ }
254
+ catch {
255
+ // ignore
256
+ }
257
+ const detected = await (0, project_detector_1.detectProject)();
258
+ // Tenta buscar última versão publicada no npm
259
+ let latestVersion = null;
260
+ try {
261
+ const res = await fetchLatestNpmVersion('lbr-web-toolkit');
262
+ latestVersion = res;
263
+ }
264
+ catch {
265
+ latestVersion = null;
266
+ }
267
+ console.log(chalk_1.default.cyan('\n📦 Versões\n'));
268
+ console.log(` • CLI local (@lbr/web-toolkit): ${chalk_1.default.green(cliVersion)}`);
269
+ if (latestVersion) {
270
+ console.log(` • CLI publicado (npm): ${chalk_1.default.green(latestVersion)}`);
271
+ if (latestVersion !== cliVersion) {
272
+ console.log(chalk_1.default.yellow(' • Aviso: a versão instalada não é a mais recente publicada no npm.'));
273
+ }
274
+ }
275
+ else {
276
+ console.log(chalk_1.default.yellow(' • Não foi possível consultar a última versão publicada no npm (sem conexão ou ainda não publicado).'));
277
+ }
278
+ console.log(` • Projeto: ${chalk_1.default.green(detected.name ?? 'desconhecido')} (${detected.version ?? 'desconhecida'})`);
279
+ if (detected.angular?.version) {
280
+ console.log(` • Angular: ${chalk_1.default.green(detected.angular.version)}`);
281
+ }
282
+ console.log();
283
+ });
284
+ async function fetchLatestNpmVersion(pkgName) {
285
+ const https = await Promise.resolve().then(() => __importStar(require('https')));
286
+ const url = `https://registry.npmjs.org/${encodeURIComponent(pkgName)}/latest`;
287
+ return new Promise((resolve) => {
288
+ https.get(url, (res) => {
289
+ let data = '';
290
+ res.on('data', (chunk) => (data += chunk));
291
+ res.on('end', () => {
292
+ try {
293
+ const json = JSON.parse(data);
294
+ resolve(json.version ?? null);
295
+ }
296
+ catch {
297
+ resolve(null);
298
+ }
299
+ });
300
+ }).on('error', () => resolve(null));
301
+ });
302
+ }
303
+ program.parseAsync(process.argv);
package/dist/config.js ADDED
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadToolkitConfig = loadToolkitConfig;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ async function loadToolkitConfig(cwd = process.cwd()) {
10
+ const configPath = path_1.default.join(cwd, '.lbr-web-toolkit.json');
11
+ if (!(await fs_extra_1.default.pathExists(configPath))) {
12
+ return null;
13
+ }
14
+ const raw = await fs_extra_1.default.readJson(configPath);
15
+ return raw;
16
+ }
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generatePage = generatePage;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const config_1 = require("./config");
11
+ const integration_1 = require("./integration");
12
+ async function generatePage(options, cwd = process.cwd()) {
13
+ const config = await (0, config_1.loadToolkitConfig)(cwd);
14
+ if (!config) {
15
+ throw new Error('Arquivo .lbr-web-toolkit.json não encontrado. Rode "lbr-web init" primeiro na raiz do projeto.');
16
+ }
17
+ const pagesRoot = path_1.default.join(cwd, config.paths.pages);
18
+ const targetDir = path_1.default.join(pagesRoot, options.subfolder ?? '', options.name);
19
+ await fs_extra_1.default.ensureDir(targetDir);
20
+ const className = toPascalCase(options.name) + 'PageComponent';
21
+ const selector = 'app-' + toKebabCase(options.name);
22
+ const tsPath = path_1.default.join(targetDir, `${options.name}.component.ts`);
23
+ const htmlPath = path_1.default.join(targetDir, `${options.name}.component.html`);
24
+ const tsContent = `import { Component } from '@angular/core';
25
+ import { CommonModule } from '@angular/common';
26
+
27
+ @Component({
28
+ selector: '${selector}',
29
+ standalone: true,
30
+ imports: [CommonModule],
31
+ templateUrl: './${options.name}.component.html',
32
+ })
33
+ export class ${className} {
34
+ isLoading = true;
35
+
36
+ demoTableData = [
37
+ { id: 1, name: 'Exemplo 1', status: 'Ativo' },
38
+ { id: 2, name: 'Exemplo 2', status: 'Pendente' },
39
+ ];
40
+
41
+ constructor() {
42
+ // Simula carregamento inicial
43
+ setTimeout(() => {
44
+ this.isLoading = false;
45
+ }, 800);
46
+ }
47
+ }
48
+ `;
49
+ const htmlContent = `<div class="p-4 sm:p-6 xl:p-8">
50
+ <!-- Header da página -->
51
+ <div class="flex items-center justify-between mb-6">
52
+ <div>
53
+ <h1 class="text-2xl font-bold text-gray-900 dark:text-white">
54
+ ${className}
55
+ </h1>
56
+ <p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
57
+ Página gerada pelo @lbr/web-toolkit. Use este layout como base para a sua feature.
58
+ </p>
59
+ </div>
60
+ </div>
61
+
62
+ <!-- Área de loading global da página -->
63
+ <div *ngIf="isLoading" class="flex items-center justify-center py-16">
64
+ <div class="flex items-center gap-3 text-gray-500 dark:text-gray-300">
65
+ <span class="inline-flex h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-transparent"></span>
66
+ <span>Carregando dados iniciais...</span>
67
+ </div>
68
+ </div>
69
+
70
+ <!-- Conteúdo principal -->
71
+ <div *ngIf="!isLoading" class="space-y-6">
72
+ <!-- Bloco de exemplo: cards/estatísticas -->
73
+ <div class="grid grid-cols-1 gap-4 md:grid-cols-3">
74
+ <div class="rounded-2xl border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/5">
75
+ <p class="text-sm text-gray-500 dark:text-gray-400">Total de registros</p>
76
+ <p class="mt-2 text-2xl font-semibold text-gray-900 dark:text-white">2</p>
77
+ </div>
78
+ <div class="rounded-2xl border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/5">
79
+ <p class="text-sm text-gray-500 dark:text-gray-400">Status principal</p>
80
+ <p class="mt-2 text-2xl font-semibold text-emerald-500">Ativo</p>
81
+ </div>
82
+ <div class="rounded-2xl border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/5">
83
+ <p class="text-sm text-gray-500 dark:text-gray-400">Observações</p>
84
+ <p class="mt-2 text-sm text-gray-700 dark:text-gray-200">
85
+ Substitua estes cards pelos indicadores da sua tela.
86
+ </p>
87
+ </div>
88
+ </div>
89
+
90
+ <!-- Bloco de exemplo: tabela simples -->
91
+ <div class="rounded-2xl border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/5">
92
+ <div class="flex items-center justify-between mb-4">
93
+ <h2 class="text-lg font-semibold text-gray-900 dark:text-white">
94
+ Lista de exemplos
95
+ </h2>
96
+ <button
97
+ type="button"
98
+ class="inline-flex items-center rounded-lg bg-emerald-600 px-3 py-2 text-sm font-medium text-white hover:bg-emerald-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 dark:ring-offset-slate-900"
99
+ >
100
+ Novo registro
101
+ </button>
102
+ </div>
103
+
104
+ <div class="overflow-x-auto">
105
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-800 text-sm">
106
+ <thead class="bg-gray-50 dark:bg-white/5">
107
+ <tr>
108
+ <th class="px-4 py-2 text-left font-medium text-gray-700 dark:text-gray-200">ID</th>
109
+ <th class="px-4 py-2 text-left font-medium text-gray-700 dark:text-gray-200">Nome</th>
110
+ <th class="px-4 py-2 text-left font-medium text-gray-700 dark:text-gray-200">Status</th>
111
+ </tr>
112
+ </thead>
113
+ <tbody class="divide-y divide-gray-200 dark:divide-gray-800">
114
+ <tr *ngFor="let item of demoTableData">
115
+ <td class="px-4 py-2 text-gray-800 dark:text-gray-100">{{ item.id }}</td>
116
+ <td class="px-4 py-2 text-gray-800 dark:text-gray-100">{{ item.name }}</td>
117
+ <td class="px-4 py-2">
118
+ <span
119
+ class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium"
120
+ [ngClass]="{
121
+ 'bg-emerald-100 text-emerald-700': item.status === 'Ativo',
122
+ 'bg-amber-100 text-amber-700': item.status === 'Pendente'
123
+ }"
124
+ >
125
+ {{ item.status }}
126
+ </span>
127
+ </td>
128
+ </tr>
129
+ </tbody>
130
+ </table>
131
+ </div>
132
+
133
+ <p class="mt-4 text-xs text-gray-500 dark:text-gray-400">
134
+ Dica: troque este array <code>demoTableData</code> no componente por dados reais vindos do seu serviço.
135
+ </p>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ `;
140
+ await fs_extra_1.default.writeFile(tsPath, tsContent, 'utf-8');
141
+ await fs_extra_1.default.writeFile(htmlPath, htmlContent, 'utf-8');
142
+ console.log(chalk_1.default.green('\n✅ Página gerada com sucesso!'));
143
+ console.log(chalk_1.default.cyan(` Diretório: ${targetDir}\n`));
144
+ // Integração com rotas, header e sidebar
145
+ const routePath = [
146
+ options.subfolder?.replace(/^\/|\/$/g, ''),
147
+ options.name.replace(/^\/|\/$/g, ''),
148
+ ]
149
+ .filter(Boolean)
150
+ .join('/');
151
+ try {
152
+ await (0, integration_1.integratePage)({
153
+ name: options.name,
154
+ subfolder: options.subfolder,
155
+ className,
156
+ routePath,
157
+ headerTitle: options.headerTitle,
158
+ headerSubtitle: options.headerSubtitle,
159
+ sidebarCategory: options.sidebarCategory,
160
+ sidebarItemName: options.sidebarItemName,
161
+ sidebarIcon: options.sidebarIcon,
162
+ }, config, cwd);
163
+ }
164
+ catch (err) {
165
+ console.log(chalk_1.default.red(`⚠️ Erro ao integrar página: ${err?.message ?? err}`));
166
+ }
167
+ }
168
+ function toPascalCase(str) {
169
+ return str
170
+ .split(/[-_\s]/)
171
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
172
+ .join('');
173
+ }
174
+ function toKebabCase(str) {
175
+ return str
176
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
177
+ .replace(/[\s_]+/g, '-')
178
+ .toLowerCase();
179
+ }