@configjs/cli 1.1.3 → 1.1.4

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.
@@ -1533,7 +1533,7 @@ var jotaiPlugin = {
1533
1533
  description: "State management atomique",
1534
1534
  category: "state" /* STATE */,
1535
1535
  version: "^2.16.1",
1536
- frameworks: ["react"],
1536
+ frameworks: ["react", "nextjs"],
1537
1537
  incompatibleWith: ["@reduxjs/toolkit", "zustand"],
1538
1538
  /**
1539
1539
  * Détecte si Jotai est déjà installé
@@ -2344,7 +2344,7 @@ var axiosPlugin = {
2344
2344
  description: "Client HTTP bas\xE9 sur les promesses",
2345
2345
  category: "http" /* HTTP */,
2346
2346
  version: "^1.13.2",
2347
- frameworks: ["react", "vue", "svelte"],
2347
+ frameworks: ["react", "vue", "svelte", "nextjs"],
2348
2348
  /**
2349
2349
  * Détecte si Axios est déjà installé
2350
2350
  */
@@ -2679,7 +2679,7 @@ var tanstackQueryPlugin = {
2679
2679
  description: "Data fetching et caching",
2680
2680
  category: "http" /* HTTP */,
2681
2681
  version: "^5.90.16",
2682
- frameworks: ["react"],
2682
+ frameworks: ["react", "nextjs"],
2683
2683
  /**
2684
2684
  * Détecte si TanStack Query est déjà installé
2685
2685
  */
@@ -4421,7 +4421,7 @@ var reactHookFormPlugin = {
4421
4421
  description: "Gestion de formulaires performante pour React",
4422
4422
  category: "forms" /* FORMS */,
4423
4423
  version: "^7.69.0",
4424
- frameworks: ["react"],
4424
+ frameworks: ["react", "nextjs"],
4425
4425
  /**
4426
4426
  * Détecte si React Hook Form est déjà installé
4427
4427
  */
@@ -4946,7 +4946,7 @@ var zodPlugin = {
4946
4946
  description: "Validation de sch\xE9mas TypeScript-first",
4947
4947
  category: "forms" /* FORMS */,
4948
4948
  version: "^3.24.1",
4949
- frameworks: ["react", "vue", "svelte"],
4949
+ frameworks: ["react", "vue", "svelte", "nextjs"],
4950
4950
  /**
4951
4951
  * Détecte si Zod est déjà installé
4952
4952
  */
@@ -5599,7 +5599,7 @@ var radixUiPlugin = {
5599
5599
  description: "Composants UI headless et accessibles",
5600
5600
  category: "ui" /* UI */,
5601
5601
  version: "^1.2.4",
5602
- frameworks: ["react"],
5602
+ frameworks: ["react", "nextjs"],
5603
5603
  /**
5604
5604
  * Détecte si Radix UI est déjà installé
5605
5605
  */
@@ -6329,7 +6329,7 @@ var reactIconsPlugin = {
6329
6329
  description: "Biblioth\xE8que d'ic\xF4nes pour React",
6330
6330
  category: "ui" /* UI */,
6331
6331
  version: "^5.3.0",
6332
- frameworks: ["react"],
6332
+ frameworks: ["react", "nextjs"],
6333
6333
  /**
6334
6334
  * Détecte si React Icons est déjà installé
6335
6335
  */
@@ -6954,7 +6954,7 @@ var eslintPlugin = {
6954
6954
  description: "Linter JavaScript/TypeScript",
6955
6955
  category: "tooling" /* TOOLING */,
6956
6956
  version: "^9.39.2",
6957
- frameworks: ["react", "vue", "svelte"],
6957
+ frameworks: ["react", "vue", "svelte", "nextjs"],
6958
6958
  /**
6959
6959
  * Détecte si ESLint est déjà installé
6960
6960
  */
@@ -7204,7 +7204,7 @@ var prettierPlugin = {
7204
7204
  description: "Formateur de code",
7205
7205
  category: "tooling" /* TOOLING */,
7206
7206
  version: "^3.7.4",
7207
- frameworks: ["react", "vue", "svelte"],
7207
+ frameworks: ["react", "vue", "svelte", "nextjs"],
7208
7208
  /**
7209
7209
  * Détecte si Prettier est déjà installé
7210
7210
  */
@@ -7582,7 +7582,7 @@ var dateFnsPlugin = {
7582
7582
  description: "Manipulation de dates moderne",
7583
7583
  category: "tooling" /* TOOLING */,
7584
7584
  version: "^4.1.0",
7585
- frameworks: ["react", "vue", "svelte"],
7585
+ frameworks: ["react", "vue", "svelte", "nextjs"],
7586
7586
  /**
7587
7587
  * Détecte si date-fns est déjà installé
7588
7588
  */
@@ -8083,6 +8083,1661 @@ describe('Example Component', () => {
8083
8083
  `;
8084
8084
  }
8085
8085
 
8086
+ // src/plugins/css/tailwindcss-nextjs.ts
8087
+ import { join as join24 } from "path";
8088
+ var tailwindcssNextjsPlugin = {
8089
+ name: "tailwindcss-nextjs",
8090
+ displayName: "TailwindCSS (Next.js)",
8091
+ description: "Framework CSS utilitaire pour Next.js",
8092
+ category: "css" /* CSS */,
8093
+ version: "^3.4.1",
8094
+ frameworks: ["nextjs"],
8095
+ /**
8096
+ * Détecte si TailwindCSS est déjà installé
8097
+ */
8098
+ detect: (ctx) => {
8099
+ return ctx.dependencies["tailwindcss"] !== void 0 || ctx.devDependencies["tailwindcss"] !== void 0 || ctx.dependencies["postcss"] !== void 0 || ctx.devDependencies["postcss"] !== void 0 || ctx.dependencies["autoprefixer"] !== void 0 || ctx.devDependencies["autoprefixer"] !== void 0;
8100
+ },
8101
+ /**
8102
+ * Installe TailwindCSS, PostCSS et Autoprefixer pour Next.js
8103
+ */
8104
+ async install(ctx) {
8105
+ if (this.detect?.(ctx)) {
8106
+ logger.info("TailwindCSS is already installed");
8107
+ return {
8108
+ packages: {},
8109
+ success: true,
8110
+ message: "TailwindCSS already installed"
8111
+ };
8112
+ }
8113
+ const packages = ["tailwindcss", "postcss", "autoprefixer"];
8114
+ try {
8115
+ await installPackages(packages, {
8116
+ dev: true,
8117
+ packageManager: ctx.packageManager,
8118
+ projectRoot: ctx.projectRoot,
8119
+ exact: false,
8120
+ silent: false
8121
+ });
8122
+ logger.info("Successfully installed TailwindCSS for Next.js");
8123
+ return {
8124
+ packages: {
8125
+ devDependencies: packages
8126
+ },
8127
+ success: true,
8128
+ message: `Installed ${packages.join(", ")}`
8129
+ };
8130
+ } catch (error) {
8131
+ logger.error("Failed to install TailwindCSS:", error);
8132
+ return {
8133
+ packages: {},
8134
+ success: false,
8135
+ message: `Failed to install TailwindCSS: ${error instanceof Error ? error.message : String(error)}`
8136
+ };
8137
+ }
8138
+ },
8139
+ /**
8140
+ * Configure TailwindCSS dans le projet Next.js
8141
+ *
8142
+ * Crée/modifie :
8143
+ * - tailwind.config.js (ou .ts) : Configuration TailwindCSS
8144
+ * - postcss.config.js (ou .mjs) : Configuration PostCSS
8145
+ * - app/globals.css ou styles/globals.css : Import TailwindCSS
8146
+ */
8147
+ async configure(ctx) {
8148
+ const backupManager = new BackupManager();
8149
+ const writer = new ConfigWriter(backupManager);
8150
+ const files = [];
8151
+ const projectRoot = ctx.projectRoot;
8152
+ const extension = ctx.typescript ? "ts" : "js";
8153
+ const postcssExtension = ctx.typescript ? "js" : "mjs";
8154
+ try {
8155
+ const tailwindConfigPath = join24(
8156
+ projectRoot,
8157
+ `tailwind.config.${extension}`
8158
+ );
8159
+ const tailwindConfigExists = await checkPathExists(tailwindConfigPath);
8160
+ if (tailwindConfigExists) {
8161
+ const existingContent = await readFileContent(tailwindConfigPath);
8162
+ if (existingContent.includes("content:") && existingContent.includes("theme:")) {
8163
+ logger.info("TailwindCSS config already exists");
8164
+ } else {
8165
+ const tailwindConfig = getTailwindConfigContent(ctx, extension);
8166
+ await writer.writeFile(tailwindConfigPath, tailwindConfig, {
8167
+ backup: true
8168
+ });
8169
+ files.push({
8170
+ type: "modify",
8171
+ path: normalizePath(tailwindConfigPath),
8172
+ content: tailwindConfig,
8173
+ backup: true
8174
+ });
8175
+ }
8176
+ } else {
8177
+ const tailwindConfig = getTailwindConfigContent(ctx, extension);
8178
+ await writer.createFile(tailwindConfigPath, tailwindConfig);
8179
+ files.push({
8180
+ type: "create",
8181
+ path: normalizePath(tailwindConfigPath),
8182
+ content: tailwindConfig,
8183
+ backup: false
8184
+ });
8185
+ logger.info(`Created tailwind.config.${extension}`);
8186
+ }
8187
+ const postcssConfigPath = join24(
8188
+ projectRoot,
8189
+ `postcss.config.${postcssExtension}`
8190
+ );
8191
+ const postcssConfigExists = await checkPathExists(postcssConfigPath);
8192
+ if (!postcssConfigExists) {
8193
+ const postcssConfig = getPostcssConfigContent(postcssExtension);
8194
+ await writer.createFile(postcssConfigPath, postcssConfig);
8195
+ files.push({
8196
+ type: "create",
8197
+ path: normalizePath(postcssConfigPath),
8198
+ content: postcssConfig,
8199
+ backup: false
8200
+ });
8201
+ logger.info(`Created postcss.config.${postcssExtension}`);
8202
+ }
8203
+ const cssFiles = [
8204
+ join24(projectRoot, "app", "globals.css"),
8205
+ join24(projectRoot, ctx.srcDir, "app", "globals.css"),
8206
+ join24(projectRoot, "styles", "globals.css"),
8207
+ join24(projectRoot, ctx.srcDir, "styles", "globals.css")
8208
+ ];
8209
+ let cssFileModified = false;
8210
+ for (const cssPath of cssFiles) {
8211
+ const cssExists = await checkPathExists(cssPath);
8212
+ if (cssExists) {
8213
+ const cssContent = await readFileContent(cssPath);
8214
+ const modifiedCss = injectTailwindDirectives(cssContent);
8215
+ await writer.writeFile(cssPath, modifiedCss, { backup: true });
8216
+ files.push({
8217
+ type: "modify",
8218
+ path: normalizePath(cssPath),
8219
+ content: modifiedCss,
8220
+ backup: true
8221
+ });
8222
+ logger.info(`Updated ${cssPath} with TailwindCSS directives`);
8223
+ cssFileModified = true;
8224
+ break;
8225
+ }
8226
+ }
8227
+ if (!cssFileModified) {
8228
+ const cssPath = join24(projectRoot, "app", "globals.css");
8229
+ const cssContent = getCssContent2();
8230
+ await writer.createFile(cssPath, cssContent);
8231
+ files.push({
8232
+ type: "create",
8233
+ path: normalizePath(cssPath),
8234
+ content: cssContent,
8235
+ backup: false
8236
+ });
8237
+ logger.info(`Created ${cssPath} with TailwindCSS directives`);
8238
+ }
8239
+ return {
8240
+ files,
8241
+ success: true,
8242
+ message: "TailwindCSS configured successfully for Next.js"
8243
+ };
8244
+ } catch (error) {
8245
+ logger.error("Failed to configure TailwindCSS:", error);
8246
+ return {
8247
+ files,
8248
+ success: false,
8249
+ message: `Failed to configure TailwindCSS: ${error instanceof Error ? error.message : String(error)}`
8250
+ };
8251
+ }
8252
+ },
8253
+ /**
8254
+ * Rollback de la configuration TailwindCSS
8255
+ */
8256
+ async rollback(_ctx) {
8257
+ const backupManager = new BackupManager();
8258
+ try {
8259
+ await backupManager.restoreAll();
8260
+ logger.info("TailwindCSS configuration rolled back");
8261
+ } catch (error) {
8262
+ logger.error("Failed to rollback TailwindCSS configuration:", error);
8263
+ throw error;
8264
+ }
8265
+ }
8266
+ };
8267
+ function getTailwindConfigContent(ctx, extension) {
8268
+ const isTS = extension === "ts";
8269
+ const contentPaths = [
8270
+ "./pages/**/*.{js,ts,jsx,tsx,mdx}",
8271
+ "./components/**/*.{js,ts,jsx,tsx,mdx}",
8272
+ "./app/**/*.{js,ts,jsx,tsx,mdx}"
8273
+ ];
8274
+ if (ctx.srcDir && ctx.srcDir !== "src") {
8275
+ contentPaths.unshift(`./${ctx.srcDir}/**/*.{js,ts,jsx,tsx,mdx}`);
8276
+ }
8277
+ const contentArray = contentPaths.map((path) => ` "${path}"`).join(",\n");
8278
+ if (isTS) {
8279
+ return `import type { Config } from 'tailwindcss'
8280
+
8281
+ const config: Config = {
8282
+ content: [
8283
+ ${contentArray},
8284
+ ],
8285
+ theme: {
8286
+ extend: {},
8287
+ },
8288
+ plugins: [],
8289
+ }
8290
+ export default config
8291
+ `;
8292
+ }
8293
+ return `/** @type {import('tailwindcss').Config} */
8294
+ module.exports = {
8295
+ content: [
8296
+ ${contentArray},
8297
+ ],
8298
+ theme: {
8299
+ extend: {},
8300
+ },
8301
+ plugins: [],
8302
+ }
8303
+ `;
8304
+ }
8305
+ function getPostcssConfigContent(extension) {
8306
+ if (extension === "mjs") {
8307
+ return `/** @type {import('postcss-load-config').Config} */
8308
+ const config = {
8309
+ plugins: {
8310
+ tailwindcss: {},
8311
+ autoprefixer: {},
8312
+ },
8313
+ }
8314
+
8315
+ export default config
8316
+ `;
8317
+ }
8318
+ return `module.exports = {
8319
+ plugins: {
8320
+ tailwindcss: {},
8321
+ autoprefixer: {},
8322
+ },
8323
+ }
8324
+ `;
8325
+ }
8326
+ function getCssContent2() {
8327
+ return `@tailwind base;
8328
+ @tailwind components;
8329
+ @tailwind utilities;
8330
+ `;
8331
+ }
8332
+ function injectTailwindDirectives(content) {
8333
+ if (content.includes("@tailwind base") || content.includes("@tailwind components") || content.includes("@tailwind utilities")) {
8334
+ logger.warn("TailwindCSS directives already present in CSS file");
8335
+ return content;
8336
+ }
8337
+ return `@tailwind base;
8338
+ @tailwind components;
8339
+ @tailwind utilities;
8340
+
8341
+ ${content}`;
8342
+ }
8343
+
8344
+ // src/plugins/ui/shadcn-ui-nextjs.ts
8345
+ import { join as join25 } from "path";
8346
+ var shadcnUiNextjsPlugin = {
8347
+ name: "shadcn-ui-nextjs",
8348
+ displayName: "Shadcn/ui (Next.js)",
8349
+ description: "Composants UI accessibles et personnalisables pour Next.js",
8350
+ category: "ui" /* UI */,
8351
+ version: "^3.6.2",
8352
+ frameworks: ["nextjs"],
8353
+ requires: ["tailwindcss"],
8354
+ /**
8355
+ * Détecte si Shadcn/ui est déjà configuré
8356
+ */
8357
+ detect: (ctx) => {
8358
+ return ctx.dependencies["class-variance-authority"] !== void 0 || ctx.devDependencies["class-variance-authority"] !== void 0 || ctx.dependencies["@radix-ui/react-slot"] !== void 0 || ctx.devDependencies["@radix-ui/react-slot"] !== void 0;
8359
+ },
8360
+ /**
8361
+ * Installe les dépendances nécessaires pour Shadcn/ui
8362
+ */
8363
+ async install(ctx) {
8364
+ if (this.detect?.(ctx)) {
8365
+ logger.info("Shadcn/ui dependencies are already installed");
8366
+ return {
8367
+ packages: {},
8368
+ success: true,
8369
+ message: "Shadcn/ui dependencies already installed"
8370
+ };
8371
+ }
8372
+ try {
8373
+ const packages = [
8374
+ "class-variance-authority",
8375
+ "clsx",
8376
+ "tailwind-merge",
8377
+ "@radix-ui/react-slot"
8378
+ ];
8379
+ const radixPrimitives = [
8380
+ "@radix-ui/react-dialog",
8381
+ "@radix-ui/react-dropdown-menu",
8382
+ "@radix-ui/react-label",
8383
+ "@radix-ui/react-select",
8384
+ "@radix-ui/react-separator",
8385
+ "@radix-ui/react-toast"
8386
+ ];
8387
+ packages.push(...radixPrimitives);
8388
+ await installPackages(packages, {
8389
+ dev: false,
8390
+ packageManager: ctx.packageManager,
8391
+ projectRoot: ctx.projectRoot,
8392
+ exact: false,
8393
+ silent: false
8394
+ });
8395
+ logger.info("Successfully installed Shadcn/ui dependencies");
8396
+ return {
8397
+ packages: {
8398
+ dependencies: packages
8399
+ },
8400
+ success: true,
8401
+ message: `Installed Shadcn/ui dependencies: ${packages.join(", ")}`
8402
+ };
8403
+ } catch (error) {
8404
+ logger.error("Failed to install Shadcn/ui dependencies:", error);
8405
+ return {
8406
+ packages: {},
8407
+ success: false,
8408
+ message: `Failed to install Shadcn/ui dependencies: ${error instanceof Error ? error.message : String(error)}`
8409
+ };
8410
+ }
8411
+ },
8412
+ /**
8413
+ * Configure Shadcn/ui dans le projet Next.js
8414
+ *
8415
+ * Crée :
8416
+ * - components.json : Configuration Shadcn/ui adaptée Next.js
8417
+ * - lib/utils.ts : Utilitaires (cn helper) - adapté pour Next.js
8418
+ * - components/ui/button.tsx : Exemple de composant Button
8419
+ */
8420
+ async configure(ctx) {
8421
+ const backupManager = new BackupManager();
8422
+ const writer = new ConfigWriter(backupManager);
8423
+ const files = [];
8424
+ const projectRoot = ctx.projectRoot;
8425
+ const hasSrcDir = ctx.srcDir && ctx.srcDir !== ".";
8426
+ const baseDir = hasSrcDir ? join25(projectRoot, ctx.srcDir) : projectRoot;
8427
+ try {
8428
+ const componentsJsonPath = join25(projectRoot, "components.json");
8429
+ const componentsJsonExists = await checkPathExists(componentsJsonPath);
8430
+ if (componentsJsonExists) {
8431
+ logger.warn("components.json already exists, skipping creation");
8432
+ } else {
8433
+ const componentsJsonContent = getComponentsJsonContentNextjs(ctx);
8434
+ await writer.createFile(componentsJsonPath, componentsJsonContent);
8435
+ files.push({
8436
+ type: "create",
8437
+ path: normalizePath(componentsJsonPath),
8438
+ content: componentsJsonContent,
8439
+ backup: false
8440
+ });
8441
+ logger.info(`Created components.json: ${componentsJsonPath}`);
8442
+ }
8443
+ const libDir = join25(baseDir, "lib");
8444
+ await ensureDirectory(libDir);
8445
+ const utilsPath = join25(libDir, `utils.${ctx.typescript ? "ts" : "js"}`);
8446
+ const utilsExists = await checkPathExists(utilsPath);
8447
+ if (utilsExists) {
8448
+ logger.warn(
8449
+ "utils.ts already exists, checking if cn function is present"
8450
+ );
8451
+ const existingContent = await readFileContent(utilsPath);
8452
+ if (!existingContent.includes("export function cn")) {
8453
+ const cnFunction = ctx.typescript ? getCnFunctionTS2() : getCnFunctionJS2();
8454
+ const updatedContent = existingContent + "\n\n" + cnFunction;
8455
+ await writer.writeFile(utilsPath, updatedContent, { backup: true });
8456
+ files.push({
8457
+ type: "modify",
8458
+ path: normalizePath(utilsPath),
8459
+ content: updatedContent,
8460
+ backup: true
8461
+ });
8462
+ logger.info("Added cn function to utils.ts");
8463
+ }
8464
+ } else {
8465
+ const utilsContent = ctx.typescript ? getUtilsContentTS2() : getUtilsContentJS2();
8466
+ await writer.createFile(utilsPath, utilsContent);
8467
+ files.push({
8468
+ type: "create",
8469
+ path: normalizePath(utilsPath),
8470
+ content: utilsContent,
8471
+ backup: false
8472
+ });
8473
+ logger.info(`Created utils file: ${utilsPath}`);
8474
+ }
8475
+ const uiDir = join25(baseDir, "components", "ui");
8476
+ await ensureDirectory(uiDir);
8477
+ const buttonPath = join25(uiDir, `button.${ctx.typescript ? "tsx" : "jsx"}`);
8478
+ const buttonExists = await checkPathExists(buttonPath);
8479
+ if (!buttonExists) {
8480
+ const buttonContent = ctx.typescript ? getButtonContentTS4() : getButtonContentJS4();
8481
+ await writer.createFile(buttonPath, buttonContent);
8482
+ files.push({
8483
+ type: "create",
8484
+ path: normalizePath(buttonPath),
8485
+ content: buttonContent,
8486
+ backup: false
8487
+ });
8488
+ logger.info(`Created Button component: ${buttonPath}`);
8489
+ }
8490
+ const cssFiles = [
8491
+ join25(projectRoot, "app", "globals.css"),
8492
+ join25(baseDir, "app", "globals.css"),
8493
+ join25(projectRoot, "styles", "globals.css"),
8494
+ join25(baseDir, "styles", "globals.css")
8495
+ ];
8496
+ for (const cssPath of cssFiles) {
8497
+ const cssExists = await checkPathExists(cssPath);
8498
+ if (cssExists) {
8499
+ const cssContent = await readFileContent(cssPath);
8500
+ if (!cssContent.includes("@layer base")) {
8501
+ const shadcnVariables = getShadcnCSSVariables2();
8502
+ const updatedCss = cssContent + "\n\n" + shadcnVariables;
8503
+ await writer.writeFile(cssPath, updatedCss, { backup: true });
8504
+ files.push({
8505
+ type: "modify",
8506
+ path: normalizePath(cssPath),
8507
+ content: updatedCss,
8508
+ backup: true
8509
+ });
8510
+ logger.info(`Added Shadcn/ui CSS variables to ${cssPath}`);
8511
+ }
8512
+ break;
8513
+ }
8514
+ }
8515
+ return {
8516
+ files,
8517
+ success: true,
8518
+ message: "Shadcn/ui configured successfully for Next.js"
8519
+ };
8520
+ } catch (error) {
8521
+ logger.error("Failed to configure Shadcn/ui:", error);
8522
+ return {
8523
+ files,
8524
+ success: false,
8525
+ message: `Failed to configure Shadcn/ui: ${error instanceof Error ? error.message : String(error)}`
8526
+ };
8527
+ }
8528
+ },
8529
+ /**
8530
+ * Rollback de la configuration Shadcn/ui
8531
+ */
8532
+ async rollback(_ctx) {
8533
+ const backupManager = new BackupManager();
8534
+ try {
8535
+ await backupManager.restoreAll();
8536
+ logger.info("Shadcn/ui configuration rolled back");
8537
+ } catch (error) {
8538
+ logger.error("Failed to rollback Shadcn/ui configuration:", error);
8539
+ throw error;
8540
+ }
8541
+ }
8542
+ };
8543
+ function getComponentsJsonContentNextjs(ctx) {
8544
+ const hasSrcDir = ctx.srcDir && ctx.srcDir !== ".";
8545
+ const prefix = hasSrcDir ? `${ctx.srcDir}/` : "";
8546
+ const style = "new-york";
8547
+ const tailwindConfig = "tailwind.config.js";
8548
+ const tailwindCss = hasSrcDir ? `${ctx.srcDir}/app/globals.css` : "app/globals.css";
8549
+ const components = `${prefix}components`;
8550
+ const utils = `${prefix}lib/utils`;
8551
+ return JSON.stringify(
8552
+ {
8553
+ $schema: "https://ui.shadcn.com/schema.json",
8554
+ style,
8555
+ rsc: true,
8556
+ // React Server Components pour Next.js
8557
+ tsx: ctx.typescript,
8558
+ tailwind: {
8559
+ config: tailwindConfig,
8560
+ css: tailwindCss,
8561
+ baseColor: "slate",
8562
+ cssVariables: true,
8563
+ prefix: ""
8564
+ },
8565
+ aliases: {
8566
+ components,
8567
+ utils,
8568
+ ui: `${components}/ui`,
8569
+ lib: `${prefix}lib`,
8570
+ hooks: `${prefix}hooks`
8571
+ }
8572
+ },
8573
+ null,
8574
+ 2
8575
+ );
8576
+ }
8577
+ function getUtilsContentTS2() {
8578
+ return `import { type ClassValue, clsx } from 'clsx'
8579
+ import { twMerge } from 'tailwind-merge'
8580
+
8581
+ /**
8582
+ * Fonction utilitaire pour fusionner les classes TailwindCSS
8583
+ *
8584
+ * Utilise clsx pour g\xE9rer les conditions et twMerge pour r\xE9soudre les conflits
8585
+ *
8586
+ * @example
8587
+ * \`\`\`tsx
8588
+ * import { cn } from '@/lib/utils'
8589
+ *
8590
+ * <div className={cn('px-2 py-1', isActive && 'bg-blue-500')} />
8591
+ * \`\`\`
8592
+ */
8593
+ export function cn(...inputs: ClassValue[]) {
8594
+ return twMerge(clsx(inputs))
8595
+ }
8596
+ `;
8597
+ }
8598
+ function getUtilsContentJS2() {
8599
+ return `import { clsx } from 'clsx'
8600
+ import { twMerge } from 'tailwind-merge'
8601
+
8602
+ /**
8603
+ * Fonction utilitaire pour fusionner les classes TailwindCSS
8604
+ *
8605
+ * Utilise clsx pour g\xE9rer les conditions et twMerge pour r\xE9soudre les conflits
8606
+ *
8607
+ * @example
8608
+ * \`\`\`jsx
8609
+ * import { cn } from '@/lib/utils'
8610
+ *
8611
+ * <div className={cn('px-2 py-1', isActive && 'bg-blue-500')} />
8612
+ * \`\`\`
8613
+ */
8614
+ export function cn(...inputs) {
8615
+ return twMerge(clsx(inputs))
8616
+ }
8617
+ `;
8618
+ }
8619
+ function getCnFunctionTS2() {
8620
+ return `import { type ClassValue, clsx } from 'clsx'
8621
+ import { twMerge } from 'tailwind-merge'
8622
+
8623
+ export function cn(...inputs: ClassValue[]) {
8624
+ return twMerge(clsx(inputs))
8625
+ }
8626
+ `;
8627
+ }
8628
+ function getCnFunctionJS2() {
8629
+ return `import { clsx } from 'clsx'
8630
+ import { twMerge } from 'tailwind-merge'
8631
+
8632
+ export function cn(...inputs) {
8633
+ return twMerge(clsx(inputs))
8634
+ }
8635
+ `;
8636
+ }
8637
+ function getButtonContentTS4() {
8638
+ return `import * as React from 'react'
8639
+ import { Slot } from '@radix-ui/react-slot'
8640
+ import { cva, type VariantProps } from 'class-variance-authority'
8641
+
8642
+ import { cn } from '@/lib/utils'
8643
+
8644
+ const buttonVariants = cva(
8645
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
8646
+ {
8647
+ variants: {
8648
+ variant: {
8649
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
8650
+ destructive:
8651
+ 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
8652
+ outline:
8653
+ 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
8654
+ secondary:
8655
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
8656
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
8657
+ link: 'text-primary underline-offset-4 hover:underline',
8658
+ },
8659
+ size: {
8660
+ default: 'h-10 px-4 py-2',
8661
+ sm: 'h-9 rounded-md px-3',
8662
+ lg: 'h-11 rounded-md px-8',
8663
+ icon: 'h-10 w-10',
8664
+ },
8665
+ },
8666
+ defaultVariants: {
8667
+ variant: 'default',
8668
+ size: 'default',
8669
+ },
8670
+ }
8671
+ )
8672
+
8673
+ export interface ButtonProps
8674
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
8675
+ VariantProps<typeof buttonVariants> {
8676
+ asChild?: boolean
8677
+ }
8678
+
8679
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
8680
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
8681
+ const Comp = asChild ? Slot : 'button'
8682
+ return (
8683
+ <Comp
8684
+ className={cn(buttonVariants({ variant, size, className }))}
8685
+ ref={ref}
8686
+ {...props}
8687
+ />
8688
+ )
8689
+ }
8690
+ )
8691
+ Button.displayName = 'Button'
8692
+
8693
+ export { Button, buttonVariants }
8694
+ `;
8695
+ }
8696
+ function getButtonContentJS4() {
8697
+ return `import * as React from 'react'
8698
+ import { Slot } from '@radix-ui/react-slot'
8699
+ import { cva } from 'class-variance-authority'
8700
+
8701
+ import { cn } from '@/lib/utils'
8702
+
8703
+ const buttonVariants = cva(
8704
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
8705
+ {
8706
+ variants: {
8707
+ variant: {
8708
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
8709
+ destructive:
8710
+ 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
8711
+ outline:
8712
+ 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
8713
+ secondary:
8714
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
8715
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
8716
+ link: 'text-primary underline-offset-4 hover:underline',
8717
+ },
8718
+ size: {
8719
+ default: 'h-10 px-4 py-2',
8720
+ sm: 'h-9 rounded-md px-3',
8721
+ lg: 'h-11 rounded-md px-8',
8722
+ icon: 'h-10 w-10',
8723
+ },
8724
+ },
8725
+ defaultVariants: {
8726
+ variant: 'default',
8727
+ size: 'default',
8728
+ },
8729
+ }
8730
+ )
8731
+
8732
+ const Button = React.forwardRef(
8733
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
8734
+ const Comp = asChild ? Slot : 'button'
8735
+ return (
8736
+ <Comp
8737
+ className={cn(buttonVariants({ variant, size, className }))}
8738
+ ref={ref}
8739
+ {...props}
8740
+ />
8741
+ )
8742
+ }
8743
+ )
8744
+ Button.displayName = 'Button'
8745
+
8746
+ export { Button, buttonVariants }
8747
+ `;
8748
+ }
8749
+ function getShadcnCSSVariables2() {
8750
+ return `@layer base {
8751
+ :root {
8752
+ --background: 0 0% 100%;
8753
+ --foreground: 222.2 84% 4.9%;
8754
+ --card: 0 0% 100%;
8755
+ --card-foreground: 222.2 84% 4.9%;
8756
+ --popover: 0 0% 100%;
8757
+ --popover-foreground: 222.2 84% 4.9%;
8758
+ --primary: 222.2 47.4% 11.2%;
8759
+ --primary-foreground: 210 40% 98%;
8760
+ --secondary: 210 40% 96.1%;
8761
+ --secondary-foreground: 222.2 47.4% 11.2%;
8762
+ --muted: 210 40% 96.1%;
8763
+ --muted-foreground: 215.4 16.3% 46.9%;
8764
+ --accent: 210 40% 96.1%;
8765
+ --accent-foreground: 222.2 47.4% 11.2%;
8766
+ --destructive: 0 84.2% 60.2%;
8767
+ --destructive-foreground: 210 40% 98%;
8768
+ --border: 214.3 31.8% 91.4%;
8769
+ --input: 214.3 31.8% 91.4%;
8770
+ --ring: 222.2 84% 4.9%;
8771
+ --radius: 0.5rem;
8772
+ }
8773
+
8774
+ .dark {
8775
+ --background: 222.2 84% 4.9%;
8776
+ --foreground: 210 40% 98%;
8777
+ --card: 222.2 84% 4.9%;
8778
+ --card-foreground: 210 40% 98%;
8779
+ --popover: 222.2 84% 4.9%;
8780
+ --popover-foreground: 210 40% 98%;
8781
+ --primary: 210 40% 98%;
8782
+ --primary-foreground: 222.2 47.4% 11.2%;
8783
+ --secondary: 217.2 32.6% 17.5%;
8784
+ --secondary-foreground: 210 40% 98%;
8785
+ --muted: 217.2 32.6% 17.5%;
8786
+ --muted-foreground: 215 20.2% 65.1%;
8787
+ --accent: 217.2 32.6% 17.5%;
8788
+ --accent-foreground: 210 40% 98%;
8789
+ --destructive: 0 62.8% 30.6%;
8790
+ --destructive-foreground: 210 40% 98%;
8791
+ --border: 217.2 32.6% 17.5%;
8792
+ --input: 217.2 32.6% 17.5%;
8793
+ --ring: 212.7 26.8% 83.9%;
8794
+ }
8795
+ }
8796
+
8797
+ @layer base {
8798
+ * {
8799
+ @apply border-border;
8800
+ }
8801
+ body {
8802
+ @apply bg-background text-foreground;
8803
+ }
8804
+ }
8805
+ `;
8806
+ }
8807
+
8808
+ // src/plugins/ui/react-hot-toast-nextjs.ts
8809
+ import { join as join26 } from "path";
8810
+ var reactHotToastNextjsPlugin = {
8811
+ name: "react-hot-toast-nextjs",
8812
+ displayName: "React Hot Toast (Next.js)",
8813
+ description: "Notifications toast pour Next.js",
8814
+ category: "ui" /* UI */,
8815
+ version: "^2.4.1",
8816
+ frameworks: ["nextjs"],
8817
+ /**
8818
+ * Détecte si React Hot Toast est déjà installé
8819
+ */
8820
+ detect: (ctx) => {
8821
+ return ctx.dependencies["react-hot-toast"] !== void 0 || ctx.devDependencies["react-hot-toast"] !== void 0;
8822
+ },
8823
+ /**
8824
+ * Installe React Hot Toast
8825
+ */
8826
+ async install(ctx) {
8827
+ if (this.detect?.(ctx)) {
8828
+ logger.info("React Hot Toast is already installed");
8829
+ return {
8830
+ packages: {},
8831
+ success: true,
8832
+ message: "React Hot Toast already installed"
8833
+ };
8834
+ }
8835
+ try {
8836
+ const packages = ["react-hot-toast"];
8837
+ await installPackages(packages, {
8838
+ dev: false,
8839
+ packageManager: ctx.packageManager,
8840
+ projectRoot: ctx.projectRoot,
8841
+ exact: false,
8842
+ silent: false
8843
+ });
8844
+ logger.info("Successfully installed React Hot Toast");
8845
+ return {
8846
+ packages: {
8847
+ dependencies: packages
8848
+ },
8849
+ success: true,
8850
+ message: `Installed React Hot Toast: ${packages.join(", ")}`
8851
+ };
8852
+ } catch (error) {
8853
+ logger.error("Failed to install React Hot Toast:", error);
8854
+ return {
8855
+ packages: {},
8856
+ success: false,
8857
+ message: `Failed to install React Hot Toast: ${error instanceof Error ? error.message : String(error)}`
8858
+ };
8859
+ }
8860
+ },
8861
+ /**
8862
+ * Configure React Hot Toast dans le projet Next.js
8863
+ *
8864
+ * Ajoute :
8865
+ * - Toaster dans app/layout.tsx (App Router) ou pages/_app.tsx (Pages Router)
8866
+ */
8867
+ async configure(ctx) {
8868
+ const backupManager = new BackupManager();
8869
+ const writer = new ConfigWriter(backupManager);
8870
+ const files = [];
8871
+ const projectRoot = ctx.projectRoot;
8872
+ const extension = ctx.typescript ? "tsx" : "jsx";
8873
+ const hasSrcDir = ctx.srcDir && ctx.srcDir !== ".";
8874
+ try {
8875
+ const isAppRouter = ctx.nextjsRouter === "app" || ctx.nextjsRouter === void 0 && await checkPathExists(
8876
+ hasSrcDir ? join26(projectRoot, ctx.srcDir, "app") : join26(projectRoot, "app")
8877
+ );
8878
+ const appLayoutPath = hasSrcDir ? join26(projectRoot, ctx.srcDir, "app", `layout.${extension}`) : join26(projectRoot, "app", `layout.${extension}`);
8879
+ const pagesAppPath = hasSrcDir ? join26(projectRoot, ctx.srcDir, "pages", `_app.${extension}`) : join26(projectRoot, "pages", `_app.${extension}`);
8880
+ let targetPath = null;
8881
+ let targetContent = "";
8882
+ if (isAppRouter) {
8883
+ const appLayoutExists = await checkPathExists(appLayoutPath);
8884
+ if (appLayoutExists) {
8885
+ targetPath = appLayoutPath;
8886
+ targetContent = await readFileContent(appLayoutPath);
8887
+ } else {
8888
+ targetPath = appLayoutPath;
8889
+ targetContent = ctx.typescript ? getAppLayoutContentTS() : getAppLayoutContentJS();
8890
+ }
8891
+ } else {
8892
+ const pagesAppExists = await checkPathExists(pagesAppPath);
8893
+ if (pagesAppExists) {
8894
+ targetPath = pagesAppPath;
8895
+ targetContent = await readFileContent(pagesAppPath);
8896
+ } else {
8897
+ targetPath = pagesAppPath;
8898
+ targetContent = ctx.typescript ? getPagesAppContentTS() : getPagesAppContentJS();
8899
+ }
8900
+ }
8901
+ if (targetPath) {
8902
+ const hasImport = targetContent.includes("react-hot-toast");
8903
+ const hasToaster = targetContent.includes("<Toaster />");
8904
+ if (!hasImport || !hasToaster) {
8905
+ const updatedContent = injectToaster2(
8906
+ targetContent,
8907
+ ctx.typescript,
8908
+ isAppRouter
8909
+ );
8910
+ await writer.writeFile(targetPath, updatedContent, { backup: true });
8911
+ files.push({
8912
+ type: targetContent ? "modify" : "create",
8913
+ path: normalizePath(targetPath),
8914
+ content: updatedContent,
8915
+ backup: targetContent ? true : false
8916
+ });
8917
+ logger.info(`Added Toaster to ${targetPath}`);
8918
+ } else {
8919
+ logger.warn("Toaster already configured");
8920
+ }
8921
+ }
8922
+ return {
8923
+ files,
8924
+ success: true,
8925
+ message: "React Hot Toast configured successfully for Next.js"
8926
+ };
8927
+ } catch (error) {
8928
+ logger.error("Failed to configure React Hot Toast:", error);
8929
+ await backupManager.restoreAll();
8930
+ return {
8931
+ files,
8932
+ success: false,
8933
+ message: `Failed to configure React Hot Toast: ${error instanceof Error ? error.message : String(error)}`
8934
+ };
8935
+ }
8936
+ },
8937
+ /**
8938
+ * Rollback de la configuration React Hot Toast
8939
+ */
8940
+ async rollback(_ctx) {
8941
+ const backupManager = new BackupManager();
8942
+ try {
8943
+ await backupManager.restoreAll();
8944
+ logger.info("React Hot Toast configuration rolled back");
8945
+ } catch (error) {
8946
+ logger.error("Failed to rollback React Hot Toast configuration:", error);
8947
+ throw error;
8948
+ }
8949
+ }
8950
+ };
8951
+ function injectToaster2(content, _isTypeScript, isAppRouter) {
8952
+ if (content.includes("<Toaster />") || content.includes("<Toaster/>")) {
8953
+ return content;
8954
+ }
8955
+ let modifiedContent = content;
8956
+ if (!content.includes("react-hot-toast")) {
8957
+ const importStatement = "import { Toaster } from 'react-hot-toast'\n";
8958
+ const lines = modifiedContent.split("\n");
8959
+ let lastImportIndex = -1;
8960
+ for (let i = 0; i < lines.length; i++) {
8961
+ if (lines[i]?.startsWith("import ")) {
8962
+ lastImportIndex = i;
8963
+ }
8964
+ }
8965
+ if (lastImportIndex >= 0) {
8966
+ lines.splice(lastImportIndex + 1, 0, importStatement.trim());
8967
+ modifiedContent = lines.join("\n");
8968
+ } else {
8969
+ modifiedContent = importStatement + modifiedContent;
8970
+ }
8971
+ }
8972
+ if (isAppRouter) {
8973
+ if (modifiedContent.includes("return (")) {
8974
+ modifiedContent = modifiedContent.replace(
8975
+ /(return\s*\([\s\S]*?<body[^>]*>)/,
8976
+ (match) => `${match}
8977
+ <Toaster />`
8978
+ );
8979
+ } else if (modifiedContent.includes("<body")) {
8980
+ modifiedContent = modifiedContent.replace(
8981
+ /(<body[^>]*>)/,
8982
+ (match) => `${match}
8983
+ <Toaster />`
8984
+ );
8985
+ } else {
8986
+ modifiedContent = modifiedContent.replace(
8987
+ /(\s*)(<\/[^>]+>\s*)$/,
8988
+ (match, indent) => `${indent}<Toaster />
8989
+ ${match}`
8990
+ );
8991
+ }
8992
+ } else {
8993
+ if (modifiedContent.includes("return (")) {
8994
+ modifiedContent = modifiedContent.replace(
8995
+ /(return\s*\([\s\S]*?<)/,
8996
+ (match) => `${match}<Toaster />
8997
+ `
8998
+ );
8999
+ } else {
9000
+ modifiedContent = modifiedContent.replace(
9001
+ /(\s*)(<\/[^>]+>\s*)$/,
9002
+ (match, indent) => `${indent}<Toaster />
9003
+ ${match}`
9004
+ );
9005
+ }
9006
+ }
9007
+ return modifiedContent;
9008
+ }
9009
+ function getAppLayoutContentTS() {
9010
+ return `import type { Metadata } from 'next'
9011
+ import { Inter } from 'next/font/google'
9012
+ import { Toaster } from 'react-hot-toast'
9013
+ import './globals.css'
9014
+
9015
+ const inter = Inter({ subsets: ['latin'] })
9016
+
9017
+ export const metadata: Metadata = {
9018
+ title: 'Create Next App',
9019
+ description: 'Generated by create next app',
9020
+ }
9021
+
9022
+ export default function RootLayout({
9023
+ children,
9024
+ }: {
9025
+ children: React.ReactNode
9026
+ }) {
9027
+ return (
9028
+ <html lang="en">
9029
+ <body className={inter.className}>
9030
+ <Toaster />
9031
+ {children}
9032
+ </body>
9033
+ </html>
9034
+ )
9035
+ }
9036
+ `;
9037
+ }
9038
+ function getAppLayoutContentJS() {
9039
+ return `import { Inter } from 'next/font/google'
9040
+ import { Toaster } from 'react-hot-toast'
9041
+ import './globals.css'
9042
+
9043
+ const inter = Inter({ subsets: ['latin'] })
9044
+
9045
+ export const metadata = {
9046
+ title: 'Create Next App',
9047
+ description: 'Generated by create next app',
9048
+ }
9049
+
9050
+ export default function RootLayout({ children }) {
9051
+ return (
9052
+ <html lang="en">
9053
+ <body className={inter.className}>
9054
+ <Toaster />
9055
+ {children}
9056
+ </body>
9057
+ </html>
9058
+ )
9059
+ }
9060
+ `;
9061
+ }
9062
+ function getPagesAppContentTS() {
9063
+ return `import type { AppProps } from 'next/app'
9064
+ import { Toaster } from 'react-hot-toast'
9065
+ import '../styles/globals.css'
9066
+
9067
+ export default function App({ Component, pageProps }: AppProps) {
9068
+ return (
9069
+ <>
9070
+ <Toaster />
9071
+ <Component {...pageProps} />
9072
+ </>
9073
+ )
9074
+ }
9075
+ `;
9076
+ }
9077
+ function getPagesAppContentJS() {
9078
+ return `import { Toaster } from 'react-hot-toast'
9079
+ import '../styles/globals.css'
9080
+
9081
+ export default function App({ Component, pageProps }) {
9082
+ return (
9083
+ <>
9084
+ <Toaster />
9085
+ <Component {...pageProps} />
9086
+ </>
9087
+ )
9088
+ }
9089
+ `;
9090
+ }
9091
+
9092
+ // src/plugins/nextjs/image-optimization.ts
9093
+ import { join as join27 } from "path";
9094
+ var nextjsImageOptimizationPlugin = {
9095
+ name: "nextjs-image-optimization",
9096
+ displayName: "Next.js Image Optimization",
9097
+ description: "Configuration de l'optimisation d'images pour Next.js",
9098
+ category: "tooling" /* TOOLING */,
9099
+ version: void 0,
9100
+ frameworks: ["nextjs"],
9101
+ /**
9102
+ * Détecte si l'optimisation d'images est déjà configurée
9103
+ */
9104
+ detect: (ctx) => {
9105
+ return ctx.dependencies["next"] !== void 0 || ctx.devDependencies["next"] !== void 0;
9106
+ },
9107
+ /**
9108
+ * Pas d'installation nécessaire, Next.js inclut déjà l'optimisation d'images
9109
+ */
9110
+ install(_ctx) {
9111
+ logger.info(
9112
+ "Image optimization is built into Next.js, no installation needed"
9113
+ );
9114
+ return Promise.resolve({
9115
+ packages: {},
9116
+ success: true,
9117
+ message: "Image optimization is built into Next.js"
9118
+ });
9119
+ },
9120
+ /**
9121
+ * Configure l'optimisation d'images dans next.config.js
9122
+ */
9123
+ async configure(ctx) {
9124
+ const backupManager = new BackupManager();
9125
+ const writer = new ConfigWriter(backupManager);
9126
+ const files = [];
9127
+ const projectRoot = ctx.projectRoot;
9128
+ const extension = ctx.typescript ? "ts" : "js";
9129
+ try {
9130
+ const nextConfigPath = join27(projectRoot, `next.config.${extension}`);
9131
+ const nextConfigExists = await checkPathExists(nextConfigPath);
9132
+ if (nextConfigExists) {
9133
+ const existingContent = await readFileContent(nextConfigPath);
9134
+ const updatedContent = injectImageConfig(existingContent, extension);
9135
+ if (updatedContent !== existingContent) {
9136
+ await writer.writeFile(nextConfigPath, updatedContent, {
9137
+ backup: true
9138
+ });
9139
+ files.push({
9140
+ type: "modify",
9141
+ path: normalizePath(nextConfigPath),
9142
+ content: updatedContent,
9143
+ backup: true
9144
+ });
9145
+ logger.info(
9146
+ `Updated next.config.${extension} with image optimization`
9147
+ );
9148
+ }
9149
+ } else {
9150
+ const configContent = getNextConfigContent(extension);
9151
+ await writer.createFile(nextConfigPath, configContent);
9152
+ files.push({
9153
+ type: "create",
9154
+ path: normalizePath(nextConfigPath),
9155
+ content: configContent,
9156
+ backup: false
9157
+ });
9158
+ logger.info(`Created next.config.${extension} with image optimization`);
9159
+ }
9160
+ return {
9161
+ files,
9162
+ success: true,
9163
+ message: "Next.js image optimization configured successfully"
9164
+ };
9165
+ } catch (error) {
9166
+ logger.error("Failed to configure image optimization:", error);
9167
+ return {
9168
+ files,
9169
+ success: false,
9170
+ message: `Failed to configure image optimization: ${error instanceof Error ? error.message : String(error)}`
9171
+ };
9172
+ }
9173
+ },
9174
+ /**
9175
+ * Rollback de la configuration
9176
+ */
9177
+ async rollback(_ctx) {
9178
+ const backupManager = new BackupManager();
9179
+ try {
9180
+ await backupManager.restoreAll();
9181
+ logger.info("Image optimization configuration rolled back");
9182
+ } catch (error) {
9183
+ logger.error(
9184
+ "Failed to rollback image optimization configuration:",
9185
+ error
9186
+ );
9187
+ throw error;
9188
+ }
9189
+ }
9190
+ };
9191
+ function getNextConfigContent(extension) {
9192
+ if (extension === "ts") {
9193
+ return `import type { NextConfig } from 'next'
9194
+
9195
+ const nextConfig: NextConfig = {
9196
+ images: {
9197
+ remotePatterns: [
9198
+ {
9199
+ protocol: 'https',
9200
+ hostname: '**',
9201
+ },
9202
+ ],
9203
+ },
9204
+ }
9205
+
9206
+ export default nextConfig
9207
+ `;
9208
+ }
9209
+ return `/** @type {import('next').NextConfig} */
9210
+ const nextConfig = {
9211
+ images: {
9212
+ remotePatterns: [
9213
+ {
9214
+ protocol: 'https',
9215
+ hostname: '**',
9216
+ },
9217
+ ],
9218
+ },
9219
+ }
9220
+
9221
+ module.exports = nextConfig
9222
+ `;
9223
+ }
9224
+ function injectImageConfig(content, _extension) {
9225
+ if (content.includes("images:") && content.includes("remotePatterns")) {
9226
+ logger.warn("Image configuration already exists in next.config");
9227
+ return content;
9228
+ }
9229
+ let modifiedContent = content;
9230
+ const configRegex = /(const\s+nextConfig\s*=\s*\{|nextConfig\s*=\s*\{)([\s\S]*?)(\})/m;
9231
+ if (configRegex.test(modifiedContent)) {
9232
+ modifiedContent = modifiedContent.replace(
9233
+ configRegex,
9234
+ (match, start, configContent) => {
9235
+ if (configContent.includes("images:")) {
9236
+ return match;
9237
+ }
9238
+ const trimmed = configContent.trim();
9239
+ const hasTrailingComma = trimmed.endsWith(",");
9240
+ const imageConfig = ` images: {
9241
+ remotePatterns: [
9242
+ {
9243
+ protocol: 'https',
9244
+ hostname: '**',
9245
+ },
9246
+ ],
9247
+ },${hasTrailingComma ? "" : "\n"}`;
9248
+ return `${start}${trimmed}${hasTrailingComma ? "" : ","}
9249
+ ${imageConfig}
9250
+ }`;
9251
+ }
9252
+ );
9253
+ } else {
9254
+ const imageConfig = `
9255
+ images: {
9256
+ remotePatterns: [
9257
+ {
9258
+ protocol: 'https',
9259
+ hostname: '**',
9260
+ },
9261
+ ],
9262
+ },`;
9263
+ modifiedContent = modifiedContent.replace(
9264
+ /(\}\s*)$/,
9265
+ (match) => `${imageConfig}
9266
+ ${match}`
9267
+ );
9268
+ }
9269
+ return modifiedContent;
9270
+ }
9271
+
9272
+ // src/plugins/nextjs/font-optimization.ts
9273
+ import { join as join28 } from "path";
9274
+ var nextjsFontOptimizationPlugin = {
9275
+ name: "nextjs-font-optimization",
9276
+ displayName: "Next.js Font Optimization",
9277
+ description: "Configuration de l'optimisation de polices avec next/font",
9278
+ category: "tooling" /* TOOLING */,
9279
+ version: void 0,
9280
+ frameworks: ["nextjs"],
9281
+ /**
9282
+ * Détecte si l'optimisation de polices est déjà configurée
9283
+ */
9284
+ detect: (ctx) => {
9285
+ return ctx.dependencies["next"] !== void 0 || ctx.devDependencies["next"] !== void 0;
9286
+ },
9287
+ /**
9288
+ * Pas d'installation nécessaire, next/font est inclus dans Next.js
9289
+ */
9290
+ install(_ctx) {
9291
+ logger.info(
9292
+ "Font optimization is built into Next.js, no installation needed"
9293
+ );
9294
+ return Promise.resolve({
9295
+ packages: {},
9296
+ success: true,
9297
+ message: "Font optimization is built into Next.js"
9298
+ });
9299
+ },
9300
+ /**
9301
+ * Configure l'optimisation de polices dans app/layout.tsx ou pages/_app.tsx
9302
+ */
9303
+ async configure(ctx) {
9304
+ const backupManager = new BackupManager();
9305
+ const writer = new ConfigWriter(backupManager);
9306
+ const files = [];
9307
+ const projectRoot = ctx.projectRoot;
9308
+ const extension = ctx.typescript ? "tsx" : "jsx";
9309
+ const hasSrcDir = ctx.srcDir && ctx.srcDir !== ".";
9310
+ try {
9311
+ const isAppRouter = ctx.nextjsRouter === "app" || ctx.nextjsRouter === void 0 && await checkPathExists(
9312
+ hasSrcDir ? join28(projectRoot, ctx.srcDir, "app") : join28(projectRoot, "app")
9313
+ );
9314
+ const appLayoutPath = hasSrcDir ? join28(projectRoot, ctx.srcDir, "app", `layout.${extension}`) : join28(projectRoot, "app", `layout.${extension}`);
9315
+ const pagesAppPath = hasSrcDir ? join28(projectRoot, ctx.srcDir, "pages", `_app.${extension}`) : join28(projectRoot, "pages", `_app.${extension}`);
9316
+ let targetPath = null;
9317
+ let targetContent = "";
9318
+ if (isAppRouter) {
9319
+ const appLayoutExists = await checkPathExists(appLayoutPath);
9320
+ if (appLayoutExists) {
9321
+ targetPath = appLayoutPath;
9322
+ targetContent = await readFileContent(appLayoutPath);
9323
+ }
9324
+ } else {
9325
+ const pagesAppExists = await checkPathExists(pagesAppPath);
9326
+ if (pagesAppExists) {
9327
+ targetPath = pagesAppPath;
9328
+ targetContent = await readFileContent(pagesAppPath);
9329
+ }
9330
+ }
9331
+ if (targetPath && targetContent) {
9332
+ const hasFontImport = targetContent.includes("from 'next/font");
9333
+ const hasGoogleFont = targetContent.includes("Inter") || targetContent.includes("Roboto");
9334
+ if (!hasFontImport || !hasGoogleFont) {
9335
+ const updatedContent = injectFontOptimization(
9336
+ targetContent,
9337
+ ctx.typescript,
9338
+ isAppRouter
9339
+ );
9340
+ await writer.writeFile(targetPath, updatedContent, { backup: true });
9341
+ files.push({
9342
+ type: "modify",
9343
+ path: normalizePath(targetPath),
9344
+ content: updatedContent,
9345
+ backup: true
9346
+ });
9347
+ logger.info(`Added font optimization to ${targetPath}`);
9348
+ } else {
9349
+ logger.warn("Font optimization already configured");
9350
+ }
9351
+ } else {
9352
+ logger.warn("Could not find layout or _app file to configure fonts");
9353
+ }
9354
+ return {
9355
+ files,
9356
+ success: true,
9357
+ message: "Next.js font optimization configured successfully"
9358
+ };
9359
+ } catch (error) {
9360
+ logger.error("Failed to configure font optimization:", error);
9361
+ return {
9362
+ files,
9363
+ success: false,
9364
+ message: `Failed to configure font optimization: ${error instanceof Error ? error.message : String(error)}`
9365
+ };
9366
+ }
9367
+ },
9368
+ /**
9369
+ * Rollback de la configuration
9370
+ */
9371
+ async rollback(_ctx) {
9372
+ const backupManager = new BackupManager();
9373
+ try {
9374
+ await backupManager.restoreAll();
9375
+ logger.info("Font optimization configuration rolled back");
9376
+ } catch (error) {
9377
+ logger.error("Failed to rollback font optimization configuration:", error);
9378
+ throw error;
9379
+ }
9380
+ }
9381
+ };
9382
+ function injectFontOptimization(content, _isTypeScript, isAppRouter) {
9383
+ if (content.includes("from 'next/font") || content.includes('from "next/font')) {
9384
+ return content;
9385
+ }
9386
+ let modifiedContent = content;
9387
+ const fontImport = "import { Inter } from 'next/font/google'\n";
9388
+ const lines = modifiedContent.split("\n");
9389
+ let lastImportIndex = -1;
9390
+ for (let i = 0; i < lines.length; i++) {
9391
+ if (lines[i]?.startsWith("import ")) {
9392
+ lastImportIndex = i;
9393
+ }
9394
+ }
9395
+ if (lastImportIndex >= 0) {
9396
+ lines.splice(lastImportIndex + 1, 0, fontImport.trim());
9397
+ modifiedContent = lines.join("\n");
9398
+ } else {
9399
+ modifiedContent = fontImport + modifiedContent;
9400
+ }
9401
+ const fontConfig = isAppRouter ? `
9402
+ const inter = Inter({ subsets: ['latin'] })
9403
+ ` : `
9404
+ const inter = Inter({ subsets: ['latin'] })
9405
+ `;
9406
+ modifiedContent = modifiedContent.replace(
9407
+ /(import[^;]+;[\s\S]*?)(\n)/,
9408
+ (match) => `${match}${fontConfig}`
9409
+ );
9410
+ if (isAppRouter) {
9411
+ if (modifiedContent.includes("<body")) {
9412
+ modifiedContent = modifiedContent.replace(
9413
+ /<body([^>]*)>/,
9414
+ (match, attrs) => {
9415
+ if (attrs.includes("className")) {
9416
+ return match;
9417
+ }
9418
+ return `<body${attrs} className={inter.className}>`;
9419
+ }
9420
+ );
9421
+ }
9422
+ } else {
9423
+ if (modifiedContent.includes("return (")) {
9424
+ modifiedContent = modifiedContent.replace(
9425
+ /(return\s*\([\s\S]*?<div[^>]*)/,
9426
+ (match) => {
9427
+ if (match.includes("className")) {
9428
+ return match;
9429
+ }
9430
+ return `${match} className={inter.className}`;
9431
+ }
9432
+ );
9433
+ }
9434
+ }
9435
+ return modifiedContent;
9436
+ }
9437
+
9438
+ // src/plugins/nextjs/middleware.ts
9439
+ import { join as join29 } from "path";
9440
+ var nextjsMiddlewarePlugin = {
9441
+ name: "nextjs-middleware",
9442
+ displayName: "Next.js Middleware",
9443
+ description: "Template de middleware pour Next.js",
9444
+ category: "tooling" /* TOOLING */,
9445
+ version: void 0,
9446
+ frameworks: ["nextjs"],
9447
+ /**
9448
+ * Détecte si le middleware existe déjà
9449
+ */
9450
+ detect: (_ctx) => {
9451
+ return false;
9452
+ },
9453
+ /**
9454
+ * Pas d'installation nécessaire
9455
+ */
9456
+ install(_ctx) {
9457
+ logger.info("Middleware is a file, no installation needed");
9458
+ return Promise.resolve({
9459
+ packages: {},
9460
+ success: true,
9461
+ message: "Middleware is a file, no installation needed"
9462
+ });
9463
+ },
9464
+ /**
9465
+ * Crée le fichier middleware.ts/js à la racine du projet
9466
+ */
9467
+ async configure(ctx) {
9468
+ const backupManager = new BackupManager();
9469
+ const writer = new ConfigWriter(backupManager);
9470
+ const files = [];
9471
+ const projectRoot = ctx.projectRoot;
9472
+ const extension = ctx.typescript ? "ts" : "js";
9473
+ try {
9474
+ const middlewarePath = join29(projectRoot, `middleware.${extension}`);
9475
+ const middlewareExists = await checkPathExists(middlewarePath);
9476
+ if (middlewareExists) {
9477
+ logger.warn("middleware.ts already exists, skipping creation");
9478
+ } else {
9479
+ const middlewareContent = getMiddlewareContent(extension);
9480
+ await writer.createFile(middlewarePath, middlewareContent);
9481
+ files.push({
9482
+ type: "create",
9483
+ path: normalizePath(middlewarePath),
9484
+ content: middlewareContent,
9485
+ backup: false
9486
+ });
9487
+ logger.info(`Created middleware.${extension}`);
9488
+ }
9489
+ return {
9490
+ files,
9491
+ success: true,
9492
+ message: "Next.js middleware created successfully"
9493
+ };
9494
+ } catch (error) {
9495
+ logger.error("Failed to create middleware:", error);
9496
+ return {
9497
+ files,
9498
+ success: false,
9499
+ message: `Failed to create middleware: ${error instanceof Error ? error.message : String(error)}`
9500
+ };
9501
+ }
9502
+ },
9503
+ /**
9504
+ * Rollback de la configuration
9505
+ */
9506
+ async rollback(_ctx) {
9507
+ const backupManager = new BackupManager();
9508
+ try {
9509
+ await backupManager.restoreAll();
9510
+ logger.info("Middleware configuration rolled back");
9511
+ } catch (error) {
9512
+ logger.error("Failed to rollback middleware configuration:", error);
9513
+ throw error;
9514
+ }
9515
+ }
9516
+ };
9517
+ function getMiddlewareContent(extension) {
9518
+ if (extension === "ts") {
9519
+ return `import { NextResponse } from 'next/server'
9520
+ import type { NextRequest } from 'next/server'
9521
+
9522
+ export function middleware(request: NextRequest) {
9523
+ // Ajoutez votre logique de middleware ici
9524
+ // Exemple : redirection, authentification, etc.
9525
+
9526
+ return NextResponse.next()
9527
+ }
9528
+
9529
+ // Configurez les chemins sur lesquels le middleware s'applique
9530
+ export const config = {
9531
+ matcher: [
9532
+ /*
9533
+ * Match all request paths except for the ones starting with:
9534
+ * - api (API routes)
9535
+ * - _next/static (static files)
9536
+ * - _next/image (image optimization files)
9537
+ * - favicon.ico (favicon file)
9538
+ */
9539
+ '/((?!api|_next/static|_next/image|favicon.ico).*)',
9540
+ ],
9541
+ }
9542
+ `;
9543
+ }
9544
+ return `import { NextResponse } from 'next/server'
9545
+
9546
+ export function middleware(request) {
9547
+ // Ajoutez votre logique de middleware ici
9548
+ // Exemple : redirection, authentification, etc.
9549
+
9550
+ return NextResponse.next()
9551
+ }
9552
+
9553
+ // Configurez les chemins sur lesquels le middleware s'applique
9554
+ export const config = {
9555
+ matcher: [
9556
+ /*
9557
+ * Match all request paths except for the ones starting with:
9558
+ * - api (API routes)
9559
+ * - _next/static (static files)
9560
+ * - _next/image (image optimization files)
9561
+ * - favicon.ico (favicon file)
9562
+ */
9563
+ '/((?!api|_next/static|_next/image|favicon.ico).*)',
9564
+ ],
9565
+ }
9566
+ `;
9567
+ }
9568
+
9569
+ // src/plugins/nextjs/api-routes.ts
9570
+ import { join as join30 } from "path";
9571
+ var nextjsApiRoutesPlugin = {
9572
+ name: "nextjs-api-routes",
9573
+ displayName: "Next.js API Routes",
9574
+ description: "Template d'API routes pour Next.js",
9575
+ category: "tooling" /* TOOLING */,
9576
+ version: void 0,
9577
+ frameworks: ["nextjs"],
9578
+ /**
9579
+ * Détecte si des API routes existent déjà
9580
+ */
9581
+ detect: (_ctx) => {
9582
+ return false;
9583
+ },
9584
+ /**
9585
+ * Pas d'installation nécessaire
9586
+ */
9587
+ install(_ctx) {
9588
+ logger.info("API routes are files, no installation needed");
9589
+ return Promise.resolve({
9590
+ packages: {},
9591
+ success: true,
9592
+ message: "API routes are files, no installation needed"
9593
+ });
9594
+ },
9595
+ /**
9596
+ * Crée un exemple d'API route
9597
+ */
9598
+ async configure(ctx) {
9599
+ const backupManager = new BackupManager();
9600
+ const writer = new ConfigWriter(backupManager);
9601
+ const files = [];
9602
+ const projectRoot = ctx.projectRoot;
9603
+ const extension = ctx.typescript ? "ts" : "js";
9604
+ const hasSrcDir = ctx.srcDir && ctx.srcDir !== ".";
9605
+ try {
9606
+ const appApiPath = hasSrcDir ? join30(
9607
+ projectRoot,
9608
+ ctx.srcDir,
9609
+ "app",
9610
+ "api",
9611
+ "hello",
9612
+ `route.${extension}`
9613
+ ) : join30(projectRoot, "app", "api", "hello", `route.${extension}`);
9614
+ const pagesApiPath = hasSrcDir ? join30(projectRoot, ctx.srcDir, "pages", "api", `hello.${extension}`) : join30(projectRoot, "pages", "api", `hello.${extension}`);
9615
+ const appApiExists = await checkPathExists(appApiPath);
9616
+ const pagesApiExists = await checkPathExists(pagesApiPath);
9617
+ if (appApiExists || pagesApiExists) {
9618
+ logger.warn("API route already exists");
9619
+ return {
9620
+ files: [],
9621
+ success: true,
9622
+ message: "API route already exists"
9623
+ };
9624
+ }
9625
+ const isAppRouter = ctx.nextjsRouter === "app" || ctx.nextjsRouter === void 0 && await checkPathExists(
9626
+ hasSrcDir ? join30(projectRoot, ctx.srcDir, "app") : join30(projectRoot, "app")
9627
+ );
9628
+ let targetPath;
9629
+ if (isAppRouter) {
9630
+ targetPath = appApiPath;
9631
+ await ensureDirectory(
9632
+ join30(projectRoot, hasSrcDir ? ctx.srcDir : ".", "app", "api", "hello")
9633
+ );
9634
+ } else {
9635
+ targetPath = pagesApiPath;
9636
+ await ensureDirectory(
9637
+ join30(projectRoot, hasSrcDir ? ctx.srcDir : ".", "pages", "api")
9638
+ );
9639
+ }
9640
+ const apiContent = getApiRouteContent(extension, isAppRouter);
9641
+ await writer.createFile(targetPath, apiContent);
9642
+ files.push({
9643
+ type: "create",
9644
+ path: normalizePath(targetPath),
9645
+ content: apiContent,
9646
+ backup: false
9647
+ });
9648
+ logger.info(`Created API route: ${targetPath}`);
9649
+ return {
9650
+ files,
9651
+ success: true,
9652
+ message: "Next.js API route created successfully"
9653
+ };
9654
+ } catch (error) {
9655
+ logger.error("Failed to create API route:", error);
9656
+ return {
9657
+ files,
9658
+ success: false,
9659
+ message: `Failed to create API route: ${error instanceof Error ? error.message : String(error)}`
9660
+ };
9661
+ }
9662
+ },
9663
+ /**
9664
+ * Rollback de la configuration
9665
+ */
9666
+ async rollback(_ctx) {
9667
+ const backupManager = new BackupManager();
9668
+ try {
9669
+ await backupManager.restoreAll();
9670
+ logger.info("API route configuration rolled back");
9671
+ } catch (error) {
9672
+ logger.error("Failed to rollback API route configuration:", error);
9673
+ throw error;
9674
+ }
9675
+ }
9676
+ };
9677
+ function getApiRouteContent(extension, isAppRouter) {
9678
+ if (isAppRouter) {
9679
+ if (extension === "ts") {
9680
+ return `import { NextResponse } from 'next/server'
9681
+ import type { NextRequest } from 'next/server'
9682
+
9683
+ export async function GET(request: NextRequest) {
9684
+ return NextResponse.json({ message: 'Hello from Next.js API!' })
9685
+ }
9686
+
9687
+ export async function POST(request: NextRequest) {
9688
+ const body = await request.json()
9689
+ return NextResponse.json({ message: 'POST request received', data: body })
9690
+ }
9691
+ `;
9692
+ }
9693
+ return `import { NextResponse } from 'next/server'
9694
+
9695
+ export async function GET(request) {
9696
+ return NextResponse.json({ message: 'Hello from Next.js API!' })
9697
+ }
9698
+
9699
+ export async function POST(request) {
9700
+ const body = await request.json()
9701
+ return NextResponse.json({ message: 'POST request received', data: body })
9702
+ }
9703
+ `;
9704
+ } else {
9705
+ if (extension === "ts") {
9706
+ return `import type { NextApiRequest, NextApiResponse } from 'next'
9707
+
9708
+ type Data = {
9709
+ message: string
9710
+ }
9711
+
9712
+ export default function handler(
9713
+ req: NextApiRequest,
9714
+ res: NextApiResponse<Data>
9715
+ ) {
9716
+ if (req.method === 'GET') {
9717
+ res.status(200).json({ message: 'Hello from Next.js API!' })
9718
+ } else if (req.method === 'POST') {
9719
+ res.status(200).json({ message: 'POST request received', data: req.body })
9720
+ } else {
9721
+ res.setHeader('Allow', ['GET', 'POST'])
9722
+ res.status(405).end(\`Method \${req.method} Not Allowed\`)
9723
+ }
9724
+ }
9725
+ `;
9726
+ }
9727
+ return `export default function handler(req, res) {
9728
+ if (req.method === 'GET') {
9729
+ res.status(200).json({ message: 'Hello from Next.js API!' })
9730
+ } else if (req.method === 'POST') {
9731
+ res.status(200).json({ message: 'POST request received', data: req.body })
9732
+ } else {
9733
+ res.setHeader('Allow', ['GET', 'POST'])
9734
+ res.status(405).end(\`Method \${req.method} Not Allowed\`)
9735
+ }
9736
+ }
9737
+ `;
9738
+ }
9739
+ }
9740
+
8086
9741
  // src/plugins/registry.ts
8087
9742
  var pluginRegistry = [
8088
9743
  // Routing
@@ -8097,6 +9752,7 @@ var pluginRegistry = [
8097
9752
  tanstackQueryPlugin,
8098
9753
  // CSS
8099
9754
  tailwindcssPlugin,
9755
+ tailwindcssNextjsPlugin,
8100
9756
  styledComponentsPlugin,
8101
9757
  emotionPlugin,
8102
9758
  reactBootstrapPlugin,
@@ -8105,9 +9761,11 @@ var pluginRegistry = [
8105
9761
  zodPlugin,
8106
9762
  // UI
8107
9763
  shadcnUiPlugin,
9764
+ shadcnUiNextjsPlugin,
8108
9765
  radixUiPlugin,
8109
9766
  reactIconsPlugin,
8110
9767
  reactHotToastPlugin,
9768
+ reactHotToastNextjsPlugin,
8111
9769
  framerMotionPlugin,
8112
9770
  // Tooling
8113
9771
  eslintPlugin,
@@ -8115,7 +9773,12 @@ var pluginRegistry = [
8115
9773
  huskyPlugin,
8116
9774
  dateFnsPlugin,
8117
9775
  // Testing
8118
- reactTestingLibraryPlugin
9776
+ reactTestingLibraryPlugin,
9777
+ // Next.js specific
9778
+ nextjsImageOptimizationPlugin,
9779
+ nextjsFontOptimizationPlugin,
9780
+ nextjsMiddlewarePlugin,
9781
+ nextjsApiRoutesPlugin
8119
9782
  // etc.
8120
9783
  ];
8121
9784
  function validatePlugin(plugin) {