create-next-pro-cli 0.1.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.
Files changed (106) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/CODE_OF_CONDUCT.md +33 -0
  3. package/CONTRIBUTING.md +44 -0
  4. package/FONCTIONALITY.md +25 -0
  5. package/README.md +292 -0
  6. package/bin.ts +2 -0
  7. package/bun.lock +27 -0
  8. package/create-next-pro-completion.sh +36 -0
  9. package/install.sh +18 -0
  10. package/package.json +32 -0
  11. package/public/logo.svg +76 -0
  12. package/src/index.ts +511 -0
  13. package/src/scaffold-dev.ts +89 -0
  14. package/src/scaffold.ts +70 -0
  15. package/templates/Component/Component.tsx +13 -0
  16. package/templates/Component/component.json +4 -0
  17. package/templates/Page/default.tsx +0 -0
  18. package/templates/Page/error.tsx +38 -0
  19. package/templates/Page/global-error.tsx +0 -0
  20. package/templates/Page/layout.tsx +0 -0
  21. package/templates/Page/loading.tsx +17 -0
  22. package/templates/Page/not-found.tsx +0 -0
  23. package/templates/Page/page-ui.tsx +22 -0
  24. package/templates/Page/page.json +4 -0
  25. package/templates/Page/page.tsx +24 -0
  26. package/templates/Page/route.ts +0 -0
  27. package/templates/Page/template.tsx +0 -0
  28. package/templates/Projects/default/README.md +36 -0
  29. package/templates/Projects/default/eslint.config.mjs +16 -0
  30. package/templates/Projects/default/messages/en/_global_ui.json +30 -0
  31. package/templates/Projects/default/messages/en/_home.json +4 -0
  32. package/templates/Projects/default/messages/en/dashboard.json +14 -0
  33. package/templates/Projects/default/messages/en/login.json +7 -0
  34. package/templates/Projects/default/messages/en/register.json +7 -0
  35. package/templates/Projects/default/messages/en/settings.json +4 -0
  36. package/templates/Projects/default/messages/en/user_info.json +4 -0
  37. package/templates/Projects/default/messages/en.ts +4 -0
  38. package/templates/Projects/default/messages/fr/_global_ui.json +30 -0
  39. package/templates/Projects/default/messages/fr/_home.json +4 -0
  40. package/templates/Projects/default/messages/fr/dashboard.json +14 -0
  41. package/templates/Projects/default/messages/fr/login.json +7 -0
  42. package/templates/Projects/default/messages/fr/register.json +7 -0
  43. package/templates/Projects/default/messages/fr/settings.json +4 -0
  44. package/templates/Projects/default/messages/fr/user_info.json +4 -0
  45. package/templates/Projects/default/messages/fr.ts +4 -0
  46. package/templates/Projects/default/messages/getMergedMessages.ts +31 -0
  47. package/templates/Projects/default/middleware.ts +11 -0
  48. package/templates/Projects/default/next.config.ts +12 -0
  49. package/templates/Projects/default/package.json +32 -0
  50. package/templates/Projects/default/postcss.config.mjs +5 -0
  51. package/templates/Projects/default/public/cnp-logo.ico +0 -0
  52. package/templates/Projects/default/public/cnp-logo.png +0 -0
  53. package/templates/Projects/default/public/cnp-logo.svg +76 -0
  54. package/templates/Projects/default/public/file.svg +1 -0
  55. package/templates/Projects/default/public/globe.svg +1 -0
  56. package/templates/Projects/default/public/next.svg +1 -0
  57. package/templates/Projects/default/public/vercel.svg +1 -0
  58. package/templates/Projects/default/public/window.svg +1 -0
  59. package/templates/Projects/default/src/app/[locale]/(public)/_home/loading.tsx +1 -0
  60. package/templates/Projects/default/src/app/[locale]/(public)/_home/page.tsx +16 -0
  61. package/templates/Projects/default/src/app/[locale]/(public)/layout.tsx +11 -0
  62. package/templates/Projects/default/src/app/[locale]/(public)/login/page.tsx +19 -0
  63. package/templates/Projects/default/src/app/[locale]/(public)/register/page.tsx +23 -0
  64. package/templates/Projects/default/src/app/[locale]/(user)/dashboard/error.tsx +38 -0
  65. package/templates/Projects/default/src/app/[locale]/(user)/dashboard/loading.tsx +17 -0
  66. package/templates/Projects/default/src/app/[locale]/(user)/dashboard/page.tsx +12 -0
  67. package/templates/Projects/default/src/app/[locale]/(user)/layout.tsx +12 -0
  68. package/templates/Projects/default/src/app/[locale]/(user)/settings/loading.tsx +17 -0
  69. package/templates/Projects/default/src/app/[locale]/(user)/settings/page.tsx +12 -0
  70. package/templates/Projects/default/src/app/[locale]/(user)/user_info/loading.tsx +17 -0
  71. package/templates/Projects/default/src/app/[locale]/(user)/user_info/page.tsx +12 -0
  72. package/templates/Projects/default/src/app/[locale]/layout.tsx +32 -0
  73. package/templates/Projects/default/src/app/[locale]/loading.tsx +17 -0
  74. package/templates/Projects/default/src/app/[locale]/not-found.tsx +22 -0
  75. package/templates/Projects/default/src/app/[locale]/page.tsx +116 -0
  76. package/templates/Projects/default/src/app/api/auth/[...nextauth]/route.ts +81 -0
  77. package/templates/Projects/default/src/app/api/auth/[...nextauth].ts +0 -0
  78. package/templates/Projects/default/src/app/api/auth/post-login/route.ts +27 -0
  79. package/templates/Projects/default/src/app/api/me/route.ts +15 -0
  80. package/templates/Projects/default/src/app/favicon.ico +0 -0
  81. package/templates/Projects/default/src/app/layout.tsx +11 -0
  82. package/templates/Projects/default/src/app/not-found.tsx +17 -0
  83. package/templates/Projects/default/src/app/page.tsx +6 -0
  84. package/templates/Projects/default/src/app/sitemap.ts +27 -0
  85. package/templates/Projects/default/src/app/styles/globals.css +26 -0
  86. package/templates/Projects/default/src/app/ui/dashboard/MainDashboard.tsx +21 -0
  87. package/templates/Projects/default/src/app/ui/dashboard/StatsCard.tsx +14 -0
  88. package/templates/Projects/default/src/app/ui/dashboard/WelcomeCard.tsx +10 -0
  89. package/templates/Projects/default/src/auth.config.ts +0 -0
  90. package/templates/Projects/default/src/config.ts +4 -0
  91. package/templates/Projects/default/src/lib/auth/isConnected.ts +16 -0
  92. package/templates/Projects/default/src/lib/i18n/navigation.ts +19 -0
  93. package/templates/Projects/default/src/lib/i18n/request.ts +31 -0
  94. package/templates/Projects/default/src/lib/i18n/routing.ts +20 -0
  95. package/templates/Projects/default/src/ui/_global/BackButton.tsx +15 -0
  96. package/templates/Projects/default/src/ui/_global/GlobalHeader.tsx +34 -0
  97. package/templates/Projects/default/src/ui/_global/GlobalMain.tsx +15 -0
  98. package/templates/Projects/default/src/ui/_global/LocaleSwitcher.tsx +38 -0
  99. package/templates/Projects/default/src/ui/_global/PublicNav.tsx +30 -0
  100. package/templates/Projects/default/src/ui/_global/UserNav.tsx +32 -0
  101. package/templates/Projects/default/src/ui/dashboard/StatsCard.tsx +14 -0
  102. package/templates/Projects/default/src/ui/dashboard/WelcomeCard.tsx +10 -0
  103. package/templates/Projects/default/src/ui/dashboard/page.tsx +22 -0
  104. package/templates/Projects/default/tailwind.config.ts +1 -0
  105. package/templates/Projects/default/tsconfig.json +34 -0
  106. package/tsconfig.json +27 -0
package/src/index.ts ADDED
@@ -0,0 +1,511 @@
1
+ // src/index.ts
2
+
3
+ import * as prompts from "prompts";
4
+ import { scaffoldProject } from "@/scaffold";
5
+ import { mkdir, writeFile, readdir, readFile } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ import { existsSync } from "node:fs";
8
+
9
+ /**
10
+ * Main CLI entry point for create-next-pro.
11
+ *
12
+ * Note: For now, the project behaves as if --force is always enabled and no creation prompt is taken into account.
13
+ * All actions are performed directly without confirmation.
14
+ */
15
+ export async function main() {
16
+ console.log("🚀 Welcome to create-next-pro\n");
17
+
18
+ let args = Bun.argv.slice(2);
19
+ const force = args.includes("--force");
20
+ // For now, --force is always considered enabled but do not overwrite existing projects
21
+ // WARNING: if you enable --force it will overwrite existing projects. This is a temporary setting for development purposes.
22
+ // const force = true;
23
+
24
+ // If addpage is called without options, add default flags -LPl
25
+ if (args[0] === "addpage" && args.length === 1) {
26
+ args.push("-LPl");
27
+ }
28
+
29
+ /**
30
+ * Handle addcomponent command: create a component in the correct location and update translation JSON.
31
+ */
32
+ if (args[0] === "addcomponent") {
33
+ let componentName = args[1];
34
+ let pageScope = null;
35
+ let pageIndex = args.findIndex((arg) => arg === "-P" || arg === "--page");
36
+ if (pageIndex !== -1 && args[pageIndex + 1]) {
37
+ pageScope = args[pageIndex + 1];
38
+ }
39
+
40
+ // Handle nested pageScope (e.g. ParentPage.ChildPage)
41
+ let nestedPath = null;
42
+ if (pageScope && pageScope.includes(".")) {
43
+ nestedPath = join(...pageScope.split("."));
44
+ }
45
+
46
+ if (!componentName || componentName.startsWith("-")) {
47
+ // Si le nom n'est pas fourni ou est une option, demander via prompt
48
+ const response = await prompts.prompt({
49
+ type: "text",
50
+ name: "componentName",
51
+ message: "🧩 Component name to add:",
52
+ validate: (name: string) =>
53
+ name ? true : "Component name is required",
54
+ });
55
+ componentName = response.componentName;
56
+ }
57
+
58
+ const componentNameUpper = capitalize(componentName);
59
+ const templatePath = join(import.meta.dir, "..", "templates", "Component");
60
+ const messagesPath = join(process.cwd(), "messages");
61
+
62
+ // Determine target path for the component
63
+ let componentTargetPath;
64
+ let translationKey;
65
+ if (pageScope) {
66
+ if (nestedPath) {
67
+ componentTargetPath = join(process.cwd(), "src", "ui", nestedPath);
68
+ translationKey = pageScope;
69
+ } else {
70
+ componentTargetPath = join(process.cwd(), "src", "ui", pageScope);
71
+ translationKey = pageScope;
72
+ }
73
+ } else {
74
+ componentTargetPath = join(process.cwd(), "src", "ui", "_global");
75
+ translationKey = "_global_ui";
76
+ }
77
+ if (!existsSync(componentTargetPath)) {
78
+ await mkdir(componentTargetPath, { recursive: true });
79
+ }
80
+ const componentFile = join(
81
+ componentTargetPath,
82
+ `${componentNameUpper}.tsx`
83
+ );
84
+
85
+ // Read and adapt the TSX template
86
+ const templateComponentPath = join(templatePath, "Component.tsx");
87
+ if (existsSync(templateComponentPath)) {
88
+ let content = await readFile(templateComponentPath, "utf-8");
89
+ // Remplacement du nom du component et de la clé de traduction
90
+ content = content
91
+ .replace(/Component/g, componentNameUpper)
92
+ .replace(/componentPage/g, translationKey);
93
+ await writeFile(componentFile, content);
94
+ console.log(`📄 File created: ${componentFile}`);
95
+ } else {
96
+ console.error(
97
+ "❌ Template Component.tsx introuvable :",
98
+ templateComponentPath
99
+ );
100
+ }
101
+
102
+ // Add the component to each language's JSON file (only folders)
103
+ const entries = await readdir(messagesPath, { withFileTypes: true });
104
+ const langDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
105
+ const jsonTemplate = join(templatePath, "component.json");
106
+ if (!existsSync(jsonTemplate)) {
107
+ console.error("❌ Template component.json not found:", jsonTemplate);
108
+ return;
109
+ }
110
+ const jsonContent = await readFile(jsonTemplate, "utf-8");
111
+ const parsed = JSON.parse(jsonContent);
112
+
113
+ for (const locale of langDirs) {
114
+ // Only process if messages/<locale> is a directory
115
+ const localeDir = join(messagesPath, locale);
116
+ if (
117
+ !existsSync(localeDir) ||
118
+ !require("node:fs").statSync(localeDir).isDirectory()
119
+ )
120
+ continue;
121
+ let jsonTarget;
122
+ if (pageScope) {
123
+ jsonTarget = join(messagesPath, locale, `${pageScope}.json`);
124
+ } else {
125
+ jsonTarget = join(messagesPath, locale, `_global_ui.json`);
126
+ }
127
+
128
+ let current: Record<string, any> = {};
129
+ if (existsSync(jsonTarget)) {
130
+ const jsonFile = await readFile(jsonTarget, "utf-8");
131
+ current = JSON.parse(jsonFile) as Record<string, any>;
132
+ }
133
+ current[componentNameUpper] = parsed;
134
+ await writeFile(jsonTarget, JSON.stringify(current, null, 2));
135
+ }
136
+
137
+ console.log(
138
+ `✅ Component "${componentNameUpper}" added ${
139
+ pageScope ? `to page ${pageScope}` : "globally"
140
+ } with localized messages.`
141
+ );
142
+ return;
143
+ }
144
+
145
+ /**
146
+ * Handle addpage command: create a page (nested or not) and update translation JSON.
147
+ */
148
+ if (args[0] === "addpage") {
149
+ let pageName = args[1];
150
+ if (!pageName || pageName.startsWith("-")) {
151
+ const response = await prompts.prompt({
152
+ type: "text",
153
+ name: "pageName",
154
+ message: "📝 Page name to add:",
155
+ validate: (name: string) => (name ? true : "Page name is required"),
156
+ });
157
+ pageName = response.pageName;
158
+ }
159
+
160
+ // Handle nested pages
161
+ let parentName = null;
162
+ let childName = null;
163
+ if (pageName.includes(".")) {
164
+ [parentName, childName] = pageName.split(".");
165
+ }
166
+
167
+ let shortFlags = args.find((arg) => /^-[A-Za-z]+$/.test(arg));
168
+ let longFlags = new Set(args.filter((a) => a.startsWith("--")));
169
+ const flags = new Set<string>();
170
+
171
+ if (!shortFlags && Array.from(longFlags).length === 0) {
172
+ shortFlags = "-LPl";
173
+ }
174
+
175
+ if (shortFlags) {
176
+ for (const char of shortFlags.slice(1)) {
177
+ switch (char) {
178
+ case "L":
179
+ flags.add("layout");
180
+ break;
181
+ case "P":
182
+ flags.add("page");
183
+ break;
184
+ case "l":
185
+ flags.add("loading");
186
+ break;
187
+ case "n":
188
+ flags.add("not-found");
189
+ break;
190
+ case "e":
191
+ flags.add("error");
192
+ break;
193
+ case "g":
194
+ flags.add("global-error");
195
+ break;
196
+ case "r":
197
+ flags.add("route");
198
+ break;
199
+ case "t":
200
+ flags.add("template");
201
+ break;
202
+ case "d":
203
+ flags.add("default");
204
+ break;
205
+ }
206
+ }
207
+ }
208
+
209
+ for (const flag of [
210
+ "layout",
211
+ "page",
212
+ "loading",
213
+ "not-found",
214
+ "error",
215
+ "global-error",
216
+ "route",
217
+ "template",
218
+ "default",
219
+ ]) {
220
+ if (longFlags.has("--" + flag)) flags.add(flag);
221
+ }
222
+
223
+ const srcPath = join(process.cwd(), "src", "app", "[locale]");
224
+ const messagesPath = join(process.cwd(), "messages");
225
+ const templatePath = join(import.meta.dir, "..", "templates", "Page");
226
+ const entries = await readdir(messagesPath, { withFileTypes: true });
227
+ const locales = entries.filter((e) => e.isDirectory()).map((e) => e.name);
228
+
229
+ // Create folders/files for nested or simple page
230
+ let uiPageDir, localePagePath, jsonFileName;
231
+ if (parentName && childName) {
232
+ uiPageDir = join(process.cwd(), "src", "ui", parentName, childName);
233
+ localePagePath = join(srcPath, parentName, childName);
234
+ jsonFileName = parentName;
235
+ } else {
236
+ uiPageDir = join(process.cwd(), "src", "ui", pageName);
237
+ localePagePath = join(srcPath, pageName);
238
+ jsonFileName = pageName;
239
+ }
240
+ if (!existsSync(uiPageDir)) {
241
+ await mkdir(uiPageDir, { recursive: true });
242
+ }
243
+ const uiPageFile = join(uiPageDir, "page-ui.tsx");
244
+ const uiPageTemplate = join(templatePath, "page-ui.tsx");
245
+ if (existsSync(uiPageTemplate)) {
246
+ let uiContent = await readFile(uiPageTemplate, "utf-8");
247
+ uiContent = uiContent
248
+ .replace(/template/g, childName || pageName)
249
+ .replace(/Template/g, capitalize(childName || pageName));
250
+ await writeFile(uiPageFile, uiContent);
251
+ console.log(`📄 File created: ${uiPageFile}`);
252
+ } else {
253
+ console.warn("⚠️ Template page-ui.tsx manquant.");
254
+ }
255
+ if (!existsSync(localePagePath)) {
256
+ await mkdir(localePagePath, { recursive: true });
257
+ }
258
+ for (const flag of flags) {
259
+ const filename = toFileName(flag);
260
+ const src = join(templatePath, filename);
261
+ const dst = join(localePagePath, filename);
262
+ if (!existsSync(src)) {
263
+ console.warn(`⚠️ Missing template file: ${filename}`);
264
+ continue;
265
+ }
266
+ const content = await readFile(src, "utf-8");
267
+ const replaced = content
268
+ .replace(/template/g, childName || pageName)
269
+ .replace(/Template/g, capitalize(childName || pageName));
270
+ await writeFile(dst, replaced);
271
+ console.log(`📄 File created: ${dst}`);
272
+ }
273
+
274
+ // Add JSON to parent object if nested, otherwise create a simple file
275
+ const jsonTemplate = join(templatePath, "page.json");
276
+ if (!existsSync(jsonTemplate)) {
277
+ console.warn("⚠️ Missing template page.json.");
278
+ }
279
+ const content = await readFile(jsonTemplate, "utf-8");
280
+ const replaced = content
281
+ .replace(/template/g, childName || pageName)
282
+ .replace(/Template/g, capitalize(childName || pageName));
283
+ for (const locale of locales) {
284
+ // Only process if messages/<locale> is a directory
285
+ const localeDir = join(messagesPath, locale);
286
+ if (
287
+ !existsSync(localeDir) ||
288
+ !require("node:fs").statSync(localeDir).isDirectory()
289
+ )
290
+ continue;
291
+ const jsonTarget = join(messagesPath, locale, `${jsonFileName}.json`);
292
+ let current: Record<string, any> = {};
293
+ if (existsSync(jsonTarget)) {
294
+ const jsonFile = await readFile(jsonTarget, "utf-8");
295
+ try {
296
+ current = JSON.parse(jsonFile) as Record<string, any>;
297
+ } catch {
298
+ current = {};
299
+ }
300
+ }
301
+ if (parentName && childName) {
302
+ current[childName] = JSON.parse(replaced);
303
+ } else {
304
+ // fichier simple
305
+ current = JSON.parse(replaced);
306
+ }
307
+ await writeFile(jsonTarget, JSON.stringify(current, null, 2));
308
+ }
309
+
310
+ console.log(`✅ Page "${pageName}" with templates added for each locale.`);
311
+ return;
312
+ }
313
+
314
+ /**
315
+ * Handle rmpage command: remove a page and all related files/folders.
316
+ */
317
+ if (args[0] === "rmpage") {
318
+ let pageName = args[1];
319
+ if (!pageName || pageName.startsWith("-")) {
320
+ const response = await prompts.prompt({
321
+ type: "text",
322
+ name: "pageName",
323
+ message: "🗑️ Page name to remove:",
324
+ validate: (name: string) => (name ? true : "Page name is required"),
325
+ });
326
+ pageName = response.pageName;
327
+ }
328
+
329
+ // Remove translation files messages/<lang>/<PageName>.json
330
+ const messagesPath = join(process.cwd(), "messages");
331
+ const entries = await readdir(messagesPath, { withFileTypes: true });
332
+ const langDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
333
+ for (const locale of langDirs) {
334
+ const jsonTarget = join(messagesPath, locale, `${pageName}.json`);
335
+ if (existsSync(jsonTarget)) {
336
+ await writeFile(jsonTarget, "");
337
+ await import("node:child_process").then((cp) =>
338
+ cp.execSync(`rm -f '${jsonTarget}'`)
339
+ );
340
+ console.log(`🗑️ Deleted: ${jsonTarget}`);
341
+ }
342
+ }
343
+
344
+ // Remove folder src/ui/<PageName>
345
+ const uiPageDir = join(process.cwd(), "src", "ui", pageName);
346
+ if (existsSync(uiPageDir)) {
347
+ await import("node:child_process").then((cp) =>
348
+ cp.execSync(`rm -rf '${uiPageDir}'`)
349
+ );
350
+ console.log(`🗑️ Deleted: ${uiPageDir}`);
351
+ }
352
+
353
+ // Remove folder src/app/[locale]/<PageName>
354
+ const appLocaleDir = join(
355
+ process.cwd(),
356
+ "src",
357
+ "app",
358
+ "[locale]",
359
+ pageName
360
+ );
361
+ if (existsSync(appLocaleDir)) {
362
+ await import("node:child_process").then((cp) =>
363
+ cp.execSync(`rm -rf '${appLocaleDir}'`)
364
+ );
365
+ console.log(`🗑️ Deleted: ${appLocaleDir}`);
366
+ }
367
+
368
+ console.log(`✅ Page "${pageName}" deleted.`);
369
+ return;
370
+ }
371
+
372
+ /**
373
+ * Handle direct project creation if a name argument is provided.
374
+ */
375
+ const nameArg = args.find((arg) => !arg.startsWith("--"));
376
+
377
+ if (nameArg) {
378
+ const response = {
379
+ projectName: nameArg,
380
+ useTypescript: true,
381
+ useEslint: true,
382
+ useTailwind: true,
383
+ useSrcDir: true,
384
+ useTurbopack: true,
385
+ useI18n: true,
386
+ customAlias: false,
387
+ importAlias: "@/*",
388
+ force,
389
+ };
390
+
391
+ console.log(`📦 Creating project "${response.projectName}"...`);
392
+ await scaffoldProject(response);
393
+ return;
394
+ }
395
+
396
+ /**
397
+ * Interactive prompt for project creation (not currently used, see note above).
398
+ */
399
+ const response = await prompts.prompt([
400
+ {
401
+ type: "text",
402
+ name: "projectName",
403
+ message: "🧱 Project name:",
404
+ initial: "my-next-app",
405
+ },
406
+ {
407
+ type: "toggle",
408
+ name: "useTypescript",
409
+ message: "✔ Use TypeScript?",
410
+ initial: true,
411
+ active: "Yes",
412
+ inactive: "No",
413
+ },
414
+ {
415
+ type: "toggle",
416
+ name: "useEslint",
417
+ message: "✔ Use ESLint?",
418
+ initial: true,
419
+ active: "Yes",
420
+ inactive: "No",
421
+ },
422
+ {
423
+ type: "toggle",
424
+ name: "useTailwind",
425
+ message: "✔ Use Tailwind CSS?",
426
+ initial: true,
427
+ active: "Yes",
428
+ inactive: "No",
429
+ },
430
+ {
431
+ type: "toggle",
432
+ name: "useSrcDir",
433
+ message: "✔ Use `src/` directory?",
434
+ initial: false,
435
+ active: "Yes",
436
+ inactive: "No",
437
+ },
438
+ {
439
+ type: "toggle",
440
+ name: "useTurbopack",
441
+ message: "✔ Use Turbopack for `next dev`?",
442
+ initial: true,
443
+ active: "Yes",
444
+ inactive: "No",
445
+ },
446
+ {
447
+ type: "toggle",
448
+ name: "useI18n",
449
+ message: "✔ Use i18n with next-intl for translations?",
450
+ initial: true,
451
+ active: "Yes",
452
+ inactive: "No",
453
+ },
454
+ {
455
+ type: "toggle",
456
+ name: "customAlias",
457
+ message: "✔ Customize import alias (`@/*` by default)?",
458
+ initial: false,
459
+ active: "Yes",
460
+ inactive: "No",
461
+ },
462
+ {
463
+ type: (prev: boolean) => (prev ? "text" : null),
464
+ name: "importAlias",
465
+ message: "✔ What import alias would you like?",
466
+ initial: "@core/*",
467
+ },
468
+ ]);
469
+
470
+ console.log("\n✅ Your choices:");
471
+ console.log(response);
472
+
473
+ await scaffoldProject(response);
474
+ }
475
+
476
+ /**
477
+ * Capitalize the first letter of a string.
478
+ */
479
+ function capitalize(str: string): string {
480
+ return str.charAt(0).toUpperCase() + str.slice(1);
481
+ }
482
+
483
+ /**
484
+ * Map a key to its corresponding file name for page/component templates.
485
+ * @param key string
486
+ * @returns file name string
487
+ */
488
+ function toFileName(key: string): string {
489
+ switch (key) {
490
+ case "layout":
491
+ return "layout.tsx";
492
+ case "page":
493
+ return "page.tsx";
494
+ case "loading":
495
+ return "loading.tsx";
496
+ case "not-found":
497
+ return "not-found.tsx";
498
+ case "error":
499
+ return "error.tsx";
500
+ case "global-error":
501
+ return "global-error.tsx";
502
+ case "route":
503
+ return "route.ts";
504
+ case "template":
505
+ return "template.tsx";
506
+ case "default":
507
+ return "default.tsx";
508
+ default:
509
+ return `${key}.tsx`;
510
+ }
511
+ }
@@ -0,0 +1,89 @@
1
+ // src/scaffold-dev.ts
2
+
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+
6
+ /**
7
+ * Experimental tool for generating a Next.js project template structure.
8
+ * This script is under development and will evolve to allow creation of new project templates in the future.
9
+ *
10
+ * @param options Object containing the project name
11
+ */
12
+ async function scaffoldTemplate(options: { projectName: string }) {
13
+ const base = options.projectName;
14
+ console.log(`\n📁 Creating template directory: ${base}`);
15
+
16
+ // List of folders to create for the template
17
+ const folders = [
18
+ "app/[locale]/_main",
19
+ "app/[locale]/dashboard",
20
+ "lib/i18n",
21
+ "messages/en",
22
+ "messages/fr",
23
+ "public",
24
+ "styles",
25
+ ];
26
+
27
+ // List of files to create for the template
28
+ const files = [
29
+ "app/[locale]/layout.tsx",
30
+ "app/[locale]/page.tsx",
31
+ "app/[locale]/not-found.tsx",
32
+ "app/[locale]/error.tsx",
33
+
34
+ "app/[locale]/_main/page.tsx",
35
+ "app/[locale]/_main/layout.tsx",
36
+ "app/[locale]/_main/loading.tsx",
37
+ "app/[locale]/_main/template.tsx",
38
+
39
+ "app/[locale]/dashboard/page.tsx",
40
+ "app/[locale]/dashboard/layout.tsx",
41
+ "app/[locale]/dashboard/loading.tsx",
42
+ "app/[locale]/dashboard/template.tsx",
43
+
44
+ "lib/i18n/routing.ts",
45
+ "lib/i18n/request.ts",
46
+ "lib/i18n/navigation.ts",
47
+
48
+ "messages/en/home.json",
49
+ "messages/en/dashboard.json",
50
+ "messages/en/navbar.json",
51
+
52
+ "messages/fr/home.json",
53
+ "messages/fr/dashboard.json",
54
+ "messages/fr/navbar.json",
55
+
56
+ "styles/globals.css",
57
+ "middleware.ts",
58
+ "next.config.ts",
59
+ "postcss.config.mjs",
60
+ "eslint.config.mjs",
61
+ "tailwind.config.ts",
62
+ "tsconfig.json",
63
+ "next-env.d.ts",
64
+ "package.json",
65
+ "README.md",
66
+ ];
67
+
68
+ // Create all folders
69
+ for (const folder of folders) {
70
+ await mkdir(join(base, folder), { recursive: true });
71
+ }
72
+
73
+ // Create all files with a placeholder comment
74
+ for (const file of files) {
75
+ const fullPath = join(base, file);
76
+ await writeFile(fullPath, `// ${file}`);
77
+ }
78
+
79
+ console.log("✅ Full template generated.");
80
+ }
81
+
82
+ // Direct execution if this file is run as a script
83
+ if (require.main === module) {
84
+ const projectName = process.argv[2] || "template-next-app";
85
+ scaffoldTemplate({ projectName }).catch((err) => {
86
+ console.error("Error during template generation:", err);
87
+ process.exit(1);
88
+ });
89
+ }
@@ -0,0 +1,70 @@
1
+ // src/scaffold.ts
2
+
3
+ import { cp, mkdir, rm } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { existsSync } from "node:fs";
6
+
7
+ /**
8
+ * Options for scaffolding a Next.js project.
9
+ */
10
+ interface ScaffoldOptions {
11
+ projectName: string;
12
+ useTypescript: boolean;
13
+ useEslint: boolean;
14
+ useTailwind: boolean;
15
+ useSrcDir: boolean;
16
+ useTurbopack: boolean;
17
+ useI18n: boolean;
18
+ customAlias: boolean;
19
+ importAlias: string;
20
+ force?: boolean;
21
+ }
22
+
23
+ /**
24
+ * Scaffold a new Next.js project based on provided options.
25
+ *
26
+ * - Copies the default template to the target directory
27
+ * - Removes the target directory if it exists and --force is set
28
+ * - Optionally customizes the structure in future (e.g. remove unused files)
29
+ *
30
+ * @param options ScaffoldOptions for the project
31
+ */
32
+ export async function scaffoldProject(options: ScaffoldOptions) {
33
+ const targetPath = join(process.cwd(), options.projectName);
34
+ const templatePath = join(
35
+ import.meta.dir,
36
+ "..",
37
+ "templates",
38
+ "Projects",
39
+ "default"
40
+ );
41
+
42
+ // Check if target directory exists
43
+ if (existsSync(targetPath)) {
44
+ if (options.force) {
45
+ console.warn("⚠️ Target directory already exists, removing...");
46
+ await rm(targetPath, { recursive: true, force: true });
47
+ } else {
48
+ console.error(
49
+ "❌ Target directory already exists. Use --force to overwrite."
50
+ );
51
+ process.exit(1);
52
+ }
53
+ }
54
+
55
+ try {
56
+ console.log("📁 Creating project directory...");
57
+ await mkdir(targetPath, { recursive: true });
58
+
59
+ console.log("📦 Copying files from template...");
60
+ await cp(templatePath, targetPath, { recursive: true });
61
+
62
+ // Future customization: remove unused files if useTailwind === false, etc.
63
+
64
+ console.log("✅ Project scaffolded successfully!");
65
+ console.log(`➡️ cd ${options.projectName} && bun install && bun dev`);
66
+ } catch (err) {
67
+ console.error("❌ Error during scaffolding:", err);
68
+ process.exit(1);
69
+ }
70
+ }
@@ -0,0 +1,13 @@
1
+ "use client"; // DO NOT FORGET , this is a client component.
2
+ import { useTranslations } from "next-intl";
3
+
4
+ const Component = () => {
5
+ const t = useTranslations("componentPage");
6
+ return (
7
+ <div className="rounded border bg-white p-6 shadow-sm">
8
+ <h2 className="text-lg font-semibold mb-2">{t("Component.title")}</h2>
9
+ <p className="text-gray-600">{t("Component.description")}</p>
10
+ </div>
11
+ );
12
+ };
13
+ export default Component;
@@ -0,0 +1,4 @@
1
+ {
2
+ "title": "ui component template",
3
+ "description": "Welcome to your ui component template"
4
+ }
File without changes