@perma-tools/cli 0.0.1
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 +159 -0
- package/dist/actions.d.ts +12 -0
- package/dist/actions.js +157 -0
- package/dist/cli-parser.d.ts +21 -0
- package/dist/cli-parser.js +141 -0
- package/dist/configs/axios.config.d.ts +2 -0
- package/dist/configs/axios.config.js +12 -0
- package/dist/configs/biome.config.d.ts +2 -0
- package/dist/configs/biome.config.js +12 -0
- package/dist/configs/cookies-next.config.d.ts +2 -0
- package/dist/configs/cookies-next.config.js +6 -0
- package/dist/configs/date-fns.config.d.ts +2 -0
- package/dist/configs/date-fns.config.js +6 -0
- package/dist/configs/eslint-prettier.config.d.ts +2 -0
- package/dist/configs/eslint-prettier.config.js +35 -0
- package/dist/configs/framer-motion.config.d.ts +2 -0
- package/dist/configs/framer-motion.config.js +6 -0
- package/dist/configs/ky.config.d.ts +2 -0
- package/dist/configs/ky.config.js +12 -0
- package/dist/configs/lucide-react.config.d.ts +2 -0
- package/dist/configs/lucide-react.config.js +6 -0
- package/dist/configs/nuqs.config.d.ts +2 -0
- package/dist/configs/nuqs.config.js +12 -0
- package/dist/configs/react-hook-form.config.d.ts +2 -0
- package/dist/configs/react-hook-form.config.js +6 -0
- package/dist/configs/recharts.config.d.ts +2 -0
- package/dist/configs/recharts.config.js +6 -0
- package/dist/configs/tanstack-query.config.d.ts +2 -0
- package/dist/configs/tanstack-query.config.js +19 -0
- package/dist/configs/zod.config.d.ts +2 -0
- package/dist/configs/zod.config.js +6 -0
- package/dist/dependency-versions.d.ts +13 -0
- package/dist/dependency-versions.js +50 -0
- package/dist/git-setup.d.ts +23 -0
- package/dist/git-setup.js +275 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +537 -0
- package/dist/project-generator.d.ts +15 -0
- package/dist/project-generator.js +311 -0
- package/dist/snippet-manager.d.ts +11 -0
- package/dist/snippet-manager.js +76 -0
- package/dist/snippets/axios/index.ts +6 -0
- package/dist/snippets/biome/biome.json +93 -0
- package/dist/snippets/eslint-prettier/eslintignore +6 -0
- package/dist/snippets/eslint-prettier/eslintrc.js +3 -0
- package/dist/snippets/eslint-prettier/prettierignore +9 -0
- package/dist/snippets/eslint-prettier/prettierrc.js +9 -0
- package/dist/snippets/ky/index.ts +5 -0
- package/dist/snippets/react-query/index.tsx +53 -0
- package/dist/snippets/vscode/extensions.json +3 -0
- package/dist/snippets/vscode/settings.json +19 -0
- package/dist/templates/frontend/next15/base/.env.example +3 -0
- package/dist/templates/frontend/next15/base/README.md +50 -0
- package/dist/templates/frontend/next15/base/next.config.ts +7 -0
- package/dist/templates/frontend/next15/base/package.json +23 -0
- package/dist/templates/frontend/next15/base/postcss.config.mjs +8 -0
- package/dist/templates/frontend/next15/base/public/public.md +12 -0
- package/dist/templates/frontend/next15/base/src/@types/@types.md +7 -0
- package/dist/templates/frontend/next15/base/src/@types/types.d.ts +1 -0
- package/dist/templates/frontend/next15/base/src/app/(application)/page.tsx +50 -0
- package/dist/templates/frontend/next15/base/src/app/favicon.ico +0 -0
- package/dist/templates/frontend/next15/base/src/app/layout.tsx +23 -0
- package/dist/templates/frontend/next15/base/src/app/providers.tsx +5 -0
- package/dist/templates/frontend/next15/base/src/components/components.md +9 -0
- package/dist/templates/frontend/next15/base/src/constants/constants.md +10 -0
- package/dist/templates/frontend/next15/base/src/hooks/hooks.md +11 -0
- package/dist/templates/frontend/next15/base/src/lib/lib.md +9 -0
- package/dist/templates/frontend/next15/base/src/services/services.md +14 -0
- package/dist/templates/frontend/next15/base/src/styles/globals.css +1 -0
- package/dist/templates/frontend/next15/base/src/types/types.md +15 -0
- package/dist/templates/frontend/next15/base/src/utils/utils.md +18 -0
- package/dist/templates/frontend/next15/base/tsconfig.json +26 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.js +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { spinner } from "@clack/prompts";
|
|
2
|
+
import { cp, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { ActionExecutor } from "./actions.js";
|
|
6
|
+
import { getDependencyVersion } from "./dependency-versions.js";
|
|
7
|
+
import { SnippetManager } from "./snippet-manager.js";
|
|
8
|
+
export class ProjectGenerator {
|
|
9
|
+
snippetManager;
|
|
10
|
+
rootPath;
|
|
11
|
+
constructor(rootPath) {
|
|
12
|
+
this.snippetManager = new SnippetManager();
|
|
13
|
+
// Se rootPath não for fornecido, calcula a partir da localização deste arquivo
|
|
14
|
+
// __dirname alternativa para ES modules
|
|
15
|
+
if (!rootPath) {
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
// Detectar se está em desenvolvimento ou produção
|
|
19
|
+
// Em desenvolvimento: __dirname = packages/cli/
|
|
20
|
+
// Em produção (NPM): __dirname = node_modules/@perma-tools/cli/dist/
|
|
21
|
+
// Em ambos os casos, templates e snippets estão no mesmo nível
|
|
22
|
+
// Dev: packages/cli/ (arquivos .ts ao lado de templates/ e snippets/)
|
|
23
|
+
// Prod: dist/ (arquivos .js ao lado de templates/ e snippets/ copiados)
|
|
24
|
+
this.rootPath = __dirname;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
this.rootPath = rootPath;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async generate(config) {
|
|
31
|
+
const s = spinner();
|
|
32
|
+
try {
|
|
33
|
+
// 1. Copiar template base
|
|
34
|
+
s.start("📁 Copiando template base...");
|
|
35
|
+
await this.copyTemplate(config);
|
|
36
|
+
s.stop("✅ Template copiado!");
|
|
37
|
+
// 2. Processar snippets
|
|
38
|
+
const snippets = this.snippetManager.getSnippetsForProject(config);
|
|
39
|
+
if (snippets.length > 0) {
|
|
40
|
+
s.start("🔧 Configurando bibliotecas...");
|
|
41
|
+
await this.processSnippets(config, snippets);
|
|
42
|
+
s.stop("✅ Bibliotecas configuradas!");
|
|
43
|
+
}
|
|
44
|
+
// 3. Atualizar package.json
|
|
45
|
+
s.start("📦 Atualizando dependências...");
|
|
46
|
+
await this.updatePackageJson(config, snippets);
|
|
47
|
+
s.stop("✅ Dependências atualizadas!");
|
|
48
|
+
// 4. Criar .env.example
|
|
49
|
+
s.start("⚙️ Criando arquivos de configuração...");
|
|
50
|
+
await this.createEnvFile(config);
|
|
51
|
+
s.stop("✅ Configuração criada!");
|
|
52
|
+
// 5. Criar .vscode (sempre, se for frontend)
|
|
53
|
+
s.start("🔧 Configurando VSCode...");
|
|
54
|
+
await this.createVscodeConfig(config);
|
|
55
|
+
s.stop("✅ VSCode configurado!");
|
|
56
|
+
// 6. Instalar dependências (se o usuário quiser)
|
|
57
|
+
if (config.shouldInstallDeps) {
|
|
58
|
+
s.start(`📦 Instalando dependências com ${config.packageManager}...`);
|
|
59
|
+
await this.installDependencies(config);
|
|
60
|
+
s.stop("✅ Dependências instaladas!");
|
|
61
|
+
}
|
|
62
|
+
// 7. Configurar shadcn/ui (se o usuário quiser)
|
|
63
|
+
if (config.withShadcn) {
|
|
64
|
+
s.start("🎨 Configurando shadcn/ui...");
|
|
65
|
+
await this.setupShadcn(config);
|
|
66
|
+
s.stop("✅ shadcn/ui configurado!");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
s.stop("❌ Erro durante a geração");
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async copyTemplate(config) {
|
|
75
|
+
const templatePath = this.getTemplatePath(config);
|
|
76
|
+
const projectPath = config.projectPath;
|
|
77
|
+
// Copiar template recursivamente
|
|
78
|
+
await cp(templatePath, projectPath, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
getTemplatePath(config) {
|
|
81
|
+
if (config.applicationType === "frontend") {
|
|
82
|
+
if (config.framework === "nextjs-15") {
|
|
83
|
+
return join(this.rootPath, "templates/frontend/next15/base");
|
|
84
|
+
}
|
|
85
|
+
else if (config.framework === "vitejs-6") {
|
|
86
|
+
return join(this.rootPath, "templates/frontend/vite6/base");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (config.applicationType === "backend") {
|
|
90
|
+
return join(this.rootPath, "templates/backend/base");
|
|
91
|
+
}
|
|
92
|
+
throw new Error("Template não encontrado para a configuração especificada");
|
|
93
|
+
}
|
|
94
|
+
async processSnippets(config, snippets) {
|
|
95
|
+
const executor = new ActionExecutor(config.projectPath, this.rootPath, config.packageManager);
|
|
96
|
+
// Coletar todas as ações de todos os snippets
|
|
97
|
+
const allActions = snippets.flatMap((snippet) => snippet.actions);
|
|
98
|
+
// Ordenar por prioridade
|
|
99
|
+
allActions.sort((a, b) => (a.priority ?? 999) - (b.priority ?? 999));
|
|
100
|
+
// Executar todas as ações
|
|
101
|
+
for (const action of allActions) {
|
|
102
|
+
await executor.execute(action);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async updatePackageJson(config, snippets) {
|
|
106
|
+
const packageJsonPath = join(config.projectPath, "package.json");
|
|
107
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
|
|
108
|
+
// Obter todas as dependências dos snippets
|
|
109
|
+
const { dependencies, devDependencies } = this.snippetManager.getAllDependencies(snippets);
|
|
110
|
+
// Adicionar dependências com versões específicas
|
|
111
|
+
if (dependencies.length > 0) {
|
|
112
|
+
packageJson.dependencies = packageJson.dependencies || {};
|
|
113
|
+
for (const dep of dependencies) {
|
|
114
|
+
packageJson.dependencies[dep] = getDependencyVersion(dep);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (devDependencies.length > 0) {
|
|
118
|
+
packageJson.devDependencies = packageJson.devDependencies || {};
|
|
119
|
+
for (const dep of devDependencies) {
|
|
120
|
+
packageJson.devDependencies[dep] = getDependencyVersion(dep);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Atualizar nome do projeto
|
|
124
|
+
if (config.projectName) {
|
|
125
|
+
packageJson.name = config.projectName;
|
|
126
|
+
}
|
|
127
|
+
// Adicionar scripts do linter
|
|
128
|
+
packageJson.scripts = packageJson.scripts || {};
|
|
129
|
+
if (config.linter === "biome") {
|
|
130
|
+
packageJson.scripts.lint = "biome check .";
|
|
131
|
+
packageJson.scripts["lint:fix"] = "biome check --write .";
|
|
132
|
+
packageJson.scripts.format = "biome format --write .";
|
|
133
|
+
}
|
|
134
|
+
else if (config.linter === "eslint-prettier") {
|
|
135
|
+
packageJson.scripts.lint = "next lint";
|
|
136
|
+
packageJson.scripts["lint:fix"] = "eslint . --fix";
|
|
137
|
+
packageJson.scripts.format = "prettier --write .";
|
|
138
|
+
}
|
|
139
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), "utf-8");
|
|
140
|
+
}
|
|
141
|
+
async createEnvFile(config) {
|
|
142
|
+
const envPath = join(config.projectPath, ".env.example");
|
|
143
|
+
let envContent = "# Environment Variables\n\n";
|
|
144
|
+
// Adicionar variáveis baseadas nas escolhas
|
|
145
|
+
if (config.httpClient === "ky") {
|
|
146
|
+
envContent += "NEXT_PUBLIC_API_URL=http://localhost:3000/api\n";
|
|
147
|
+
}
|
|
148
|
+
else if (config.httpClient === "axios") {
|
|
149
|
+
envContent += "REACT_APP_API_BASE_URL=http://localhost:3000/api\n";
|
|
150
|
+
}
|
|
151
|
+
await writeFile(envPath, envContent, "utf-8");
|
|
152
|
+
}
|
|
153
|
+
async installDependencies(config) {
|
|
154
|
+
const executor = new ActionExecutor(config.projectPath, this.rootPath, config.packageManager);
|
|
155
|
+
await executor.execute({
|
|
156
|
+
type: "install-deps",
|
|
157
|
+
dependencies: [],
|
|
158
|
+
devDependencies: [],
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
async setupShadcn(config) {
|
|
162
|
+
const executor = new ActionExecutor(config.projectPath, this.rootPath, config.packageManager);
|
|
163
|
+
// 1. Executar shadcn init (sempre necessário)
|
|
164
|
+
console.log("\n🎨 Inicializando shadcn/ui...");
|
|
165
|
+
let initCommand;
|
|
166
|
+
let initArgs;
|
|
167
|
+
// Usar a cor selecionada ou neutral como padrão
|
|
168
|
+
const baseColor = config.shadcnColor || "neutral";
|
|
169
|
+
if (config.packageManager === "pnpm") {
|
|
170
|
+
initCommand = "pnpm";
|
|
171
|
+
initArgs = [
|
|
172
|
+
"dlx",
|
|
173
|
+
"shadcn@latest",
|
|
174
|
+
"init",
|
|
175
|
+
"-y",
|
|
176
|
+
"--defaults",
|
|
177
|
+
`--base-color=${baseColor}`,
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
else if (config.packageManager === "yarn") {
|
|
181
|
+
initCommand = "npx";
|
|
182
|
+
initArgs = [
|
|
183
|
+
"-y",
|
|
184
|
+
"shadcn@latest",
|
|
185
|
+
"init",
|
|
186
|
+
"-y",
|
|
187
|
+
"--defaults",
|
|
188
|
+
`--base-color=${baseColor}`,
|
|
189
|
+
];
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// npm
|
|
193
|
+
initCommand = "npx";
|
|
194
|
+
initArgs = [
|
|
195
|
+
"-y",
|
|
196
|
+
"shadcn@latest",
|
|
197
|
+
"init",
|
|
198
|
+
"-y",
|
|
199
|
+
"--defaults",
|
|
200
|
+
`--base-color=${baseColor}`,
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
await executor.execute({
|
|
204
|
+
type: "run-command",
|
|
205
|
+
command: initCommand,
|
|
206
|
+
args: initArgs,
|
|
207
|
+
});
|
|
208
|
+
// 2. Adicionar componentes (se foram selecionados)
|
|
209
|
+
if (config.shadcnComponents && config.shadcnComponents.length > 0) {
|
|
210
|
+
console.log(`\n🧩 Adicionando ${config.shadcnComponents.length} componente(s)...`);
|
|
211
|
+
let addCommand;
|
|
212
|
+
let addArgs;
|
|
213
|
+
if (config.packageManager === "pnpm") {
|
|
214
|
+
addCommand = "pnpm";
|
|
215
|
+
addArgs = [
|
|
216
|
+
"dlx",
|
|
217
|
+
"shadcn@latest",
|
|
218
|
+
"add",
|
|
219
|
+
...config.shadcnComponents,
|
|
220
|
+
"-y",
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
else if (config.packageManager === "yarn") {
|
|
224
|
+
addCommand = "npx";
|
|
225
|
+
addArgs = [
|
|
226
|
+
"-y",
|
|
227
|
+
"shadcn@latest",
|
|
228
|
+
"add",
|
|
229
|
+
...config.shadcnComponents,
|
|
230
|
+
"-y",
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// npm
|
|
235
|
+
addCommand = "npx";
|
|
236
|
+
addArgs = [
|
|
237
|
+
"-y",
|
|
238
|
+
"shadcn@latest",
|
|
239
|
+
"add",
|
|
240
|
+
...config.shadcnComponents,
|
|
241
|
+
"-y",
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
await executor.execute({
|
|
245
|
+
type: "run-command",
|
|
246
|
+
command: addCommand,
|
|
247
|
+
args: addArgs,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async createVscodeConfig(config) {
|
|
252
|
+
const vscodeDir = join(config.projectPath, ".vscode");
|
|
253
|
+
const extensionsPath = join(vscodeDir, "extensions.json");
|
|
254
|
+
const settingsPath = join(vscodeDir, "settings.json");
|
|
255
|
+
// Criar diretório .vscode se não existir
|
|
256
|
+
await mkdir(vscodeDir, { recursive: true });
|
|
257
|
+
// Determinar extensões recomendadas baseado no linter
|
|
258
|
+
const extensions = ["bradlc.vscode-tailwindcss"]; // Sempre incluir Tailwind
|
|
259
|
+
if (config.linter === "biome") {
|
|
260
|
+
extensions.push("biomejs.biome");
|
|
261
|
+
}
|
|
262
|
+
else if (config.linter === "eslint-prettier") {
|
|
263
|
+
extensions.push("dbaeumer.vscode-eslint", "esbenp.prettier-vscode");
|
|
264
|
+
}
|
|
265
|
+
// Criar extensions.json
|
|
266
|
+
const extensionsJson = {
|
|
267
|
+
recommendations: extensions,
|
|
268
|
+
};
|
|
269
|
+
// Criar settings.json baseado no linter
|
|
270
|
+
const settingsJson = {
|
|
271
|
+
"editor.formatOnSave": true,
|
|
272
|
+
"editor.formatOnPaste": true,
|
|
273
|
+
"files.eol": "\n",
|
|
274
|
+
"typescript.preferences.preferTypeOnlyAutoImports": true,
|
|
275
|
+
};
|
|
276
|
+
// Configurar defaultFormatter baseado no linter
|
|
277
|
+
if (config.linter === "biome") {
|
|
278
|
+
settingsJson["editor.defaultFormatter"] = "biomejs.biome";
|
|
279
|
+
settingsJson["[typescriptreact]"] = {
|
|
280
|
+
"editor.defaultFormatter": "biomejs.biome",
|
|
281
|
+
};
|
|
282
|
+
settingsJson["[typescript]"] = {
|
|
283
|
+
"editor.defaultFormatter": "biomejs.biome",
|
|
284
|
+
};
|
|
285
|
+
settingsJson["[jsonc]"] = {
|
|
286
|
+
"editor.defaultFormatter": "biomejs.biome",
|
|
287
|
+
};
|
|
288
|
+
settingsJson["[css]"] = {
|
|
289
|
+
"editor.defaultFormatter": "biomejs.biome",
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
else if (config.linter === "eslint-prettier") {
|
|
293
|
+
settingsJson["editor.defaultFormatter"] = "esbenp.prettier-vscode";
|
|
294
|
+
settingsJson["[typescriptreact]"] = {
|
|
295
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
296
|
+
};
|
|
297
|
+
settingsJson["[typescript]"] = {
|
|
298
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
299
|
+
};
|
|
300
|
+
settingsJson["[jsonc]"] = {
|
|
301
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
302
|
+
};
|
|
303
|
+
settingsJson["[css]"] = {
|
|
304
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
// Escrever arquivos
|
|
308
|
+
await writeFile(extensionsPath, JSON.stringify(extensionsJson, null, 2) + "\n", "utf-8");
|
|
309
|
+
await writeFile(settingsPath, JSON.stringify(settingsJson, null, "\t") + "\n", "utf-8");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ProjectConfig, SnippetConfig } from "./types.js";
|
|
2
|
+
export declare class SnippetManager {
|
|
3
|
+
private snippets;
|
|
4
|
+
constructor();
|
|
5
|
+
private registerSnippet;
|
|
6
|
+
getSnippetsForProject(config: ProjectConfig): SnippetConfig[];
|
|
7
|
+
getAllDependencies(snippets: SnippetConfig[]): {
|
|
8
|
+
dependencies: string[];
|
|
9
|
+
devDependencies: string[];
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { axiosConfig } from "./configs/axios.config.js";
|
|
2
|
+
import { biomeConfig } from "./configs/biome.config.js";
|
|
3
|
+
import { cookiesNextConfig } from "./configs/cookies-next.config.js";
|
|
4
|
+
import { dateFnsConfig } from "./configs/date-fns.config.js";
|
|
5
|
+
import { eslintPrettierConfig } from "./configs/eslint-prettier.config.js";
|
|
6
|
+
import { framerMotionConfig } from "./configs/framer-motion.config.js";
|
|
7
|
+
import { kyConfig } from "./configs/ky.config.js";
|
|
8
|
+
import { lucideReactConfig } from "./configs/lucide-react.config.js";
|
|
9
|
+
import { nuqsConfig } from "./configs/nuqs.config.js";
|
|
10
|
+
import { reactHookFormConfig } from "./configs/react-hook-form.config.js";
|
|
11
|
+
import { rechartsConfig } from "./configs/recharts.config.js";
|
|
12
|
+
import { tanstackQueryConfig } from "./configs/tanstack-query.config.js";
|
|
13
|
+
import { zodConfig } from "./configs/zod.config.js";
|
|
14
|
+
export class SnippetManager {
|
|
15
|
+
snippets = new Map();
|
|
16
|
+
constructor() {
|
|
17
|
+
// Registrar todos os snippets disponíveis
|
|
18
|
+
this.registerSnippet("ky", kyConfig);
|
|
19
|
+
this.registerSnippet("axios", axiosConfig);
|
|
20
|
+
this.registerSnippet("tanstack-query", tanstackQueryConfig);
|
|
21
|
+
this.registerSnippet("nuqs", nuqsConfig);
|
|
22
|
+
this.registerSnippet("date-fns", dateFnsConfig);
|
|
23
|
+
this.registerSnippet("biome", biomeConfig);
|
|
24
|
+
this.registerSnippet("eslint-prettier", eslintPrettierConfig);
|
|
25
|
+
this.registerSnippet("zod", zodConfig);
|
|
26
|
+
this.registerSnippet("react-hook-form", reactHookFormConfig);
|
|
27
|
+
this.registerSnippet("cookies-next", cookiesNextConfig);
|
|
28
|
+
this.registerSnippet("framer-motion", framerMotionConfig);
|
|
29
|
+
this.registerSnippet("lucide-react", lucideReactConfig);
|
|
30
|
+
this.registerSnippet("recharts", rechartsConfig);
|
|
31
|
+
}
|
|
32
|
+
registerSnippet(key, config) {
|
|
33
|
+
this.snippets.set(key, config);
|
|
34
|
+
}
|
|
35
|
+
getSnippetsForProject(config) {
|
|
36
|
+
const selectedSnippets = [];
|
|
37
|
+
// HTTP Client
|
|
38
|
+
if (config.httpClient && config.httpClient !== "fetch") {
|
|
39
|
+
const snippet = this.snippets.get(config.httpClient);
|
|
40
|
+
if (snippet)
|
|
41
|
+
selectedSnippets.push(snippet);
|
|
42
|
+
}
|
|
43
|
+
// Linter
|
|
44
|
+
if (config.linter) {
|
|
45
|
+
const snippet = this.snippets.get(config.linter);
|
|
46
|
+
if (snippet)
|
|
47
|
+
selectedSnippets.push(snippet);
|
|
48
|
+
}
|
|
49
|
+
// Additional Libs
|
|
50
|
+
if (config.additionalLibs) {
|
|
51
|
+
for (const lib of config.additionalLibs) {
|
|
52
|
+
const snippet = this.snippets.get(lib);
|
|
53
|
+
if (snippet)
|
|
54
|
+
selectedSnippets.push(snippet);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Ordenar por prioridade
|
|
58
|
+
return selectedSnippets.sort((a, b) => {
|
|
59
|
+
const aPriority = Math.min(...a.actions.map((action) => action.priority ?? 999));
|
|
60
|
+
const bPriority = Math.min(...b.actions.map((action) => action.priority ?? 999));
|
|
61
|
+
return aPriority - bPriority;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
getAllDependencies(snippets) {
|
|
65
|
+
const dependencies = new Set();
|
|
66
|
+
const devDependencies = new Set();
|
|
67
|
+
for (const snippet of snippets) {
|
|
68
|
+
snippet.dependencies?.forEach((dep) => dependencies.add(dep));
|
|
69
|
+
snippet.devDependencies?.forEach((dep) => devDependencies.add(dep));
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
dependencies: Array.from(dependencies),
|
|
73
|
+
devDependencies: Array.from(devDependencies),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.0.5/schema.json",
|
|
3
|
+
"root": false,
|
|
4
|
+
"extends": "//",
|
|
5
|
+
"css": {
|
|
6
|
+
"formatter": {
|
|
7
|
+
"enabled": true
|
|
8
|
+
},
|
|
9
|
+
"linter": {
|
|
10
|
+
"enabled": false
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"vcs": {
|
|
14
|
+
"enabled": true,
|
|
15
|
+
"clientKind": "git",
|
|
16
|
+
"useIgnoreFile": true
|
|
17
|
+
},
|
|
18
|
+
"javascript": {
|
|
19
|
+
"formatter": {
|
|
20
|
+
"trailingCommas": "es5",
|
|
21
|
+
"lineEnding": "lf"
|
|
22
|
+
},
|
|
23
|
+
"globals": ["React"]
|
|
24
|
+
},
|
|
25
|
+
"formatter": {
|
|
26
|
+
"enabled": true,
|
|
27
|
+
"indentStyle": "space"
|
|
28
|
+
},
|
|
29
|
+
"linter": {
|
|
30
|
+
"enabled": true,
|
|
31
|
+
"rules": {
|
|
32
|
+
"recommended": true,
|
|
33
|
+
"a11y": {
|
|
34
|
+
"recommended": true,
|
|
35
|
+
"noSvgWithoutTitle": "off"
|
|
36
|
+
},
|
|
37
|
+
"style": {
|
|
38
|
+
"recommended": true,
|
|
39
|
+
"noDefaultExport": "error",
|
|
40
|
+
"noImplicitBoolean": "off"
|
|
41
|
+
},
|
|
42
|
+
"suspicious": {
|
|
43
|
+
"recommended": true,
|
|
44
|
+
"noReactSpecificProps": "off"
|
|
45
|
+
},
|
|
46
|
+
"complexity": {
|
|
47
|
+
"recommended": true
|
|
48
|
+
},
|
|
49
|
+
"correctness": {
|
|
50
|
+
"recommended": true,
|
|
51
|
+
"useImportExtensions": "off"
|
|
52
|
+
},
|
|
53
|
+
"performance": {
|
|
54
|
+
"recommended": true
|
|
55
|
+
},
|
|
56
|
+
"security": {
|
|
57
|
+
"recommended": true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"files": {
|
|
62
|
+
"includes": ["**", "!node_modules"]
|
|
63
|
+
},
|
|
64
|
+
"overrides": [
|
|
65
|
+
{
|
|
66
|
+
"includes": [
|
|
67
|
+
"src/app/**/page.tsx",
|
|
68
|
+
"src/app/**/layout.tsx",
|
|
69
|
+
|
|
70
|
+
"*.config.js",
|
|
71
|
+
"*.config.mjs",
|
|
72
|
+
"*.config.ts"
|
|
73
|
+
],
|
|
74
|
+
"linter": {
|
|
75
|
+
"rules": {
|
|
76
|
+
"style": {
|
|
77
|
+
"noDefaultExport": "off"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"includes": ["src/@types/**/*.d.ts"],
|
|
84
|
+
"linter": {
|
|
85
|
+
"rules": {
|
|
86
|
+
"correctness": {
|
|
87
|
+
"noUndeclaredVariables": "off"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defaultShouldDehydrateQuery,
|
|
3
|
+
isServer,
|
|
4
|
+
QueryClientProvider as Provider,
|
|
5
|
+
QueryClient,
|
|
6
|
+
} from "@tanstack/react-query";
|
|
7
|
+
|
|
8
|
+
function makeQueryClient() {
|
|
9
|
+
return new QueryClient({
|
|
10
|
+
defaultOptions: {
|
|
11
|
+
queries: {
|
|
12
|
+
staleTime: 1000 * 30, // 30 seconds
|
|
13
|
+
refetchOnReconnect: false,
|
|
14
|
+
refetchOnWindowFocus: false,
|
|
15
|
+
retry: false,
|
|
16
|
+
},
|
|
17
|
+
dehydrate: {
|
|
18
|
+
// include pending queries in dehydration
|
|
19
|
+
shouldDehydrateQuery: (query) =>
|
|
20
|
+
defaultShouldDehydrateQuery(query) ||
|
|
21
|
+
query.state.status === "pending",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let browserQueryClient: QueryClient | undefined;
|
|
28
|
+
|
|
29
|
+
export function getQueryClient() {
|
|
30
|
+
if (isServer) {
|
|
31
|
+
// Server: always make a new query client
|
|
32
|
+
return makeQueryClient();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Browser: make a new query client if we don't already have one
|
|
36
|
+
// This is very important, so we don't re-make a new client if React
|
|
37
|
+
// suspends during the initial render. This may not be needed if we
|
|
38
|
+
// have a suspense boundary BELOW the creation of the query client
|
|
39
|
+
if (!browserQueryClient) {
|
|
40
|
+
browserQueryClient = makeQueryClient();
|
|
41
|
+
}
|
|
42
|
+
return browserQueryClient;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface QueryClientProviderProps {
|
|
46
|
+
children: React.ReactNode;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function QueryClientProvider({ children }: QueryClientProviderProps) {
|
|
50
|
+
const client = getQueryClient();
|
|
51
|
+
|
|
52
|
+
return <Provider client={client}>{children}</Provider>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"editor.formatOnSave": true,
|
|
3
|
+
"editor.formatOnPaste": true,
|
|
4
|
+
"files.eol": "\n",
|
|
5
|
+
"typescript.preferences.preferTypeOnlyAutoImports": true,
|
|
6
|
+
"editor.defaultFormatter": "biomejs.biome",
|
|
7
|
+
"[typescriptreact]": {
|
|
8
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
9
|
+
},
|
|
10
|
+
"[typescript]": {
|
|
11
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
12
|
+
},
|
|
13
|
+
"[jsonc]": {
|
|
14
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
15
|
+
},
|
|
16
|
+
"[css]": {
|
|
17
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Next.js 15 Template
|
|
2
|
+
|
|
3
|
+
Este é o template base gerado pela `@perma-tools/cli`.
|
|
4
|
+
|
|
5
|
+
## 📝 Próximos Passos
|
|
6
|
+
|
|
7
|
+
### 1. Adicionar Favicon
|
|
8
|
+
|
|
9
|
+
Para adicionar um favicon ao seu projeto, coloque um dos seguintes arquivos em `src/app/`:
|
|
10
|
+
|
|
11
|
+
- `favicon.ico` (16x16 ou 32x32)
|
|
12
|
+
- `icon.png` (qualquer tamanho)
|
|
13
|
+
- `icon.svg` (vetor)
|
|
14
|
+
- `apple-icon.png` (180x180 para dispositivos Apple)
|
|
15
|
+
|
|
16
|
+
O Next.js detectará automaticamente e os utilizará.
|
|
17
|
+
|
|
18
|
+
### 2. Personalizar Metadados
|
|
19
|
+
|
|
20
|
+
Edite `src/app/layout.tsx` para alterar:
|
|
21
|
+
- `title`: Título do site
|
|
22
|
+
- `description`: Descrição para SEO
|
|
23
|
+
- Adicione mais metadados conforme necessário
|
|
24
|
+
|
|
25
|
+
### 3. Configurar Variáveis de Ambiente
|
|
26
|
+
|
|
27
|
+
Se você selecionou bibliotecas que requerem variáveis de ambiente, elas estarão listadas em `.env.example`.
|
|
28
|
+
|
|
29
|
+
Copie para `.env.local` e preencha os valores:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
cp .env.example .env.local
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 4. Iniciar Desenvolvimento
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm dev
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Abra [http://localhost:3000](http://localhost:3000) no navegador.
|
|
42
|
+
|
|
43
|
+
## 🔧 Tecnologias Incluídas
|
|
44
|
+
|
|
45
|
+
Veja o `package.json` para a lista completa de bibliotecas configuradas.
|
|
46
|
+
|
|
47
|
+
## 📚 Recursos
|
|
48
|
+
|
|
49
|
+
- [Documentação Next.js](https://nextjs.org/docs)
|
|
50
|
+
- [Learn Next.js](https://nextjs.org/learn)
|