create-enterprise-next 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1774 -13
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command as Command2 } from "commander";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import fs4 from "fs";
|
|
6
|
+
import path4 from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
|
|
9
9
|
// src/cli/flags.ts
|
|
@@ -47,6 +47,20 @@ var projectConfigSchema = z.object({
|
|
|
47
47
|
path: ["database"]
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
+
if (data.orm === "drizzle" && data.database === "mongo") {
|
|
51
|
+
ctx.addIssue({
|
|
52
|
+
code: z.ZodIssueCode.custom,
|
|
53
|
+
message: "Drizzle ORM does not support MongoDB.",
|
|
54
|
+
path: ["database"]
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (data.monorepo && data.packageManager !== "pnpm") {
|
|
58
|
+
ctx.addIssue({
|
|
59
|
+
code: z.ZodIssueCode.custom,
|
|
60
|
+
message: "Monorepo setup is only supported with pnpm package manager.",
|
|
61
|
+
path: ["packageManager"]
|
|
62
|
+
});
|
|
63
|
+
}
|
|
50
64
|
});
|
|
51
65
|
|
|
52
66
|
// src/cli/flags.ts
|
|
@@ -426,13 +440,1761 @@ async function runInteractivePrompts(partial) {
|
|
|
426
440
|
};
|
|
427
441
|
}
|
|
428
442
|
|
|
429
|
-
// src/
|
|
443
|
+
// src/generate/generate-project.ts
|
|
444
|
+
import * as clack2 from "@clack/prompts";
|
|
445
|
+
|
|
446
|
+
// src/generate/context.ts
|
|
447
|
+
function createGenerationContext(config, cwd = process.cwd()) {
|
|
448
|
+
return {
|
|
449
|
+
config,
|
|
450
|
+
cwd,
|
|
451
|
+
targetDir: config.projectName.startsWith("/") ? config.projectName : `${cwd}/${config.projectName}`,
|
|
452
|
+
manifest: {
|
|
453
|
+
dependencies: {},
|
|
454
|
+
devDependencies: {},
|
|
455
|
+
scripts: {},
|
|
456
|
+
rootDependencies: {},
|
|
457
|
+
rootDevDependencies: {},
|
|
458
|
+
rootScripts: {},
|
|
459
|
+
env: {},
|
|
460
|
+
files: /* @__PURE__ */ new Map()
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
function addDependency(context, name, version) {
|
|
465
|
+
context.manifest.dependencies[name] = version;
|
|
466
|
+
}
|
|
467
|
+
function addDevDependency(context, name, version) {
|
|
468
|
+
context.manifest.devDependencies[name] = version;
|
|
469
|
+
}
|
|
470
|
+
function addScript(context, name, command) {
|
|
471
|
+
context.manifest.scripts[name] = command;
|
|
472
|
+
}
|
|
473
|
+
function addRootDevDependency(context, name, version) {
|
|
474
|
+
context.manifest.rootDevDependencies[name] = version;
|
|
475
|
+
}
|
|
476
|
+
function addEnv(context, key, defaultValue = "") {
|
|
477
|
+
context.manifest.env[key] = defaultValue;
|
|
478
|
+
}
|
|
479
|
+
function addAppFile(context, relativePath, content) {
|
|
480
|
+
const normalizedPath = context.config.monorepo ? `apps/web/${relativePath}` : relativePath;
|
|
481
|
+
context.manifest.files.set(normalizedPath, content);
|
|
482
|
+
}
|
|
483
|
+
function addProjectFile(context, relativePath, content) {
|
|
484
|
+
context.manifest.files.set(relativePath, content);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/generate/filesystem.ts
|
|
488
|
+
import fs3 from "fs/promises";
|
|
489
|
+
import { existsSync } from "fs";
|
|
490
|
+
import path3 from "path";
|
|
491
|
+
function safeResolve(targetDir, relativePath) {
|
|
492
|
+
const absoluteTarget = path3.resolve(targetDir);
|
|
493
|
+
const resolved = path3.resolve(absoluteTarget, relativePath);
|
|
494
|
+
const relative = path3.relative(absoluteTarget, resolved);
|
|
495
|
+
if (relative.startsWith("..") || path3.isAbsolute(relative)) {
|
|
496
|
+
throw new Error(`Directory traversal detected: "${relativePath}" resolves outside of target directory "${absoluteTarget}".`);
|
|
497
|
+
}
|
|
498
|
+
return resolved;
|
|
499
|
+
}
|
|
500
|
+
async function ensureDirectory(targetDir, relativePath) {
|
|
501
|
+
const resolved = safeResolve(targetDir, relativePath);
|
|
502
|
+
await fs3.mkdir(resolved, { recursive: true });
|
|
503
|
+
return resolved;
|
|
504
|
+
}
|
|
505
|
+
async function writeFile(targetDir, relativePath, content) {
|
|
506
|
+
const resolved = safeResolve(targetDir, relativePath);
|
|
507
|
+
await fs3.mkdir(path3.dirname(resolved), { recursive: true });
|
|
508
|
+
await fs3.writeFile(resolved, content, "utf-8");
|
|
509
|
+
}
|
|
510
|
+
async function directoryIsEmpty(targetDir, relativePath) {
|
|
511
|
+
const resolved = safeResolve(targetDir, relativePath);
|
|
512
|
+
if (!existsSync(resolved)) {
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
const files = await fs3.readdir(resolved);
|
|
516
|
+
const nonHiddenFiles = files.filter((f) => !f.startsWith("."));
|
|
517
|
+
return nonHiddenFiles.length === 0;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// src/generate/dependencies.ts
|
|
521
|
+
var dependencyVersions = {
|
|
522
|
+
// Base
|
|
523
|
+
next: "^15.1.0",
|
|
524
|
+
react: "^19.0.0",
|
|
525
|
+
"react-dom": "^19.0.0",
|
|
526
|
+
typescript: "^5.7.2",
|
|
527
|
+
"@types/react": "^19.0.0",
|
|
528
|
+
"@types/react-dom": "^19.0.0",
|
|
529
|
+
"@types/node": "^20.11.30",
|
|
530
|
+
eslint: "^9.16.0",
|
|
531
|
+
"eslint-config-next": "^15.1.0",
|
|
532
|
+
// Styling
|
|
533
|
+
tailwindcss: "^3.4.15",
|
|
534
|
+
postcss: "^8.4.49",
|
|
535
|
+
autoprefixer: "^10.4.20",
|
|
536
|
+
"styled-components": "^6.1.13",
|
|
537
|
+
// Auth
|
|
538
|
+
"@clerk/nextjs": "^6.9.6",
|
|
539
|
+
"next-auth": "^5.0.0-beta.25",
|
|
540
|
+
"@auth0/nextjs-auth0": "^3.5.0",
|
|
541
|
+
// ORM
|
|
542
|
+
prisma: "^6.1.0",
|
|
543
|
+
"@prisma/client": "^6.1.0",
|
|
544
|
+
"drizzle-orm": "^0.38.2",
|
|
545
|
+
"drizzle-kit": "^0.30.1",
|
|
546
|
+
pg: "^8.13.1",
|
|
547
|
+
"@types/pg": "^8.11.10",
|
|
548
|
+
mysql2: "^3.11.5",
|
|
549
|
+
"better-sqlite3": "^11.7.0",
|
|
550
|
+
"@types/better-sqlite3": "^11.6.0",
|
|
551
|
+
// State Management
|
|
552
|
+
zustand: "^5.0.2",
|
|
553
|
+
"@reduxjs/toolkit": "^2.5.0",
|
|
554
|
+
"react-redux": "^9.2.0",
|
|
555
|
+
jotai: "^2.11.0",
|
|
556
|
+
// Testing
|
|
557
|
+
vitest: "^2.1.8",
|
|
558
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
559
|
+
jsdom: "^25.0.1",
|
|
560
|
+
jest: "^29.7.0",
|
|
561
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
562
|
+
"@testing-library/react": "^16.1.0",
|
|
563
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
564
|
+
"ts-jest": "^29.2.5",
|
|
565
|
+
"ts-node": "^10.9.2",
|
|
566
|
+
"@types/jest": "^29.5.14",
|
|
567
|
+
"@playwright/test": "^1.49.1",
|
|
568
|
+
cypress: "^13.16.1",
|
|
569
|
+
// Monorepo
|
|
570
|
+
turbo: "^2.3.3"
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// src/generate/base/index.ts
|
|
574
|
+
async function generateBaseProject(context) {
|
|
575
|
+
const { config } = context;
|
|
576
|
+
const ext = config.typescript ? "tsx" : "jsx";
|
|
577
|
+
const jsExt = config.typescript ? "ts" : "js";
|
|
578
|
+
addDependency(context, "next", dependencyVersions["next"]);
|
|
579
|
+
addDependency(context, "react", dependencyVersions["react"]);
|
|
580
|
+
addDependency(context, "react-dom", dependencyVersions["react-dom"]);
|
|
581
|
+
addDevDependency(context, "eslint", dependencyVersions["eslint"]);
|
|
582
|
+
addDevDependency(context, "eslint-config-next", dependencyVersions["eslint-config-next"]);
|
|
583
|
+
if (config.typescript) {
|
|
584
|
+
addDevDependency(context, "typescript", dependencyVersions["typescript"]);
|
|
585
|
+
addDevDependency(context, "@types/react", dependencyVersions["@types/react"]);
|
|
586
|
+
addDevDependency(context, "@types/react-dom", dependencyVersions["@types/react-dom"]);
|
|
587
|
+
addDevDependency(context, "@types/node", dependencyVersions["@types/node"]);
|
|
588
|
+
}
|
|
589
|
+
if (config.typescript) {
|
|
590
|
+
addAppFile(context, "next-env.d.ts", `/// <reference types="next" />
|
|
591
|
+
/// <reference types="next/image-types/global" />
|
|
592
|
+
|
|
593
|
+
// NOTE: This file should not be edited
|
|
594
|
+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information
|
|
595
|
+
`);
|
|
596
|
+
}
|
|
597
|
+
if (config.typescript) {
|
|
598
|
+
addAppFile(
|
|
599
|
+
context,
|
|
600
|
+
"tsconfig.json",
|
|
601
|
+
JSON.stringify(
|
|
602
|
+
{
|
|
603
|
+
compilerOptions: {
|
|
604
|
+
target: "ES2022",
|
|
605
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
606
|
+
allowJs: true,
|
|
607
|
+
skipLibCheck: true,
|
|
608
|
+
strict: true,
|
|
609
|
+
noEmit: true,
|
|
610
|
+
esModuleInterop: true,
|
|
611
|
+
module: "esnext",
|
|
612
|
+
moduleResolution: "bundler",
|
|
613
|
+
resolveJsonModule: true,
|
|
614
|
+
isolatedModules: true,
|
|
615
|
+
jsx: "preserve",
|
|
616
|
+
incremental: true,
|
|
617
|
+
plugins: [
|
|
618
|
+
{
|
|
619
|
+
name: "next"
|
|
620
|
+
}
|
|
621
|
+
],
|
|
622
|
+
paths: {
|
|
623
|
+
"@/*": ["./src/*"]
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
627
|
+
exclude: ["node_modules"]
|
|
628
|
+
},
|
|
629
|
+
null,
|
|
630
|
+
2
|
|
631
|
+
) + "\n"
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
const nextConfigOptions = [];
|
|
635
|
+
if (config.docker) {
|
|
636
|
+
nextConfigOptions.push(' output: "standalone",');
|
|
637
|
+
}
|
|
638
|
+
if (config.styling === "styled-components") {
|
|
639
|
+
nextConfigOptions.push(" compiler: {\n styledComponents: true,\n },");
|
|
640
|
+
}
|
|
641
|
+
const nextConfigContent = config.typescript ? `import type { NextConfig } from "next";
|
|
642
|
+
|
|
643
|
+
const nextConfig: NextConfig = {
|
|
644
|
+
${nextConfigOptions.join("\n")}
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
export default nextConfig;
|
|
648
|
+
` : `/** @type {import('next').NextConfig} */
|
|
649
|
+
const nextConfig = {
|
|
650
|
+
${nextConfigOptions.join("\n")}
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
export default nextConfig;
|
|
654
|
+
`;
|
|
655
|
+
addAppFile(context, `next.config.${jsExt}`, nextConfigContent);
|
|
656
|
+
addAppFile(
|
|
657
|
+
context,
|
|
658
|
+
"eslint.config.mjs",
|
|
659
|
+
`import { dirname } from "path";
|
|
660
|
+
import { fileURLToPath } from "url";
|
|
661
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
662
|
+
|
|
663
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
664
|
+
const __dirname = dirname(__filename);
|
|
665
|
+
|
|
666
|
+
const compat = new FlatCompat({
|
|
667
|
+
baseDirectory: __dirname,
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
const eslintConfig = [
|
|
671
|
+
...compat.extends("next/core-web-vitals"),
|
|
672
|
+
${config.typescript ? '...compat.extends("next/typescript"),' : ""}
|
|
673
|
+
];
|
|
674
|
+
|
|
675
|
+
export default eslintConfig;
|
|
676
|
+
`
|
|
677
|
+
);
|
|
678
|
+
addProjectFile(
|
|
679
|
+
context,
|
|
680
|
+
".gitignore",
|
|
681
|
+
`# dependencies
|
|
682
|
+
/node_modules
|
|
683
|
+
/.pnpm-store
|
|
684
|
+
|
|
685
|
+
# testing
|
|
686
|
+
/coverage
|
|
687
|
+
/cypress/videos
|
|
688
|
+
/cypress/screenshots
|
|
689
|
+
/playwright-report
|
|
690
|
+
/test-results
|
|
691
|
+
|
|
692
|
+
# next.js
|
|
693
|
+
/.next/
|
|
694
|
+
/out/
|
|
695
|
+
|
|
696
|
+
# production
|
|
697
|
+
/build
|
|
698
|
+
|
|
699
|
+
# debug
|
|
700
|
+
npm-debug.log*
|
|
701
|
+
yarn-debug.log*
|
|
702
|
+
yarn-error.log*
|
|
703
|
+
pnpm-debug.log*
|
|
704
|
+
|
|
705
|
+
# local env files
|
|
706
|
+
.env*.local
|
|
707
|
+
.env
|
|
708
|
+
|
|
709
|
+
# vercel
|
|
710
|
+
.vercel
|
|
711
|
+
|
|
712
|
+
# typescript
|
|
713
|
+
*.tsbuildinfo
|
|
714
|
+
next-env.d.ts
|
|
715
|
+
`
|
|
716
|
+
);
|
|
717
|
+
const layoutImports = [];
|
|
718
|
+
let childrenWrapped = "{children}";
|
|
719
|
+
if (config.styling === "styled-components") {
|
|
720
|
+
layoutImports.push("import StyledComponentsRegistry from '@/lib/registry';");
|
|
721
|
+
childrenWrapped = `<StyledComponentsRegistry>${childrenWrapped}</StyledComponentsRegistry>`;
|
|
722
|
+
}
|
|
723
|
+
if (config.auth === "clerk") {
|
|
724
|
+
layoutImports.push("import { ClerkProvider } from '@clerk/nextjs';");
|
|
725
|
+
childrenWrapped = `<ClerkProvider>${childrenWrapped}</ClerkProvider>`;
|
|
726
|
+
} else if (config.auth === "auth0") {
|
|
727
|
+
layoutImports.push("import { UserProvider } from '@auth0/nextjs-auth0/client';");
|
|
728
|
+
childrenWrapped = `<UserProvider>${childrenWrapped}</UserProvider>`;
|
|
729
|
+
}
|
|
730
|
+
if (config.stateManagement === "redux") {
|
|
731
|
+
layoutImports.push("import StoreProvider from './StoreProvider';");
|
|
732
|
+
childrenWrapped = `<StoreProvider>${childrenWrapped}</StoreProvider>`;
|
|
733
|
+
} else if (config.stateManagement === "jotai") {
|
|
734
|
+
layoutImports.push("import JotaiProvider from './JotaiProvider';");
|
|
735
|
+
childrenWrapped = `<JotaiProvider>${childrenWrapped}</JotaiProvider>`;
|
|
736
|
+
}
|
|
737
|
+
const layoutContent = `${config.typescript ? 'import type { Metadata } from "next";\n' : ""}import "./globals.css";
|
|
738
|
+
${layoutImports.join("\n")}
|
|
739
|
+
|
|
740
|
+
export const metadata${config.typescript ? ": Metadata" : ""} = {
|
|
741
|
+
title: "${config.projectName}",
|
|
742
|
+
description: "Generated by create-enterprise-next",
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
export default function RootLayout({
|
|
746
|
+
children,
|
|
747
|
+
}: Readonly<{
|
|
748
|
+
children: React.ReactNode;
|
|
749
|
+
}>) {
|
|
750
|
+
return (
|
|
751
|
+
<html lang="en">
|
|
752
|
+
<body>
|
|
753
|
+
${childrenWrapped}
|
|
754
|
+
</body>
|
|
755
|
+
</html>
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
`;
|
|
759
|
+
addAppFile(context, `src/app/layout.${ext}`, layoutContent);
|
|
760
|
+
const testingLabel = config.testing.length > 0 ? config.testing.join(", ") : "None";
|
|
761
|
+
const modules = [
|
|
762
|
+
`<li><strong>Styling:</strong> ${config.styling}</li>`,
|
|
763
|
+
`<li><strong>Authentication:</strong> ${config.auth}</li>`,
|
|
764
|
+
`<li><strong>ORM:</strong> ${config.orm} (${config.database || "None"})</li>`,
|
|
765
|
+
`<li><strong>Testing:</strong> ${testingLabel}</li>`,
|
|
766
|
+
`<li><strong>State Management:</strong> ${config.stateManagement}</li>`,
|
|
767
|
+
`<li><strong>CI Setup:</strong> ${config.ci}</li>`,
|
|
768
|
+
`<li><strong>Docker Configuration:</strong> ${config.docker ? "Enabled" : "Disabled"}</li>`,
|
|
769
|
+
`<li><strong>Monorepo Structure:</strong> ${config.monorepo ? "Enabled" : "Disabled"}</li>`,
|
|
770
|
+
`<li><strong>Package Manager:</strong> ${config.packageManager}</li>`
|
|
771
|
+
].join("\n ");
|
|
772
|
+
let pageContent = "";
|
|
773
|
+
if (config.styling === "tailwind") {
|
|
774
|
+
pageContent = `export default function Page() {
|
|
775
|
+
return (
|
|
776
|
+
<main className="flex min-h-screen flex-col items-center justify-center bg-slate-950 text-white p-6">
|
|
777
|
+
<div className="max-w-2xl text-center space-y-6">
|
|
778
|
+
<h1 className="text-4xl font-extrabold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-emerald-400">
|
|
779
|
+
create-enterprise-next
|
|
780
|
+
</h1>
|
|
781
|
+
<p className="text-lg text-slate-400">
|
|
782
|
+
Your enterprise Next.js foundation is ready.
|
|
783
|
+
</p>
|
|
784
|
+
<div className="mt-8 p-6 bg-slate-900 rounded-xl border border-slate-800 text-left">
|
|
785
|
+
<h2 className="text-xl font-semibold mb-4 text-slate-200">Enabled Modules</h2>
|
|
786
|
+
<ul className="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm text-slate-400">
|
|
787
|
+
${modules}
|
|
788
|
+
</ul>
|
|
789
|
+
</div>
|
|
790
|
+
</div>
|
|
791
|
+
</main>
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
`;
|
|
795
|
+
} else if (config.styling === "styled-components") {
|
|
796
|
+
pageContent = `import styled from 'styled-components';
|
|
797
|
+
|
|
798
|
+
const Container = styled.main\`
|
|
799
|
+
display: flex;
|
|
800
|
+
flex-direction: column;
|
|
801
|
+
align-items: center;
|
|
802
|
+
justify-content: center;
|
|
803
|
+
min-height: 100vh;
|
|
804
|
+
background-color: #020617;
|
|
805
|
+
color: #ffffff;
|
|
806
|
+
padding: 24px;
|
|
807
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
808
|
+
\`;
|
|
809
|
+
|
|
810
|
+
const Title = styled.h1\`
|
|
811
|
+
font-size: 36px;
|
|
812
|
+
font-weight: 800;
|
|
813
|
+
margin-bottom: 12px;
|
|
814
|
+
background: linear-gradient(to right, #60a5fa, #34d399);
|
|
815
|
+
-webkit-background-clip: text;
|
|
816
|
+
-webkit-text-fill-color: transparent;
|
|
817
|
+
\`;
|
|
818
|
+
|
|
819
|
+
const Subtitle = styled.p\`
|
|
820
|
+
font-size: 18px;
|
|
821
|
+
color: #94a3b8;
|
|
822
|
+
margin-bottom: 32px;
|
|
823
|
+
\`;
|
|
824
|
+
|
|
825
|
+
const ModuleCard = styled.div\`
|
|
826
|
+
background-color: #0f172a;
|
|
827
|
+
border: 1px solid #1e293b;
|
|
828
|
+
border-radius: 12px;
|
|
829
|
+
padding: 24px;
|
|
830
|
+
max-width: 600px;
|
|
831
|
+
width: 100%;
|
|
832
|
+
text-align: left;
|
|
833
|
+
\`;
|
|
834
|
+
|
|
835
|
+
const CardTitle = styled.h2\`
|
|
836
|
+
font-size: 20px;
|
|
837
|
+
font-weight: 600;
|
|
838
|
+
margin-bottom: 16px;
|
|
839
|
+
color: #e2e8f0;
|
|
840
|
+
\`;
|
|
841
|
+
|
|
842
|
+
const ModuleList = styled.ul\`
|
|
843
|
+
display: grid;
|
|
844
|
+
grid-template-columns: 1fr;
|
|
845
|
+
gap: 12px;
|
|
846
|
+
list-style: none;
|
|
847
|
+
padding: 0;
|
|
848
|
+
margin: 0;
|
|
849
|
+
color: #94a3b8;
|
|
850
|
+
font-size: 14px;
|
|
851
|
+
|
|
852
|
+
@media (min-width: 768px) {
|
|
853
|
+
grid-template-columns: 1fr 1fr;
|
|
854
|
+
}
|
|
855
|
+
\`;
|
|
856
|
+
|
|
857
|
+
export default function Page() {
|
|
858
|
+
return (
|
|
859
|
+
<Container>
|
|
860
|
+
<Title>create-enterprise-next</Title>
|
|
861
|
+
<Subtitle>Your enterprise Next.js foundation is ready.</Subtitle>
|
|
862
|
+
<ModuleCard>
|
|
863
|
+
<CardTitle>Enabled Modules</CardTitle>
|
|
864
|
+
<ModuleList>
|
|
865
|
+
${modules}
|
|
866
|
+
</ModuleList>
|
|
867
|
+
</ModuleCard>
|
|
868
|
+
</Container>
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
`;
|
|
872
|
+
} else {
|
|
873
|
+
pageContent = `import styles from './page.module.css';
|
|
874
|
+
|
|
875
|
+
export default function Page() {
|
|
876
|
+
return (
|
|
877
|
+
<main className={styles.main}>
|
|
878
|
+
<div className={styles.container}>
|
|
879
|
+
<h1 className={styles.title}>create-enterprise-next</h1>
|
|
880
|
+
<p className={styles.subtitle}>Your enterprise Next.js foundation is ready.</p>
|
|
881
|
+
<div className={styles.card}>
|
|
882
|
+
<h2 className={styles.cardTitle}>Enabled Modules</h2>
|
|
883
|
+
<ul className={styles.list}>
|
|
884
|
+
${modules}
|
|
885
|
+
</ul>
|
|
886
|
+
</div>
|
|
887
|
+
</div>
|
|
888
|
+
</main>
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
`;
|
|
892
|
+
}
|
|
893
|
+
addAppFile(context, `src/app/page.${ext}`, pageContent);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// src/generate/base/readme.ts
|
|
897
|
+
function generateReadme(context) {
|
|
898
|
+
const { config } = context;
|
|
899
|
+
const pm = config.packageManager;
|
|
900
|
+
const pmRun = pm === "yarn" ? "yarn" : `${pm} run`;
|
|
901
|
+
const pmInstall = pm === "yarn" ? "yarn" : `${pm} install`;
|
|
902
|
+
let content = `# ${config.projectName}
|
|
903
|
+
|
|
904
|
+
`;
|
|
905
|
+
content += `This is an enterprise-grade Next.js application scaffolded with \`create-enterprise-next\`.
|
|
906
|
+
|
|
907
|
+
`;
|
|
908
|
+
content += `## Tech Stack
|
|
909
|
+
|
|
910
|
+
`;
|
|
911
|
+
content += `- **Framework:** Next.js (App Router)
|
|
912
|
+
`;
|
|
913
|
+
content += `- **Language:** ${config.typescript ? "TypeScript" : "JavaScript"}
|
|
914
|
+
`;
|
|
915
|
+
content += `- **Styling:** ${config.styling}
|
|
916
|
+
`;
|
|
917
|
+
content += `- **Authentication:** ${config.auth}
|
|
918
|
+
`;
|
|
919
|
+
content += `- **ORM:** ${config.orm}${config.database ? ` (${config.database})` : ""}
|
|
920
|
+
`;
|
|
921
|
+
content += `- **State Management:** ${config.stateManagement}
|
|
922
|
+
`;
|
|
923
|
+
content += `- **Testing:** ${config.testing.join(", ") || "None"}
|
|
924
|
+
`;
|
|
925
|
+
content += `- **CI Pipeline:** ${config.ci}
|
|
926
|
+
`;
|
|
927
|
+
content += `- **Docker:** ${config.docker ? "Yes" : "No"}
|
|
928
|
+
`;
|
|
929
|
+
content += `- **Monorepo:** ${config.monorepo ? "Yes" : "No"}
|
|
930
|
+
|
|
931
|
+
`;
|
|
932
|
+
content += `## Getting Started
|
|
933
|
+
|
|
934
|
+
`;
|
|
935
|
+
content += `### 1. Install Dependencies
|
|
936
|
+
|
|
937
|
+
`;
|
|
938
|
+
content += `\`\`\`bash
|
|
939
|
+
${pmInstall}
|
|
940
|
+
\`\`\`
|
|
941
|
+
|
|
942
|
+
`;
|
|
943
|
+
if (config.orm !== "none") {
|
|
944
|
+
content += `### 2. Database Setup
|
|
945
|
+
|
|
946
|
+
`;
|
|
947
|
+
content += `Ensure your database is running and the \`DATABASE_URL\` is defined in your \`.env\` file.
|
|
948
|
+
|
|
949
|
+
`;
|
|
950
|
+
content += `Generate ORM client:
|
|
951
|
+
`;
|
|
952
|
+
content += `\`\`\`bash
|
|
953
|
+
${pmRun} db:generate
|
|
954
|
+
\`\`\`
|
|
955
|
+
|
|
956
|
+
`;
|
|
957
|
+
content += `Push schema / run migrations:
|
|
958
|
+
`;
|
|
959
|
+
content += `\`\`\`bash
|
|
960
|
+
${pmRun} db:migrate
|
|
961
|
+
\`\`\`
|
|
962
|
+
|
|
963
|
+
`;
|
|
964
|
+
}
|
|
965
|
+
content += `### 3. Run Development Server
|
|
966
|
+
|
|
967
|
+
`;
|
|
968
|
+
content += `\`\`\`bash
|
|
969
|
+
${pm} dev
|
|
970
|
+
\`\`\`
|
|
971
|
+
|
|
972
|
+
`;
|
|
973
|
+
content += `Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
974
|
+
|
|
975
|
+
`;
|
|
976
|
+
if (config.testing.length > 0) {
|
|
977
|
+
content += `## Testing
|
|
978
|
+
|
|
979
|
+
`;
|
|
980
|
+
if (config.testing.includes("vitest")) {
|
|
981
|
+
content += `- **Vitest:** Run unit/integration tests: \`${pmRun} test:vitest\` (or \`${pmRun} test\`)
|
|
982
|
+
`;
|
|
983
|
+
}
|
|
984
|
+
if (config.testing.includes("jest")) {
|
|
985
|
+
content += `- **Jest:** Run unit/integration tests: \`${pmRun} test:jest\` (or \`${pmRun} test\`)
|
|
986
|
+
`;
|
|
987
|
+
}
|
|
988
|
+
if (config.testing.includes("playwright")) {
|
|
989
|
+
content += `- **Playwright:** Run end-to-end tests: \`${pmRun} test:e2e\`
|
|
990
|
+
`;
|
|
991
|
+
}
|
|
992
|
+
if (config.testing.includes("cypress")) {
|
|
993
|
+
content += `- **Cypress:** Run Cypress: \`${pmRun} test:cypress\` / \`${pmRun} cypress:open\`
|
|
994
|
+
`;
|
|
995
|
+
}
|
|
996
|
+
content += `
|
|
997
|
+
`;
|
|
998
|
+
}
|
|
999
|
+
if (config.docker) {
|
|
1000
|
+
content += `## Docker
|
|
1001
|
+
|
|
1002
|
+
`;
|
|
1003
|
+
content += `Build the production Docker image:
|
|
1004
|
+
`;
|
|
1005
|
+
content += `\`\`\`bash
|
|
1006
|
+
docker build -t ${config.projectName} .
|
|
1007
|
+
\`\`\`
|
|
1008
|
+
|
|
1009
|
+
`;
|
|
1010
|
+
content += `Run the container:
|
|
1011
|
+
`;
|
|
1012
|
+
content += `\`\`\`bash
|
|
1013
|
+
docker run -p 3000:3000 ${config.projectName}
|
|
1014
|
+
\`\`\`
|
|
1015
|
+
|
|
1016
|
+
`;
|
|
1017
|
+
}
|
|
1018
|
+
if (config.auth !== "none") {
|
|
1019
|
+
content += `## Authentication Setup
|
|
1020
|
+
|
|
1021
|
+
`;
|
|
1022
|
+
if (config.auth === "clerk") {
|
|
1023
|
+
content += `This project uses Clerk for authentication. Configure your credentials in \`.env.local\`:
|
|
1024
|
+
`;
|
|
1025
|
+
content += `- \`NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY\`
|
|
1026
|
+
`;
|
|
1027
|
+
content += `- \`CLERK_SECRET_KEY\`
|
|
1028
|
+
`;
|
|
1029
|
+
} else if (config.auth === "next-auth") {
|
|
1030
|
+
content += `This project uses NextAuth.js. Configure \`AUTH_SECRET\` in \`.env.local\`.
|
|
1031
|
+
`;
|
|
1032
|
+
} else if (config.auth === "auth0") {
|
|
1033
|
+
content += `This project uses Auth0. Configure \`AUTH0_SECRET\`, \`AUTH0_BASE_URL\`, and client variables in \`.env.local\`.
|
|
1034
|
+
`;
|
|
1035
|
+
}
|
|
1036
|
+
content += `
|
|
1037
|
+
`;
|
|
1038
|
+
}
|
|
1039
|
+
addAppFile(context, "README.md", content);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/generate/features/styling/index.ts
|
|
1043
|
+
async function generateStyling(context) {
|
|
1044
|
+
const { config } = context;
|
|
1045
|
+
const ext = config.typescript ? "tsx" : "jsx";
|
|
1046
|
+
if (config.styling === "tailwind") {
|
|
1047
|
+
addDevDependency(context, "tailwindcss", dependencyVersions["tailwindcss"]);
|
|
1048
|
+
addDevDependency(context, "postcss", dependencyVersions["postcss"]);
|
|
1049
|
+
addDevDependency(context, "autoprefixer", dependencyVersions["autoprefixer"]);
|
|
1050
|
+
addAppFile(
|
|
1051
|
+
context,
|
|
1052
|
+
"postcss.config.mjs",
|
|
1053
|
+
`const config = {
|
|
1054
|
+
plugins: {
|
|
1055
|
+
tailwindcss: {},
|
|
1056
|
+
autoprefixer: {},
|
|
1057
|
+
},
|
|
1058
|
+
};
|
|
1059
|
+
export default config;
|
|
1060
|
+
`
|
|
1061
|
+
);
|
|
1062
|
+
const tailwindConfigContent = config.typescript ? `import type { Config } from "tailwindcss";
|
|
1063
|
+
|
|
1064
|
+
const config: Config = {
|
|
1065
|
+
content: [
|
|
1066
|
+
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
|
1067
|
+
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
1068
|
+
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
1069
|
+
],
|
|
1070
|
+
theme: {
|
|
1071
|
+
extend: {},
|
|
1072
|
+
},
|
|
1073
|
+
plugins: [],
|
|
1074
|
+
};
|
|
1075
|
+
export default config;
|
|
1076
|
+
` : `/** @type {import('tailwindcss').Config} */
|
|
1077
|
+
const config = {
|
|
1078
|
+
content: [
|
|
1079
|
+
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
|
1080
|
+
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
1081
|
+
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
1082
|
+
],
|
|
1083
|
+
theme: {
|
|
1084
|
+
extend: {},
|
|
1085
|
+
},
|
|
1086
|
+
plugins: [],
|
|
1087
|
+
};
|
|
1088
|
+
export default config;
|
|
1089
|
+
`;
|
|
1090
|
+
addAppFile(context, `tailwind.config.${config.typescript ? "ts" : "js"}`, tailwindConfigContent);
|
|
1091
|
+
addAppFile(
|
|
1092
|
+
context,
|
|
1093
|
+
"src/app/globals.css",
|
|
1094
|
+
`@tailwind base;
|
|
1095
|
+
@tailwind components;
|
|
1096
|
+
@tailwind utilities;
|
|
1097
|
+
`
|
|
1098
|
+
);
|
|
1099
|
+
} else if (config.styling === "css-modules") {
|
|
1100
|
+
addAppFile(
|
|
1101
|
+
context,
|
|
1102
|
+
"src/app/globals.css",
|
|
1103
|
+
`body {
|
|
1104
|
+
margin: 0;
|
|
1105
|
+
padding: 0;
|
|
1106
|
+
background-color: #020617;
|
|
1107
|
+
color: #ffffff;
|
|
1108
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
1109
|
+
}
|
|
1110
|
+
`
|
|
1111
|
+
);
|
|
1112
|
+
addAppFile(
|
|
1113
|
+
context,
|
|
1114
|
+
"src/app/page.module.css",
|
|
1115
|
+
`.main {
|
|
1116
|
+
display: flex;
|
|
1117
|
+
flex-direction: column;
|
|
1118
|
+
align-items: center;
|
|
1119
|
+
justify-content: center;
|
|
1120
|
+
min-height: 100vh;
|
|
1121
|
+
background-color: #020617;
|
|
1122
|
+
color: #ffffff;
|
|
1123
|
+
padding: 24px;
|
|
1124
|
+
}
|
|
1125
|
+
.container {
|
|
1126
|
+
text-align: center;
|
|
1127
|
+
max-width: 640px;
|
|
1128
|
+
}
|
|
1129
|
+
.title {
|
|
1130
|
+
font-size: 36px;
|
|
1131
|
+
font-weight: 800;
|
|
1132
|
+
margin-bottom: 12px;
|
|
1133
|
+
background: linear-gradient(to right, #60a5fa, #34d399);
|
|
1134
|
+
-webkit-background-clip: text;
|
|
1135
|
+
-webkit-text-fill-color: transparent;
|
|
1136
|
+
}
|
|
1137
|
+
.subtitle {
|
|
1138
|
+
font-size: 18px;
|
|
1139
|
+
color: #94a3b8;
|
|
1140
|
+
margin-bottom: 32px;
|
|
1141
|
+
}
|
|
1142
|
+
.card {
|
|
1143
|
+
background-color: #0f172a;
|
|
1144
|
+
border: 1px solid #1e293b;
|
|
1145
|
+
border-radius: 12px;
|
|
1146
|
+
padding: 24px;
|
|
1147
|
+
text-align: left;
|
|
1148
|
+
}
|
|
1149
|
+
.cardTitle {
|
|
1150
|
+
font-size: 20px;
|
|
1151
|
+
font-weight: 600;
|
|
1152
|
+
margin-bottom: 16px;
|
|
1153
|
+
color: #e2e8f0;
|
|
1154
|
+
}
|
|
1155
|
+
.list {
|
|
1156
|
+
display: grid;
|
|
1157
|
+
grid-template-columns: 1fr;
|
|
1158
|
+
gap: 12px;
|
|
1159
|
+
list-style: none;
|
|
1160
|
+
padding: 0;
|
|
1161
|
+
margin: 0;
|
|
1162
|
+
color: #94a3b8;
|
|
1163
|
+
font-size: 14px;
|
|
1164
|
+
}
|
|
1165
|
+
@media (min-width: 768px) {
|
|
1166
|
+
.list {
|
|
1167
|
+
grid-template-columns: 1fr 1fr;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
`
|
|
1171
|
+
);
|
|
1172
|
+
} else if (config.styling === "styled-components") {
|
|
1173
|
+
addDependency(context, "styled-components", dependencyVersions["styled-components"]);
|
|
1174
|
+
addAppFile(
|
|
1175
|
+
context,
|
|
1176
|
+
"src/app/globals.css",
|
|
1177
|
+
`body {
|
|
1178
|
+
margin: 0;
|
|
1179
|
+
padding: 0;
|
|
1180
|
+
background-color: #020617;
|
|
1181
|
+
color: #ffffff;
|
|
1182
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
1183
|
+
}
|
|
1184
|
+
`
|
|
1185
|
+
);
|
|
1186
|
+
const registryContent = config.typescript ? `'use client';
|
|
1187
|
+
|
|
1188
|
+
import React, { useState } from 'react';
|
|
1189
|
+
import { useServerInsertedHTML } from 'next/navigation';
|
|
1190
|
+
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
|
|
1191
|
+
|
|
1192
|
+
export default function StyledComponentsRegistry({
|
|
1193
|
+
children,
|
|
1194
|
+
}: {
|
|
1195
|
+
children: React.ReactNode;
|
|
1196
|
+
}) {
|
|
1197
|
+
const [jsxStyleSheet] = useState(() => new ServerStyleSheet());
|
|
1198
|
+
|
|
1199
|
+
useServerInsertedHTML(() => {
|
|
1200
|
+
const styles = jsxStyleSheet.getStyleElement();
|
|
1201
|
+
jsxStyleSheet.instance.clearTag();
|
|
1202
|
+
return <>{styles}</>;
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
if (typeof window !== 'undefined') return <>{children}</>;
|
|
1206
|
+
|
|
1207
|
+
return (
|
|
1208
|
+
<StyleSheetManager sheet={jsxStyleSheet.instance}>
|
|
1209
|
+
{children}
|
|
1210
|
+
</StyleSheetManager>
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
` : `'use client';
|
|
1214
|
+
|
|
1215
|
+
import React, { useState } from 'react';
|
|
1216
|
+
import { useServerInsertedHTML } from 'next/navigation';
|
|
1217
|
+
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';
|
|
1218
|
+
|
|
1219
|
+
export default function StyledComponentsRegistry({ children }) {
|
|
1220
|
+
const [jsxStyleSheet] = useState(() => new ServerStyleSheet());
|
|
1221
|
+
|
|
1222
|
+
useServerInsertedHTML(() => {
|
|
1223
|
+
const styles = jsxStyleSheet.getStyleElement();
|
|
1224
|
+
jsxStyleSheet.instance.clearTag();
|
|
1225
|
+
return <>{styles}</>;
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
if (typeof window !== 'undefined') return <>{children}</>;
|
|
1229
|
+
|
|
1230
|
+
return (
|
|
1231
|
+
<StyleSheetManager sheet={jsxStyleSheet.instance}>
|
|
1232
|
+
{children}
|
|
1233
|
+
</StyleSheetManager>
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
`;
|
|
1237
|
+
addAppFile(context, `src/lib/registry.${ext}`, registryContent);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// src/generate/features/auth/index.ts
|
|
1242
|
+
async function generateAuthentication(context) {
|
|
1243
|
+
const { config } = context;
|
|
1244
|
+
const jsExt = config.typescript ? "ts" : "js";
|
|
1245
|
+
if (config.auth === "none") {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
if (config.auth === "clerk") {
|
|
1249
|
+
addDependency(context, "@clerk/nextjs", dependencyVersions["@clerk/nextjs"]);
|
|
1250
|
+
addEnv(context, "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY");
|
|
1251
|
+
addEnv(context, "CLERK_SECRET_KEY");
|
|
1252
|
+
addEnv(context, "NEXT_PUBLIC_CLERK_SIGN_IN_URL", "/sign-in");
|
|
1253
|
+
addEnv(context, "NEXT_PUBLIC_CLERK_SIGN_UP_URL", "/sign-up");
|
|
1254
|
+
const middlewareContent = `import { clerkMiddleware } from "@clerk/nextjs/server";
|
|
1255
|
+
|
|
1256
|
+
export default clerkMiddleware();
|
|
1257
|
+
|
|
1258
|
+
export const config = {
|
|
1259
|
+
matcher: [
|
|
1260
|
+
// Skip Next.js internals and all static files, unless found in search params
|
|
1261
|
+
'/((?!_next|[^?]*\\\\.[\\\\w]+$|_next/image|_next/static|favicon.ico).*)',
|
|
1262
|
+
// Always run for API routes
|
|
1263
|
+
'/(api|trpc)(.*)',
|
|
1264
|
+
],
|
|
1265
|
+
};
|
|
1266
|
+
`;
|
|
1267
|
+
addAppFile(context, `src/middleware.${jsExt}`, middlewareContent);
|
|
1268
|
+
} else if (config.auth === "next-auth") {
|
|
1269
|
+
addDependency(context, "next-auth", dependencyVersions["next-auth"]);
|
|
1270
|
+
addEnv(context, "AUTH_SECRET");
|
|
1271
|
+
addEnv(context, "AUTH_URL", "http://localhost:3000");
|
|
1272
|
+
const authConfigContent = `import NextAuth from "next-auth";
|
|
1273
|
+
|
|
1274
|
+
export const { handlers, auth, signIn, signOut } = NextAuth({
|
|
1275
|
+
providers: [],
|
|
1276
|
+
});
|
|
1277
|
+
`;
|
|
1278
|
+
addAppFile(context, `src/auth.${jsExt}`, authConfigContent);
|
|
1279
|
+
const routeContent = `import { handlers } from "@/auth";
|
|
1280
|
+
export const { GET, POST } = handlers;
|
|
1281
|
+
`;
|
|
1282
|
+
addAppFile(context, `src/app/api/auth/[...nextauth]/route.${jsExt}`, routeContent);
|
|
1283
|
+
const middlewareContent = `export { auth as middleware } from "@/auth";
|
|
1284
|
+
`;
|
|
1285
|
+
addAppFile(context, `src/middleware.${jsExt}`, middlewareContent);
|
|
1286
|
+
} else if (config.auth === "auth0") {
|
|
1287
|
+
addDependency(context, "@auth0/nextjs-auth0", dependencyVersions["@auth0/nextjs-auth0"]);
|
|
1288
|
+
addEnv(context, "AUTH0_SECRET");
|
|
1289
|
+
addEnv(context, "AUTH0_BASE_URL", "http://localhost:3000");
|
|
1290
|
+
addEnv(context, "AUTH0_ISSUER_BASE_URL");
|
|
1291
|
+
addEnv(context, "AUTH0_CLIENT_ID");
|
|
1292
|
+
addEnv(context, "AUTH0_CLIENT_SECRET");
|
|
1293
|
+
const routeContent = `import { handleAuth } from '@auth0/nextjs-auth0';
|
|
1294
|
+
export const GET = handleAuth();
|
|
1295
|
+
`;
|
|
1296
|
+
addAppFile(context, `src/app/api/auth/[auth0]/route.${jsExt}`, routeContent);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// src/generate/features/orm/index.ts
|
|
1301
|
+
async function generateOrm(context) {
|
|
1302
|
+
const { config } = context;
|
|
1303
|
+
const jsExt = config.typescript ? "ts" : "js";
|
|
1304
|
+
if (config.orm === "none") {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
if (config.orm === "prisma") {
|
|
1308
|
+
addDependency(context, "@prisma/client", dependencyVersions["@prisma/client"]);
|
|
1309
|
+
addDevDependency(context, "prisma", dependencyVersions["prisma"]);
|
|
1310
|
+
addScript(context, "db:generate", "prisma generate");
|
|
1311
|
+
addScript(context, "db:migrate", "prisma migrate dev");
|
|
1312
|
+
addScript(context, "db:studio", "prisma studio");
|
|
1313
|
+
let prismaProvider = "postgresql";
|
|
1314
|
+
let defaultDbUrl = "";
|
|
1315
|
+
if (config.database === "mysql") {
|
|
1316
|
+
prismaProvider = "mysql";
|
|
1317
|
+
} else if (config.database === "sqlite") {
|
|
1318
|
+
prismaProvider = "sqlite";
|
|
1319
|
+
defaultDbUrl = "file:./dev.db";
|
|
1320
|
+
} else if (config.database === "mongo") {
|
|
1321
|
+
prismaProvider = "mongodb";
|
|
1322
|
+
}
|
|
1323
|
+
addEnv(context, "DATABASE_URL", defaultDbUrl);
|
|
1324
|
+
const schemaContent = `generator client {
|
|
1325
|
+
provider = "prisma-client-js"
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
datasource db {
|
|
1329
|
+
provider = "${prismaProvider}"
|
|
1330
|
+
url = env("DATABASE_URL")
|
|
1331
|
+
}
|
|
1332
|
+
`;
|
|
1333
|
+
addProjectFile(context, "prisma/schema.prisma", schemaContent);
|
|
1334
|
+
const clientContent = config.typescript ? `import { PrismaClient } from '@prisma/client';
|
|
1335
|
+
|
|
1336
|
+
const globalForPrisma = globalThis as unknown as {
|
|
1337
|
+
prisma: PrismaClient | undefined;
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1340
|
+
export const db = globalForPrisma.prisma ?? new PrismaClient();
|
|
1341
|
+
|
|
1342
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db;
|
|
1343
|
+
` : `import { PrismaClient } from '@prisma/client';
|
|
1344
|
+
|
|
1345
|
+
const globalForPrisma = globalThis;
|
|
1346
|
+
|
|
1347
|
+
export const db = globalForPrisma.prisma ?? new PrismaClient();
|
|
1348
|
+
|
|
1349
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db;
|
|
1350
|
+
`;
|
|
1351
|
+
addAppFile(context, `src/lib/db.${jsExt}`, clientContent);
|
|
1352
|
+
}
|
|
1353
|
+
if (config.orm === "drizzle") {
|
|
1354
|
+
addDependency(context, "drizzle-orm", dependencyVersions["drizzle-orm"]);
|
|
1355
|
+
addDevDependency(context, "drizzle-kit", dependencyVersions["drizzle-kit"]);
|
|
1356
|
+
addScript(context, "db:generate", "drizzle-kit generate");
|
|
1357
|
+
addScript(context, "db:migrate", "drizzle-kit migrate");
|
|
1358
|
+
addScript(context, "db:studio", "drizzle-kit studio");
|
|
1359
|
+
let drizzleDialect = "postgresql";
|
|
1360
|
+
let defaultDbUrl = "";
|
|
1361
|
+
if (config.database === "postgres") {
|
|
1362
|
+
drizzleDialect = "postgresql";
|
|
1363
|
+
addDependency(context, "pg", dependencyVersions["pg"]);
|
|
1364
|
+
if (config.typescript) {
|
|
1365
|
+
addDevDependency(context, "@types/pg", dependencyVersions["@types/pg"]);
|
|
1366
|
+
}
|
|
1367
|
+
} else if (config.database === "mysql") {
|
|
1368
|
+
drizzleDialect = "mysql";
|
|
1369
|
+
addDependency(context, "mysql2", dependencyVersions["mysql2"]);
|
|
1370
|
+
} else if (config.database === "sqlite") {
|
|
1371
|
+
drizzleDialect = "sqlite";
|
|
1372
|
+
addDependency(context, "better-sqlite3", dependencyVersions["better-sqlite3"]);
|
|
1373
|
+
if (config.typescript) {
|
|
1374
|
+
addDevDependency(context, "@types/better-sqlite3", dependencyVersions["@types/better-sqlite3"]);
|
|
1375
|
+
}
|
|
1376
|
+
defaultDbUrl = "sqlite.db";
|
|
1377
|
+
}
|
|
1378
|
+
addEnv(context, "DATABASE_URL", defaultDbUrl);
|
|
1379
|
+
const drizzleConfigContent = `import { defineConfig } from 'drizzle-kit';
|
|
1380
|
+
|
|
1381
|
+
export default defineConfig({
|
|
1382
|
+
schema: './src/db/schema.${jsExt}',
|
|
1383
|
+
out: './drizzle',
|
|
1384
|
+
dialect: '${drizzleDialect}',
|
|
1385
|
+
dbCredentials: {
|
|
1386
|
+
url: process.env.DATABASE_URL!,
|
|
1387
|
+
},
|
|
1388
|
+
});
|
|
1389
|
+
`;
|
|
1390
|
+
addAppFile(context, `drizzle.config.${jsExt}`, drizzleConfigContent);
|
|
1391
|
+
addAppFile(context, `src/db/schema.${jsExt}`, "// Define your database schema here\n");
|
|
1392
|
+
let clientContent = "";
|
|
1393
|
+
if (config.database === "postgres") {
|
|
1394
|
+
clientContent = `import { drizzle } from 'drizzle-orm/node-postgres';
|
|
1395
|
+
import pg from 'pg';
|
|
1396
|
+
import * as schema from './schema';
|
|
1397
|
+
|
|
1398
|
+
const pool = new pg.Pool({
|
|
1399
|
+
connectionString: process.env.DATABASE_URL,
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
export const db = drizzle(pool, { schema });
|
|
1403
|
+
`;
|
|
1404
|
+
} else if (config.database === "mysql") {
|
|
1405
|
+
clientContent = `import { drizzle } from 'drizzle-orm/mysql2';
|
|
1406
|
+
import mysql from 'mysql2/promise';
|
|
1407
|
+
import * as schema from './schema';
|
|
1408
|
+
|
|
1409
|
+
const pool = mysql.createPool({
|
|
1410
|
+
uri: process.env.DATABASE_URL,
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
export const db = drizzle(pool, { schema });
|
|
1414
|
+
`;
|
|
1415
|
+
} else if (config.database === "sqlite") {
|
|
1416
|
+
clientContent = `import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
1417
|
+
import Database from 'better-sqlite3';
|
|
1418
|
+
import * as schema from './schema';
|
|
1419
|
+
|
|
1420
|
+
const sqlite = new Database(process.env.DATABASE_URL || 'sqlite.db');
|
|
1421
|
+
export const db = drizzle(sqlite, { schema });
|
|
1422
|
+
`;
|
|
1423
|
+
}
|
|
1424
|
+
addAppFile(context, `src/db/index.${jsExt}`, clientContent);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/generate/features/state-management/index.ts
|
|
1429
|
+
async function generateStateManagement(context) {
|
|
1430
|
+
const { config } = context;
|
|
1431
|
+
const ext = config.typescript ? "tsx" : "jsx";
|
|
1432
|
+
const jsExt = config.typescript ? "ts" : "js";
|
|
1433
|
+
if (config.stateManagement === "none") {
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
if (config.stateManagement === "zustand") {
|
|
1437
|
+
addDependency(context, "zustand", dependencyVersions["zustand"]);
|
|
1438
|
+
const storeContent = config.typescript ? `import { create } from 'zustand';
|
|
1439
|
+
|
|
1440
|
+
interface CounterState {
|
|
1441
|
+
count: number;
|
|
1442
|
+
increment: () => void;
|
|
1443
|
+
decrement: () => void;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
export const useCounterStore = create<CounterState>((set) => ({
|
|
1447
|
+
count: 0,
|
|
1448
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
1449
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
1450
|
+
}));
|
|
1451
|
+
` : `import { create } from 'zustand';
|
|
1452
|
+
|
|
1453
|
+
export const useCounterStore = create((set) => ({
|
|
1454
|
+
count: 0,
|
|
1455
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
1456
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
1457
|
+
}));
|
|
1458
|
+
`;
|
|
1459
|
+
addAppFile(context, `src/stores/counter-store.${jsExt}`, storeContent);
|
|
1460
|
+
} else if (config.stateManagement === "redux") {
|
|
1461
|
+
addDependency(context, "@reduxjs/toolkit", dependencyVersions["@reduxjs/toolkit"]);
|
|
1462
|
+
addDependency(context, "react-redux", dependencyVersions["react-redux"]);
|
|
1463
|
+
const storeContent = config.typescript ? `import { configureStore } from '@reduxjs/toolkit';
|
|
1464
|
+
|
|
1465
|
+
export const makeStore = () => {
|
|
1466
|
+
return configureStore({
|
|
1467
|
+
reducer: {},
|
|
1468
|
+
});
|
|
1469
|
+
};
|
|
1470
|
+
|
|
1471
|
+
export type AppStore = ReturnType<typeof makeStore>;
|
|
1472
|
+
export type RootState = ReturnType<AppStore['getState']>;
|
|
1473
|
+
export type AppDispatch = AppStore['dispatch'];
|
|
1474
|
+
` : `import { configureStore } from '@reduxjs/toolkit';
|
|
1475
|
+
|
|
1476
|
+
export const makeStore = () => {
|
|
1477
|
+
return configureStore({
|
|
1478
|
+
reducer: {},
|
|
1479
|
+
});
|
|
1480
|
+
};
|
|
1481
|
+
`;
|
|
1482
|
+
addAppFile(context, `src/lib/store.${jsExt}`, storeContent);
|
|
1483
|
+
const providerContent = config.typescript ? `'use client';
|
|
1484
|
+
import { useRef } from 'react';
|
|
1485
|
+
import { Provider } from 'react-redux';
|
|
1486
|
+
import { makeStore, AppStore } from '../lib/store';
|
|
1487
|
+
|
|
1488
|
+
export default function StoreProvider({
|
|
1489
|
+
children,
|
|
1490
|
+
}: {
|
|
1491
|
+
children: React.ReactNode;
|
|
1492
|
+
}) {
|
|
1493
|
+
const storeRef = useRef<AppStore>(undefined);
|
|
1494
|
+
if (!storeRef.current) {
|
|
1495
|
+
storeRef.current = makeStore();
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
return <Provider store={storeRef.current}>{children}</Provider>;
|
|
1499
|
+
}
|
|
1500
|
+
` : `'use client';
|
|
1501
|
+
import { useRef } from 'react';
|
|
1502
|
+
import { Provider } from 'react-redux';
|
|
1503
|
+
import { makeStore } from '../lib/store';
|
|
1504
|
+
|
|
1505
|
+
export default function StoreProvider({ children }) {
|
|
1506
|
+
const storeRef = useRef();
|
|
1507
|
+
if (!storeRef.current) {
|
|
1508
|
+
storeRef.current = makeStore();
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
return <Provider store={storeRef.current}>{children}</Provider>;
|
|
1512
|
+
}
|
|
1513
|
+
`;
|
|
1514
|
+
addAppFile(context, `src/app/StoreProvider.${ext}`, providerContent);
|
|
1515
|
+
} else if (config.stateManagement === "jotai") {
|
|
1516
|
+
addDependency(context, "jotai", dependencyVersions["jotai"]);
|
|
1517
|
+
const providerContent = config.typescript ? `'use client';
|
|
1518
|
+
import { Provider } from 'jotai';
|
|
1519
|
+
|
|
1520
|
+
export default function JotaiProvider({
|
|
1521
|
+
children,
|
|
1522
|
+
}: {
|
|
1523
|
+
children: React.ReactNode;
|
|
1524
|
+
}) {
|
|
1525
|
+
return <Provider>{children}</Provider>;
|
|
1526
|
+
}
|
|
1527
|
+
` : `'use client';
|
|
1528
|
+
import { Provider } from 'jotai';
|
|
1529
|
+
|
|
1530
|
+
export default function JotaiProvider({ children }) {
|
|
1531
|
+
return <Provider>{children}</Provider>;
|
|
1532
|
+
}
|
|
1533
|
+
`;
|
|
1534
|
+
addAppFile(context, `src/app/JotaiProvider.${ext}`, providerContent);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// src/generate/features/testing/index.ts
|
|
1539
|
+
async function generateTesting(context) {
|
|
1540
|
+
const { config } = context;
|
|
1541
|
+
const ext = config.typescript ? "tsx" : "jsx";
|
|
1542
|
+
const jsExt = config.typescript ? "ts" : "js";
|
|
1543
|
+
const hasVitest = config.testing.includes("vitest");
|
|
1544
|
+
const hasJest = config.testing.includes("jest");
|
|
1545
|
+
const hasPlaywright = config.testing.includes("playwright");
|
|
1546
|
+
const hasCypress = config.testing.includes("cypress");
|
|
1547
|
+
if (config.testing.length === 0) {
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
const pmRun = config.packageManager === "yarn" ? "yarn" : `${config.packageManager} run`;
|
|
1551
|
+
if (hasVitest) {
|
|
1552
|
+
addDevDependency(context, "vitest", dependencyVersions["vitest"]);
|
|
1553
|
+
addDevDependency(context, "@vitejs/plugin-react", dependencyVersions["@vitejs/plugin-react"]);
|
|
1554
|
+
addDevDependency(context, "jsdom", dependencyVersions["jsdom"]);
|
|
1555
|
+
addDevDependency(context, "@testing-library/react", dependencyVersions["@testing-library/react"]);
|
|
1556
|
+
addDevDependency(context, "@testing-library/jest-dom", dependencyVersions["@testing-library/jest-dom"]);
|
|
1557
|
+
addScript(context, "test:vitest", "vitest run");
|
|
1558
|
+
const vitestConfig = `import { defineConfig } from 'vitest/config';
|
|
1559
|
+
import react from '@vitejs/plugin-react';
|
|
1560
|
+
import path from 'path';
|
|
1561
|
+
|
|
1562
|
+
export default defineConfig({
|
|
1563
|
+
plugins: [react()],
|
|
1564
|
+
test: {
|
|
1565
|
+
environment: 'jsdom',
|
|
1566
|
+
globals: true,
|
|
1567
|
+
setupFiles: ['./vitest.setup.${jsExt}'],
|
|
1568
|
+
},
|
|
1569
|
+
resolve: {
|
|
1570
|
+
alias: {
|
|
1571
|
+
'@': path.resolve(__dirname, './src'),
|
|
1572
|
+
},
|
|
1573
|
+
},
|
|
1574
|
+
});
|
|
1575
|
+
`;
|
|
1576
|
+
addAppFile(context, `vitest.config.${jsExt}`, vitestConfig);
|
|
1577
|
+
addAppFile(context, `vitest.setup.${jsExt}`, `import '@testing-library/jest-dom';
|
|
1578
|
+
`);
|
|
1579
|
+
}
|
|
1580
|
+
if (hasJest) {
|
|
1581
|
+
addDevDependency(context, "jest", dependencyVersions["jest"]);
|
|
1582
|
+
addDevDependency(context, "jest-environment-jsdom", dependencyVersions["jest-environment-jsdom"]);
|
|
1583
|
+
addDevDependency(context, "@testing-library/react", dependencyVersions["@testing-library/react"]);
|
|
1584
|
+
addDevDependency(context, "@testing-library/jest-dom", dependencyVersions["@testing-library/jest-dom"]);
|
|
1585
|
+
if (config.typescript) {
|
|
1586
|
+
addDevDependency(context, "ts-jest", dependencyVersions["ts-jest"]);
|
|
1587
|
+
addDevDependency(context, "ts-node", dependencyVersions["ts-node"]);
|
|
1588
|
+
addDevDependency(context, "@types/jest", dependencyVersions["@types/jest"]);
|
|
1589
|
+
}
|
|
1590
|
+
addScript(context, "test:jest", "jest");
|
|
1591
|
+
const jestConfig = config.typescript ? `import type { Config } from 'jest';
|
|
1592
|
+
|
|
1593
|
+
const config: Config = {
|
|
1594
|
+
preset: 'ts-jest',
|
|
1595
|
+
testEnvironment: 'jest-environment-jsdom',
|
|
1596
|
+
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
|
1597
|
+
moduleNameMapper: {
|
|
1598
|
+
'^@/(.*)$': '<rootDir>/src/$1',
|
|
1599
|
+
'\\\\.css$': 'identity-obj-proxy',
|
|
1600
|
+
},
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
export default config;
|
|
1604
|
+
` : `/** @type {import('jest').Config} */
|
|
1605
|
+
const config = {
|
|
1606
|
+
testEnvironment: 'jest-environment-jsdom',
|
|
1607
|
+
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
|
1608
|
+
moduleNameMapper: {
|
|
1609
|
+
'^@/(.*)$': '<rootDir>/src/$1',
|
|
1610
|
+
'\\\\.css$': 'identity-obj-proxy',
|
|
1611
|
+
},
|
|
1612
|
+
};
|
|
1613
|
+
|
|
1614
|
+
export default config;
|
|
1615
|
+
`;
|
|
1616
|
+
addAppFile(context, `jest.config.${jsExt}`, jestConfig);
|
|
1617
|
+
addAppFile(context, `jest.setup.${jsExt}`, `import '@testing-library/jest-dom';
|
|
1618
|
+
`);
|
|
1619
|
+
}
|
|
1620
|
+
if (hasVitest || hasJest) {
|
|
1621
|
+
const cleanSmokeTest = `import { render, screen } from '@testing-library/react';
|
|
1622
|
+
import Page from './page';
|
|
1623
|
+
|
|
1624
|
+
test('renders welcome page', () => {
|
|
1625
|
+
render(<Page />);
|
|
1626
|
+
const heading = screen.getByRole('heading', { level: 1 });
|
|
1627
|
+
expect(heading).toBeInTheDocument();
|
|
1628
|
+
});
|
|
1629
|
+
`;
|
|
1630
|
+
addAppFile(context, `src/app/page.test.${ext}`, cleanSmokeTest);
|
|
1631
|
+
}
|
|
1632
|
+
if (hasPlaywright) {
|
|
1633
|
+
addDevDependency(context, "@playwright/test", dependencyVersions["@playwright/test"]);
|
|
1634
|
+
addScript(context, "test:e2e", "playwright test");
|
|
1635
|
+
const devCommand = config.packageManager === "npm" ? "npm run dev" : `${config.packageManager} dev`;
|
|
1636
|
+
const playwrightConfig = `import { defineConfig, devices } from '@playwright/test';
|
|
1637
|
+
|
|
1638
|
+
export default defineConfig({
|
|
1639
|
+
testDir: './tests/e2e',
|
|
1640
|
+
fullyParallel: true,
|
|
1641
|
+
forbidOnly: !!process.env.CI,
|
|
1642
|
+
retries: process.env.CI ? 2 : 0,
|
|
1643
|
+
workers: process.env.CI ? 1 : undefined,
|
|
1644
|
+
reporter: 'html',
|
|
1645
|
+
use: {
|
|
1646
|
+
baseURL: 'http://localhost:3000',
|
|
1647
|
+
trace: 'on-first-retry',
|
|
1648
|
+
},
|
|
1649
|
+
projects: [
|
|
1650
|
+
{
|
|
1651
|
+
name: 'chromium',
|
|
1652
|
+
use: { ...devices['Desktop Chrome'] },
|
|
1653
|
+
},
|
|
1654
|
+
],
|
|
1655
|
+
webServer: {
|
|
1656
|
+
command: '${devCommand}',
|
|
1657
|
+
url: 'http://localhost:3000',
|
|
1658
|
+
reuseExistingServer: !process.env.CI,
|
|
1659
|
+
},
|
|
1660
|
+
});
|
|
1661
|
+
`;
|
|
1662
|
+
addAppFile(context, `playwright.config.${jsExt}`, playwrightConfig);
|
|
1663
|
+
const playwrightSmokeTest = `import { test, expect } from '@playwright/test';
|
|
1664
|
+
|
|
1665
|
+
test('should display home page', async ({ page }) => {
|
|
1666
|
+
await page.goto('/');
|
|
1667
|
+
await expect(page.locator('h1')).toContainText('create-enterprise-next');
|
|
1668
|
+
});
|
|
1669
|
+
`;
|
|
1670
|
+
addAppFile(context, `tests/e2e/smoke.spec.${jsExt}`, playwrightSmokeTest);
|
|
1671
|
+
}
|
|
1672
|
+
if (hasCypress) {
|
|
1673
|
+
addDevDependency(context, "cypress", dependencyVersions["cypress"]);
|
|
1674
|
+
addScript(context, "test:cypress", "cypress run");
|
|
1675
|
+
addScript(context, "cypress:open", "cypress open");
|
|
1676
|
+
const cypressConfig = `import { defineConfig } from 'cypress';
|
|
1677
|
+
|
|
1678
|
+
export default defineConfig({
|
|
1679
|
+
e2e: {
|
|
1680
|
+
baseUrl: 'http://localhost:3000',
|
|
1681
|
+
supportFile: false,
|
|
1682
|
+
},
|
|
1683
|
+
});
|
|
1684
|
+
`;
|
|
1685
|
+
addAppFile(context, `cypress.config.${jsExt}`, cypressConfig);
|
|
1686
|
+
const cypressSmokeTest = `describe('Smoke Test', () => {
|
|
1687
|
+
it('should display home page', () => {
|
|
1688
|
+
cy.visit('/');
|
|
1689
|
+
cy.get('h1').should('contain', 'create-enterprise-next');
|
|
1690
|
+
});
|
|
1691
|
+
});
|
|
1692
|
+
`;
|
|
1693
|
+
addAppFile(context, `cypress/e2e/smoke.cy.${jsExt}`, cypressSmokeTest);
|
|
1694
|
+
}
|
|
1695
|
+
if (hasVitest && hasJest) {
|
|
1696
|
+
addScript(context, "test", `${pmRun} test:vitest && ${pmRun} test:jest`);
|
|
1697
|
+
} else if (hasVitest) {
|
|
1698
|
+
addScript(context, "test", `${pmRun} test:vitest`);
|
|
1699
|
+
} else if (hasJest) {
|
|
1700
|
+
addScript(context, "test", `${pmRun} test:jest`);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// src/generate/features/ci/index.ts
|
|
1705
|
+
async function generateCi(context) {
|
|
1706
|
+
const { config } = context;
|
|
1707
|
+
if (config.ci === "none") {
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
const pm = config.packageManager;
|
|
1711
|
+
const hasTesting = config.testing.length > 0;
|
|
1712
|
+
let installCmd = "npm ci";
|
|
1713
|
+
let runPrefix = "npm run";
|
|
1714
|
+
let cacheName = "npm";
|
|
1715
|
+
if (pm === "pnpm") {
|
|
1716
|
+
installCmd = "pnpm install";
|
|
1717
|
+
runPrefix = "pnpm run";
|
|
1718
|
+
cacheName = "pnpm";
|
|
1719
|
+
} else if (pm === "yarn") {
|
|
1720
|
+
installCmd = "yarn install";
|
|
1721
|
+
runPrefix = "yarn";
|
|
1722
|
+
cacheName = "yarn";
|
|
1723
|
+
} else if (pm === "bun") {
|
|
1724
|
+
installCmd = "bun install";
|
|
1725
|
+
runPrefix = "bun run";
|
|
1726
|
+
cacheName = "bun";
|
|
1727
|
+
}
|
|
1728
|
+
if (config.ci === "github-actions") {
|
|
1729
|
+
let steps = ` - name: Checkout repository
|
|
1730
|
+
uses: actions/checkout@v4
|
|
1731
|
+
|
|
1732
|
+
- name: Setup Node.js
|
|
1733
|
+
uses: actions/setup-node@v4
|
|
1734
|
+
with:
|
|
1735
|
+
node-version: 20
|
|
1736
|
+
`;
|
|
1737
|
+
if (pm === "pnpm") {
|
|
1738
|
+
steps += `
|
|
1739
|
+
- name: Setup pnpm
|
|
1740
|
+
uses: pnpm/action-setup@v3
|
|
1741
|
+
with:
|
|
1742
|
+
version: 9
|
|
1743
|
+
`;
|
|
1744
|
+
} else if (pm === "bun") {
|
|
1745
|
+
steps += `
|
|
1746
|
+
- name: Setup Bun
|
|
1747
|
+
uses: oven-sh/setup-bun@v1
|
|
1748
|
+
with:
|
|
1749
|
+
bun-version: latest
|
|
1750
|
+
`;
|
|
1751
|
+
}
|
|
1752
|
+
if (pm === "npm" || pm === "yarn") {
|
|
1753
|
+
steps += ` cache: '${cacheName}'
|
|
1754
|
+
`;
|
|
1755
|
+
}
|
|
1756
|
+
steps += `
|
|
1757
|
+
- name: Install dependencies
|
|
1758
|
+
run: ${installCmd}
|
|
1759
|
+
|
|
1760
|
+
- name: Lint
|
|
1761
|
+
run: ${runPrefix} lint
|
|
1762
|
+
`;
|
|
1763
|
+
if (config.typescript) {
|
|
1764
|
+
steps += `
|
|
1765
|
+
- name: Typecheck
|
|
1766
|
+
run: ${runPrefix} typecheck
|
|
1767
|
+
`;
|
|
1768
|
+
}
|
|
1769
|
+
if (hasTesting) {
|
|
1770
|
+
steps += `
|
|
1771
|
+
- name: Run tests
|
|
1772
|
+
run: ${runPrefix} test
|
|
1773
|
+
`;
|
|
1774
|
+
}
|
|
1775
|
+
steps += `
|
|
1776
|
+
- name: Build
|
|
1777
|
+
run: ${runPrefix} build
|
|
1778
|
+
`;
|
|
1779
|
+
const workflowContent = `name: CI
|
|
1780
|
+
|
|
1781
|
+
on:
|
|
1782
|
+
push:
|
|
1783
|
+
branches: [ main, master ]
|
|
1784
|
+
pull_request:
|
|
1785
|
+
branches: [ main, master ]
|
|
1786
|
+
|
|
1787
|
+
jobs:
|
|
1788
|
+
validate:
|
|
1789
|
+
runs-on: ubuntu-latest
|
|
1790
|
+
|
|
1791
|
+
steps:
|
|
1792
|
+
${steps}
|
|
1793
|
+
`;
|
|
1794
|
+
addProjectFile(context, ".github/workflows/ci.yml", workflowContent);
|
|
1795
|
+
}
|
|
1796
|
+
if (config.ci === "gitlab-ci") {
|
|
1797
|
+
let beforeScript = "";
|
|
1798
|
+
let image = "node:20";
|
|
1799
|
+
if (pm === "pnpm") {
|
|
1800
|
+
beforeScript = ` - corepack enable pnpm
|
|
1801
|
+
- pnpm install
|
|
1802
|
+
`;
|
|
1803
|
+
} else if (pm === "bun") {
|
|
1804
|
+
image = "oven/bun:latest";
|
|
1805
|
+
beforeScript = ` - bun install
|
|
1806
|
+
`;
|
|
1807
|
+
} else if (pm === "yarn") {
|
|
1808
|
+
beforeScript = ` - yarn install
|
|
1809
|
+
`;
|
|
1810
|
+
} else {
|
|
1811
|
+
beforeScript = ` - npm ci
|
|
1812
|
+
`;
|
|
1813
|
+
}
|
|
1814
|
+
let scriptSteps = ` - ${runPrefix} lint
|
|
1815
|
+
`;
|
|
1816
|
+
if (config.typescript) {
|
|
1817
|
+
scriptSteps += ` - ${runPrefix} typecheck
|
|
1818
|
+
`;
|
|
1819
|
+
}
|
|
1820
|
+
if (hasTesting) {
|
|
1821
|
+
scriptSteps += ` - ${runPrefix} test
|
|
1822
|
+
`;
|
|
1823
|
+
}
|
|
1824
|
+
scriptSteps += ` - ${runPrefix} build`;
|
|
1825
|
+
const gitlabContent = `image: ${image}
|
|
1826
|
+
|
|
1827
|
+
cache:
|
|
1828
|
+
paths:
|
|
1829
|
+
- node_modules/
|
|
1830
|
+
${pm === "pnpm" ? "- .pnpm-store/" : ""}
|
|
1831
|
+
|
|
1832
|
+
stages:
|
|
1833
|
+
- validate
|
|
1834
|
+
|
|
1835
|
+
validate_job:
|
|
1836
|
+
stage: validate
|
|
1837
|
+
before_script:
|
|
1838
|
+
${beforeScript}
|
|
1839
|
+
script:
|
|
1840
|
+
${scriptSteps}
|
|
1841
|
+
`;
|
|
1842
|
+
addProjectFile(context, ".gitlab-ci.yml", gitlabContent);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// src/generate/features/docker/index.ts
|
|
1847
|
+
async function generateDocker(context) {
|
|
1848
|
+
const { config } = context;
|
|
1849
|
+
if (!config.docker) {
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
const dockerfileContent = `# Multi-stage production Dockerfile for Next.js standalone output
|
|
1853
|
+
FROM node:20-alpine AS base
|
|
1854
|
+
|
|
1855
|
+
# Stage 1: Install dependencies
|
|
1856
|
+
FROM base AS deps
|
|
1857
|
+
RUN apk add --no-cache libc6-compat
|
|
1858
|
+
WORKDIR /app
|
|
1859
|
+
|
|
1860
|
+
# Copy lockfiles and package definitions
|
|
1861
|
+
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* bun.lockb* ./
|
|
1862
|
+
|
|
1863
|
+
RUN \\
|
|
1864
|
+
if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \\
|
|
1865
|
+
elif [ -f package-lock.json ]; then npm ci; \\
|
|
1866
|
+
elif [ -f yarn.lock ]; then yarn --frozen-lockfile; \\
|
|
1867
|
+
elif [ -f bun.lockb ]; then bun install --frozen-lockfile; \\
|
|
1868
|
+
else echo "Warning: No lockfile found. Installing normal dependencies." && npm install; \\
|
|
1869
|
+
fi
|
|
1870
|
+
|
|
1871
|
+
# Stage 2: Build the application
|
|
1872
|
+
FROM base AS builder
|
|
1873
|
+
WORKDIR /app
|
|
1874
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
1875
|
+
COPY . .
|
|
1876
|
+
|
|
1877
|
+
# Disable Next.js telemetry during build
|
|
1878
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
1879
|
+
|
|
1880
|
+
RUN \\
|
|
1881
|
+
if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \\
|
|
1882
|
+
elif [ -f package-lock.json ]; then npm run build; \\
|
|
1883
|
+
elif [ -f yarn.lock ]; then yarn build; \\
|
|
1884
|
+
elif [ -f bun.lockb ]; then bun run build; \\
|
|
1885
|
+
else npm run build; \\
|
|
1886
|
+
fi
|
|
1887
|
+
|
|
1888
|
+
# Stage 3: Production runner
|
|
1889
|
+
FROM base AS runner
|
|
1890
|
+
WORKDIR /app
|
|
1891
|
+
|
|
1892
|
+
ENV NODE_ENV=production
|
|
1893
|
+
ENV NEXT_TELEMETRY_DISABLED=1
|
|
1894
|
+
|
|
1895
|
+
RUN addgroup --system --gid 1001 nodejs
|
|
1896
|
+
RUN adduser --system --uid 1001 nextjs
|
|
1897
|
+
|
|
1898
|
+
# Copy static assets and public directory
|
|
1899
|
+
COPY --from=builder /app/public ./public
|
|
1900
|
+
|
|
1901
|
+
# Setup prerender cache directory permissions
|
|
1902
|
+
RUN mkdir .next
|
|
1903
|
+
RUN chown nextjs:nodejs .next
|
|
1904
|
+
|
|
1905
|
+
# Leverage output traces to reduce image size (standalone output copy)
|
|
1906
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
1907
|
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
1908
|
+
|
|
1909
|
+
USER nextjs
|
|
1910
|
+
|
|
1911
|
+
EXPOSE 3000
|
|
1912
|
+
ENV PORT=3000
|
|
1913
|
+
ENV HOSTNAME="0.0.0.0"
|
|
1914
|
+
|
|
1915
|
+
CMD ["node", "server.js"]
|
|
1916
|
+
`;
|
|
1917
|
+
const dockerignoreContent = `node_modules
|
|
1918
|
+
.next
|
|
1919
|
+
out
|
|
1920
|
+
build
|
|
1921
|
+
.dockerignore
|
|
1922
|
+
Dockerfile
|
|
1923
|
+
.git
|
|
1924
|
+
.github
|
|
1925
|
+
.gitlab-ci.yml
|
|
1926
|
+
README.md
|
|
1927
|
+
`;
|
|
1928
|
+
addAppFile(context, "Dockerfile", dockerfileContent);
|
|
1929
|
+
addAppFile(context, ".dockerignore", dockerignoreContent);
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// src/generate/features/monorepo/index.ts
|
|
1933
|
+
async function generateMonorepo(context) {
|
|
1934
|
+
const { config } = context;
|
|
1935
|
+
if (!config.monorepo) {
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
addRootDevDependency(context, "turbo", dependencyVersions["turbo"]);
|
|
1939
|
+
const workspaceContent = `packages:
|
|
1940
|
+
- 'apps/*'
|
|
1941
|
+
- 'packages/*'
|
|
1942
|
+
`;
|
|
1943
|
+
addProjectFile(context, "pnpm-workspace.yaml", workspaceContent);
|
|
1944
|
+
const turboContent = {
|
|
1945
|
+
$schema: "https://turbo.build/schema.json",
|
|
1946
|
+
tasks: {
|
|
1947
|
+
build: {
|
|
1948
|
+
dependsOn: ["^build"],
|
|
1949
|
+
outputs: [".next/**", "!.next/cache/**"]
|
|
1950
|
+
},
|
|
1951
|
+
lint: {},
|
|
1952
|
+
typecheck: {}
|
|
1953
|
+
}
|
|
1954
|
+
};
|
|
1955
|
+
addProjectFile(context, "turbo.json", JSON.stringify(turboContent, null, 2) + "\n");
|
|
1956
|
+
const configPkg = {
|
|
1957
|
+
name: "@enterprise/config",
|
|
1958
|
+
version: "0.0.0",
|
|
1959
|
+
private: true
|
|
1960
|
+
};
|
|
1961
|
+
addProjectFile(context, "packages/config/package.json", JSON.stringify(configPkg, null, 2) + "\n");
|
|
1962
|
+
const typesPkg = {
|
|
1963
|
+
name: "@enterprise/types",
|
|
1964
|
+
version: "0.0.0",
|
|
1965
|
+
private: true
|
|
1966
|
+
};
|
|
1967
|
+
addProjectFile(context, "packages/types/package.json", JSON.stringify(typesPkg, null, 2) + "\n");
|
|
1968
|
+
const uiPkg = {
|
|
1969
|
+
name: "@enterprise/ui",
|
|
1970
|
+
version: "0.0.0",
|
|
1971
|
+
private: true
|
|
1972
|
+
};
|
|
1973
|
+
addProjectFile(context, "packages/ui/package.json", JSON.stringify(uiPkg, null, 2) + "\n");
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
// src/generate/package-json.ts
|
|
1977
|
+
function compilePackageJsons(context) {
|
|
1978
|
+
const { config, manifest } = context;
|
|
1979
|
+
const sortKeys = (obj) => {
|
|
1980
|
+
const sorted = {};
|
|
1981
|
+
for (const key of Object.keys(obj).sort()) {
|
|
1982
|
+
sorted[key] = obj[key];
|
|
1983
|
+
}
|
|
1984
|
+
return sorted;
|
|
1985
|
+
};
|
|
1986
|
+
const appScripts = {
|
|
1987
|
+
dev: "next dev",
|
|
1988
|
+
build: "next build",
|
|
1989
|
+
start: "next start",
|
|
1990
|
+
lint: "next lint",
|
|
1991
|
+
...manifest.scripts
|
|
1992
|
+
};
|
|
1993
|
+
if (config.typescript) {
|
|
1994
|
+
appScripts.typecheck = "tsc --noEmit";
|
|
1995
|
+
}
|
|
1996
|
+
const appPkg = {
|
|
1997
|
+
name: config.monorepo ? "web" : config.projectName,
|
|
1998
|
+
version: "0.1.0",
|
|
1999
|
+
private: true,
|
|
2000
|
+
type: "module",
|
|
2001
|
+
scripts: sortKeys(appScripts),
|
|
2002
|
+
dependencies: sortKeys(manifest.dependencies),
|
|
2003
|
+
devDependencies: sortKeys(manifest.devDependencies)
|
|
2004
|
+
};
|
|
2005
|
+
if (config.monorepo) {
|
|
2006
|
+
context.manifest.files.set(
|
|
2007
|
+
"apps/web/package.json",
|
|
2008
|
+
JSON.stringify(appPkg, null, 2) + "\n"
|
|
2009
|
+
);
|
|
2010
|
+
const rootScripts = {
|
|
2011
|
+
dev: "turbo dev",
|
|
2012
|
+
build: "turbo build",
|
|
2013
|
+
lint: "turbo lint",
|
|
2014
|
+
...manifest.rootScripts
|
|
2015
|
+
};
|
|
2016
|
+
if (config.typescript) {
|
|
2017
|
+
rootScripts.typecheck = "turbo typecheck";
|
|
2018
|
+
}
|
|
2019
|
+
const rootPkg = {
|
|
2020
|
+
name: config.projectName,
|
|
2021
|
+
version: "0.1.0",
|
|
2022
|
+
private: true,
|
|
2023
|
+
type: "module",
|
|
2024
|
+
workspaces: ["apps/*", "packages/*"],
|
|
2025
|
+
scripts: sortKeys(rootScripts),
|
|
2026
|
+
dependencies: sortKeys(manifest.rootDependencies),
|
|
2027
|
+
devDependencies: sortKeys(manifest.rootDevDependencies)
|
|
2028
|
+
};
|
|
2029
|
+
context.manifest.files.set(
|
|
2030
|
+
"package.json",
|
|
2031
|
+
JSON.stringify(rootPkg, null, 2) + "\n"
|
|
2032
|
+
);
|
|
2033
|
+
} else {
|
|
2034
|
+
context.manifest.files.set(
|
|
2035
|
+
"package.json",
|
|
2036
|
+
JSON.stringify(appPkg, null, 2) + "\n"
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// src/generate/summary.ts
|
|
430
2042
|
import pc2 from "picocolors";
|
|
2043
|
+
function printGenerationSummary(context) {
|
|
2044
|
+
const { config } = context;
|
|
2045
|
+
const getTestingLabel = () => {
|
|
2046
|
+
if (config.testing.length === 0) return "None";
|
|
2047
|
+
return config.testing.map((t) => {
|
|
2048
|
+
if (t === "vitest") return "Vitest";
|
|
2049
|
+
if (t === "jest") return "Jest";
|
|
2050
|
+
if (t === "playwright") return "Playwright";
|
|
2051
|
+
if (t === "cypress") return "Cypress";
|
|
2052
|
+
return t;
|
|
2053
|
+
}).join(", ");
|
|
2054
|
+
};
|
|
2055
|
+
const getStylingLabel = () => {
|
|
2056
|
+
if (config.styling === "tailwind") return "Tailwind CSS";
|
|
2057
|
+
if (config.styling === "css-modules") return "CSS Modules";
|
|
2058
|
+
if (config.styling === "styled-components") return "Styled Components";
|
|
2059
|
+
return config.styling;
|
|
2060
|
+
};
|
|
2061
|
+
const getAuthLabel = () => {
|
|
2062
|
+
if (config.auth === "none") return "None";
|
|
2063
|
+
if (config.auth === "clerk") return "Clerk";
|
|
2064
|
+
if (config.auth === "next-auth") return "NextAuth.js";
|
|
2065
|
+
if (config.auth === "auth0") return "Auth0";
|
|
2066
|
+
return config.auth;
|
|
2067
|
+
};
|
|
2068
|
+
const getOrmLabel = () => {
|
|
2069
|
+
if (config.orm === "none") return "None";
|
|
2070
|
+
if (config.orm === "prisma") return "Prisma";
|
|
2071
|
+
if (config.orm === "drizzle") return "Drizzle";
|
|
2072
|
+
return config.orm;
|
|
2073
|
+
};
|
|
2074
|
+
const getDbLabel = () => {
|
|
2075
|
+
if (!config.database) return "None";
|
|
2076
|
+
if (config.database === "postgres") return "PostgreSQL";
|
|
2077
|
+
if (config.database === "mysql") return "MySQL";
|
|
2078
|
+
if (config.database === "sqlite") return "SQLite";
|
|
2079
|
+
if (config.database === "mongo") return "MongoDB";
|
|
2080
|
+
return config.database;
|
|
2081
|
+
};
|
|
2082
|
+
const getCiLabel = () => {
|
|
2083
|
+
if (config.ci === "none") return "None";
|
|
2084
|
+
if (config.ci === "github-actions") return "GitHub Actions";
|
|
2085
|
+
if (config.ci === "gitlab-ci") return "GitLab CI";
|
|
2086
|
+
return config.ci;
|
|
2087
|
+
};
|
|
2088
|
+
const getStateLabel = () => {
|
|
2089
|
+
if (config.stateManagement === "none") return "None";
|
|
2090
|
+
if (config.stateManagement === "zustand") return "Zustand";
|
|
2091
|
+
if (config.stateManagement === "redux") return "Redux Toolkit";
|
|
2092
|
+
if (config.stateManagement === "jotai") return "Jotai";
|
|
2093
|
+
return config.stateManagement;
|
|
2094
|
+
};
|
|
2095
|
+
console.log("\n" + pc2.green("\u2714 Project created successfully") + "\n");
|
|
2096
|
+
console.log(` ${pc2.bold("Project:")} ${config.projectName}`);
|
|
2097
|
+
console.log(` ${pc2.bold("Language:")} ${config.typescript ? "TypeScript" : "JavaScript"}`);
|
|
2098
|
+
console.log(` ${pc2.bold("Framework:")} Next.js (App Router)`);
|
|
2099
|
+
console.log(` ${pc2.bold("Styling:")} ${getStylingLabel()}`);
|
|
2100
|
+
console.log(` ${pc2.bold("Auth:")} ${getAuthLabel()}`);
|
|
2101
|
+
console.log(` ${pc2.bold("ORM:")} ${getOrmLabel()}`);
|
|
2102
|
+
if (config.orm !== "none") {
|
|
2103
|
+
console.log(` ${pc2.bold("Database:")} ${getDbLabel()}`);
|
|
2104
|
+
}
|
|
2105
|
+
console.log(` ${pc2.bold("State Mgmt:")} ${getStateLabel()}`);
|
|
2106
|
+
console.log(` ${pc2.bold("Testing:")} ${getTestingLabel()}`);
|
|
2107
|
+
console.log(` ${pc2.bold("CI:")} ${getCiLabel()}`);
|
|
2108
|
+
console.log(` ${pc2.bold("Docker:")} ${config.docker ? "Yes" : "No"}`);
|
|
2109
|
+
console.log(` ${pc2.bold("Monorepo:")} ${config.monorepo ? "Yes (Turborepo)" : "No"}`);
|
|
2110
|
+
console.log(` ${pc2.bold("Package Mgr:")} ${config.packageManager}`);
|
|
2111
|
+
console.log();
|
|
2112
|
+
console.log(pc2.cyan("Next steps:"));
|
|
2113
|
+
console.log();
|
|
2114
|
+
console.log(` cd ${config.projectName}`);
|
|
2115
|
+
const pm = config.packageManager;
|
|
2116
|
+
if (pm === "npm") {
|
|
2117
|
+
console.log(` npm install`);
|
|
2118
|
+
console.log(` npm run dev`);
|
|
2119
|
+
} else if (pm === "pnpm") {
|
|
2120
|
+
console.log(` pnpm install`);
|
|
2121
|
+
console.log(` pnpm dev`);
|
|
2122
|
+
} else if (pm === "yarn") {
|
|
2123
|
+
console.log(` yarn`);
|
|
2124
|
+
console.log(` yarn dev`);
|
|
2125
|
+
} else if (pm === "bun") {
|
|
2126
|
+
console.log(` bun install`);
|
|
2127
|
+
console.log(` bun dev`);
|
|
2128
|
+
}
|
|
2129
|
+
console.log();
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
// src/generate/generate-project.ts
|
|
2133
|
+
function compileEnvExample(context) {
|
|
2134
|
+
const keys = Object.keys(context.manifest.env);
|
|
2135
|
+
let content = "# Environment Variables\n\n";
|
|
2136
|
+
if (keys.length === 0) {
|
|
2137
|
+
content += "# No environment variables required for this setup.\n";
|
|
2138
|
+
} else {
|
|
2139
|
+
for (const key of keys) {
|
|
2140
|
+
content += `${key}=${context.manifest.env[key] || ""}
|
|
2141
|
+
`;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
addAppFile(context, ".env.example", content);
|
|
2145
|
+
}
|
|
2146
|
+
async function generateProject(config, cwd) {
|
|
2147
|
+
const context = createGenerationContext(config, cwd);
|
|
2148
|
+
const { targetDir, manifest } = context;
|
|
2149
|
+
const s = clack2.spinner();
|
|
2150
|
+
s.start("Initializing project structure");
|
|
2151
|
+
try {
|
|
2152
|
+
s.message("Configuring base Next.js templates");
|
|
2153
|
+
await generateBaseProject(context);
|
|
2154
|
+
s.message("Configuring styling");
|
|
2155
|
+
await generateStyling(context);
|
|
2156
|
+
s.message("Configuring authentication");
|
|
2157
|
+
await generateAuthentication(context);
|
|
2158
|
+
s.message("Configuring database & ORM");
|
|
2159
|
+
await generateOrm(context);
|
|
2160
|
+
s.message("Configuring state management");
|
|
2161
|
+
await generateStateManagement(context);
|
|
2162
|
+
s.message("Configuring testing suites");
|
|
2163
|
+
await generateTesting(context);
|
|
2164
|
+
s.message("Configuring CI workflows");
|
|
2165
|
+
await generateCi(context);
|
|
2166
|
+
s.message("Configuring Docker environment");
|
|
2167
|
+
await generateDocker(context);
|
|
2168
|
+
s.message("Configuring monorepo structures");
|
|
2169
|
+
await generateMonorepo(context);
|
|
2170
|
+
generateReadme(context);
|
|
2171
|
+
compileEnvExample(context);
|
|
2172
|
+
compilePackageJsons(context);
|
|
2173
|
+
s.message("Validating destination directory");
|
|
2174
|
+
const isTargetEmpty = await directoryIsEmpty(targetDir, ".");
|
|
2175
|
+
if (!isTargetEmpty) {
|
|
2176
|
+
throw new Error(`Destination directory "${targetDir}" is not empty.`);
|
|
2177
|
+
}
|
|
2178
|
+
s.message("Writing files to disk");
|
|
2179
|
+
await ensureDirectory(targetDir, ".");
|
|
2180
|
+
for (const [relPath, content] of manifest.files.entries()) {
|
|
2181
|
+
await writeFile(targetDir, relPath, content);
|
|
2182
|
+
}
|
|
2183
|
+
s.stop("Project configuration completed successfully");
|
|
2184
|
+
printGenerationSummary(context);
|
|
2185
|
+
} catch (error) {
|
|
2186
|
+
s.stop("Generation failed", 1);
|
|
2187
|
+
throw error;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
// src/utils/logger.ts
|
|
2192
|
+
import pc3 from "picocolors";
|
|
431
2193
|
var logger = {
|
|
432
|
-
info: (msg) => console.log(
|
|
433
|
-
success: (msg) => console.log(
|
|
434
|
-
warn: (msg) => console.warn(
|
|
435
|
-
error: (msg) => console.error(
|
|
2194
|
+
info: (msg) => console.log(pc3.blue("\u2139 ") + msg),
|
|
2195
|
+
success: (msg) => console.log(pc3.green("\u2714 ") + msg),
|
|
2196
|
+
warn: (msg) => console.warn(pc3.yellow("\u26A0 ") + msg),
|
|
2197
|
+
error: (msg) => console.error(pc3.red("\u2716 ") + msg),
|
|
436
2198
|
log: (msg) => console.log(msg)
|
|
437
2199
|
};
|
|
438
2200
|
|
|
@@ -440,10 +2202,10 @@ var logger = {
|
|
|
440
2202
|
function getPackageVersion() {
|
|
441
2203
|
try {
|
|
442
2204
|
const __filename = fileURLToPath(import.meta.url);
|
|
443
|
-
const __dirname =
|
|
444
|
-
const pkgPath =
|
|
445
|
-
if (
|
|
446
|
-
const pkg = JSON.parse(
|
|
2205
|
+
const __dirname = path4.dirname(__filename);
|
|
2206
|
+
const pkgPath = path4.resolve(__dirname, "../package.json");
|
|
2207
|
+
if (fs4.existsSync(pkgPath)) {
|
|
2208
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
|
|
447
2209
|
if (pkg.version) return pkg.version;
|
|
448
2210
|
}
|
|
449
2211
|
} catch {
|
|
@@ -479,8 +2241,7 @@ async function main() {
|
|
|
479
2241
|
console.log(JSON.stringify(config, null, 2));
|
|
480
2242
|
process.exit(0);
|
|
481
2243
|
}
|
|
482
|
-
|
|
483
|
-
logger.success("Config resolved \u2014 scaffolding logic not yet implemented");
|
|
2244
|
+
await generateProject(config);
|
|
484
2245
|
} catch (err) {
|
|
485
2246
|
logger.error(err instanceof Error ? err.message : String(err));
|
|
486
2247
|
process.exit(1);
|