@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.
Files changed (75) hide show
  1. package/README.md +159 -0
  2. package/dist/actions.d.ts +12 -0
  3. package/dist/actions.js +157 -0
  4. package/dist/cli-parser.d.ts +21 -0
  5. package/dist/cli-parser.js +141 -0
  6. package/dist/configs/axios.config.d.ts +2 -0
  7. package/dist/configs/axios.config.js +12 -0
  8. package/dist/configs/biome.config.d.ts +2 -0
  9. package/dist/configs/biome.config.js +12 -0
  10. package/dist/configs/cookies-next.config.d.ts +2 -0
  11. package/dist/configs/cookies-next.config.js +6 -0
  12. package/dist/configs/date-fns.config.d.ts +2 -0
  13. package/dist/configs/date-fns.config.js +6 -0
  14. package/dist/configs/eslint-prettier.config.d.ts +2 -0
  15. package/dist/configs/eslint-prettier.config.js +35 -0
  16. package/dist/configs/framer-motion.config.d.ts +2 -0
  17. package/dist/configs/framer-motion.config.js +6 -0
  18. package/dist/configs/ky.config.d.ts +2 -0
  19. package/dist/configs/ky.config.js +12 -0
  20. package/dist/configs/lucide-react.config.d.ts +2 -0
  21. package/dist/configs/lucide-react.config.js +6 -0
  22. package/dist/configs/nuqs.config.d.ts +2 -0
  23. package/dist/configs/nuqs.config.js +12 -0
  24. package/dist/configs/react-hook-form.config.d.ts +2 -0
  25. package/dist/configs/react-hook-form.config.js +6 -0
  26. package/dist/configs/recharts.config.d.ts +2 -0
  27. package/dist/configs/recharts.config.js +6 -0
  28. package/dist/configs/tanstack-query.config.d.ts +2 -0
  29. package/dist/configs/tanstack-query.config.js +19 -0
  30. package/dist/configs/zod.config.d.ts +2 -0
  31. package/dist/configs/zod.config.js +6 -0
  32. package/dist/dependency-versions.d.ts +13 -0
  33. package/dist/dependency-versions.js +50 -0
  34. package/dist/git-setup.d.ts +23 -0
  35. package/dist/git-setup.js +275 -0
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.js +537 -0
  38. package/dist/project-generator.d.ts +15 -0
  39. package/dist/project-generator.js +311 -0
  40. package/dist/snippet-manager.d.ts +11 -0
  41. package/dist/snippet-manager.js +76 -0
  42. package/dist/snippets/axios/index.ts +6 -0
  43. package/dist/snippets/biome/biome.json +93 -0
  44. package/dist/snippets/eslint-prettier/eslintignore +6 -0
  45. package/dist/snippets/eslint-prettier/eslintrc.js +3 -0
  46. package/dist/snippets/eslint-prettier/prettierignore +9 -0
  47. package/dist/snippets/eslint-prettier/prettierrc.js +9 -0
  48. package/dist/snippets/ky/index.ts +5 -0
  49. package/dist/snippets/react-query/index.tsx +53 -0
  50. package/dist/snippets/vscode/extensions.json +3 -0
  51. package/dist/snippets/vscode/settings.json +19 -0
  52. package/dist/templates/frontend/next15/base/.env.example +3 -0
  53. package/dist/templates/frontend/next15/base/README.md +50 -0
  54. package/dist/templates/frontend/next15/base/next.config.ts +7 -0
  55. package/dist/templates/frontend/next15/base/package.json +23 -0
  56. package/dist/templates/frontend/next15/base/postcss.config.mjs +8 -0
  57. package/dist/templates/frontend/next15/base/public/public.md +12 -0
  58. package/dist/templates/frontend/next15/base/src/@types/@types.md +7 -0
  59. package/dist/templates/frontend/next15/base/src/@types/types.d.ts +1 -0
  60. package/dist/templates/frontend/next15/base/src/app/(application)/page.tsx +50 -0
  61. package/dist/templates/frontend/next15/base/src/app/favicon.ico +0 -0
  62. package/dist/templates/frontend/next15/base/src/app/layout.tsx +23 -0
  63. package/dist/templates/frontend/next15/base/src/app/providers.tsx +5 -0
  64. package/dist/templates/frontend/next15/base/src/components/components.md +9 -0
  65. package/dist/templates/frontend/next15/base/src/constants/constants.md +10 -0
  66. package/dist/templates/frontend/next15/base/src/hooks/hooks.md +11 -0
  67. package/dist/templates/frontend/next15/base/src/lib/lib.md +9 -0
  68. package/dist/templates/frontend/next15/base/src/services/services.md +14 -0
  69. package/dist/templates/frontend/next15/base/src/styles/globals.css +1 -0
  70. package/dist/templates/frontend/next15/base/src/types/types.md +15 -0
  71. package/dist/templates/frontend/next15/base/src/utils/utils.md +18 -0
  72. package/dist/templates/frontend/next15/base/tsconfig.json +26 -0
  73. package/dist/types.d.ts +60 -0
  74. package/dist/types.js +1 -0
  75. 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,6 @@
1
+ import axios from "axios";
2
+
3
+ export const api = axios.create({
4
+ baseURL: process.env.REACT_APP_API_BASE_URL || "http://localhost:3000/api",
5
+ timeout: 30000,
6
+ });
@@ -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,6 @@
1
+ node_modules
2
+ .next
3
+ out
4
+ dist
5
+ build
6
+ .turbo
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ extends: ["next/core-web-vitals", "next/typescript"],
3
+ };
@@ -0,0 +1,9 @@
1
+ node_modules
2
+ .next
3
+ out
4
+ dist
5
+ build
6
+ .turbo
7
+ package-lock.json
8
+ pnpm-lock.yaml
9
+ yarn.lock
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ semi: true,
3
+ trailingComma: "es5",
4
+ singleQuote: true,
5
+ printWidth: 100,
6
+ tabWidth: 2,
7
+ useTabs: false,
8
+ plugins: ["prettier-plugin-tailwindcss"],
9
+ };
@@ -0,0 +1,5 @@
1
+ import ky from "ky";
2
+
3
+ export const api = ky.create({
4
+ prefixUrl: process.env.NEXT_PUBLIC_API_URL, // e.g. 'https://api.example.com'
5
+ });
@@ -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,3 @@
1
+ {
2
+ "recommendations": ["bradlc.vscode-tailwindcss", "biomejs.biome"]
3
+ }
@@ -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,3 @@
1
+ # Environment Variables
2
+
3
+ NEXT_PUBLIC_API_URL=http://localhost:3000/api
@@ -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)
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;