|
5 | 5 | * Mirrors the FastAPI fcube.py module generator for frontend domains. |
6 | 6 | * |
7 | 7 | * Commands: |
| 8 | + * node ncube.js init [name] — post-clone setup (name, .env, shadcn) |
8 | 9 | * node ncube.js startdomain <DomainName> — scaffold a new feature domain |
9 | 10 | * node ncube.js listdomains — list existing domains in lib/ |
10 | 11 | * node ncube.js setup — install shadcn/ui components |
11 | 12 | * node ncube.js create <name> [--variant base|rbac|full] |
12 | | - * — bootstrap a new project from this template |
| 13 | + * — (deprecated) bootstrap from local template |
13 | 14 | */ |
14 | 15 |
|
15 | 16 | const fs = require("fs"); |
@@ -692,7 +693,98 @@ function cmdSetup() { |
692 | 693 | } |
693 | 694 | } |
694 | 695 |
|
| 696 | +function cmdInit(rawArgs) { |
| 697 | + // Determine app name |
| 698 | + // Priority: positional arg → --name flag → current directory name |
| 699 | + const nameFlag = rawArgs.indexOf("--name"); |
| 700 | + let appName; |
| 701 | + if (nameFlag !== -1 && rawArgs[nameFlag + 1]) { |
| 702 | + appName = toKebabCase(rawArgs[nameFlag + 1]); |
| 703 | + } else { |
| 704 | + const positional = rawArgs.find((a) => !a.startsWith("--")); |
| 705 | + appName = positional |
| 706 | + ? toKebabCase(positional) |
| 707 | + : path.basename(process.cwd()); |
| 708 | + } |
| 709 | + |
| 710 | + header(`Initializing project: ${appName}`); |
| 711 | + |
| 712 | + // 1. Update package.json name |
| 713 | + const pkgPath = path.join(process.cwd(), "package.json"); |
| 714 | + if (fs.existsSync(pkgPath)) { |
| 715 | + const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); |
| 716 | + const oldName = pkg.name; |
| 717 | + if (oldName !== appName) { |
| 718 | + pkg.name = appName; |
| 719 | + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n"); |
| 720 | + ok(`package.json name: ${c.dim}${oldName}${c.reset} → ${c.green}${appName}${c.reset}`); |
| 721 | + } else { |
| 722 | + ok(`package.json name: ${appName} (already set)`); |
| 723 | + } |
| 724 | + } |
| 725 | + |
| 726 | + // 2. Create .env from .env.example if not already present |
| 727 | + const envExamplePath = path.join(process.cwd(), ".env.example"); |
| 728 | + const envPath = path.join(process.cwd(), ".env"); |
| 729 | + if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) { |
| 730 | + let envContent = fs.readFileSync(envExamplePath, "utf8"); |
| 731 | + envContent = envContent.replace( |
| 732 | + /^NEXT_PUBLIC_APP_NAME=.*$/m, |
| 733 | + `NEXT_PUBLIC_APP_NAME="${appName}"`, |
| 734 | + ); |
| 735 | + fs.writeFileSync(envPath, envContent); |
| 736 | + ok("Created .env from .env.example"); |
| 737 | + } else if (fs.existsSync(envPath)) { |
| 738 | + info(".env already exists — skipping"); |
| 739 | + } else { |
| 740 | + warn(".env.example not found — skipping .env creation"); |
| 741 | + } |
| 742 | + |
| 743 | + // 3. Install shadcn components if src/components/ui/ is missing or empty |
| 744 | + const hasSrc = fs.existsSync(path.join(process.cwd(), "src")); |
| 745 | + const uiDir = hasSrc |
| 746 | + ? path.join(process.cwd(), "src", "components", "ui") |
| 747 | + : path.join(process.cwd(), "components", "ui"); |
| 748 | + const needsSetup = |
| 749 | + !fs.existsSync(uiDir) || fs.readdirSync(uiDir).length === 0; |
| 750 | + |
| 751 | + if (needsSetup) { |
| 752 | + cmdSetup(); |
| 753 | + } else { |
| 754 | + ok("shadcn/ui components already installed — skipping setup"); |
| 755 | + } |
| 756 | + |
| 757 | + console.log(""); |
| 758 | + header("You're ready!"); |
| 759 | + dim("Next steps:"); |
| 760 | + dim(" 1. Edit .env — set NEXT_PUBLIC_API_URL to your backend URL"); |
| 761 | + dim(" 2. npm run dev"); |
| 762 | + console.log(""); |
| 763 | + info("Add feature domains with: node ncube.js startdomain <Name>"); |
| 764 | + console.log(""); |
| 765 | +} |
| 766 | + |
695 | 767 | function cmdCreate(projectName, variant) { |
| 768 | + // ── Deprecation notice ──────────────────────────────────────────────────────── |
| 769 | + console.log(""); |
| 770 | + console.log(`${c.yellow}${c.bold}⚠ Deprecation notice${c.reset}`); |
| 771 | + console.log( |
| 772 | + `${c.dim} 'create' is no longer the recommended way to start a project.${c.reset}`, |
| 773 | + ); |
| 774 | + console.log( |
| 775 | + `${c.dim} Use GitHub's "Use this template" button instead:${c.reset}`, |
| 776 | + ); |
| 777 | + console.log( |
| 778 | + `${c.dim} 1. Click "Use this template" on GitHub → name your repo${c.reset}`, |
| 779 | + ); |
| 780 | + console.log( |
| 781 | + `${c.dim} 2. git clone <your-repo> && cd <your-repo>${c.reset}`, |
| 782 | + ); |
| 783 | + console.log(`${c.dim} 3. npm install && node ncube.js init${c.reset}`); |
| 784 | + console.log(`${c.dim} Continuing with local 'create' anyway…${c.reset}`); |
| 785 | + console.log(""); |
| 786 | + // ───────────────────────────────────────────────────────────────────────────── |
| 787 | + |
696 | 788 | if (!projectName) { |
697 | 789 | err("Usage: node ncube.js create <project-name> [--variant base|rbac|full]"); |
698 | 790 | process.exit(1); |
@@ -886,23 +978,27 @@ function main() { |
886 | 978 | ${c.bold}${c.blue}NCube CLI${c.reset} — Next.js scaffolding tool |
887 | 979 |
|
888 | 980 | ${c.bold}Commands:${c.reset} |
| 981 | + ${c.cyan}init${c.reset} [name] Post-clone setup: name, .env, shadcn |
889 | 982 | ${c.cyan}startdomain${c.reset} <DomainName> Scaffold a new feature domain |
890 | 983 | ${c.cyan}listdomains${c.reset} List existing domains |
891 | 984 | ${c.cyan}setup${c.reset} Install shadcn/ui components |
892 | | - ${c.cyan}create${c.reset} <name> [--variant base|rbac|full] Bootstrap a new project |
| 985 | + ${c.cyan}create${c.reset} <name> [--variant base|rbac|full] ${c.dim}(deprecated)${c.reset} Bootstrap locally |
893 | 986 | ${c.cyan}bump${c.reset} <patch|minor|major> Bump version + add changelog entry |
894 | 987 |
|
895 | 988 | ${c.bold}Examples:${c.reset} |
| 989 | + node ncube.js init my-saas |
896 | 990 | node ncube.js startdomain Product |
897 | 991 | node ncube.js listdomains |
898 | 992 | node ncube.js setup |
899 | | - node ncube.js create my-saas --variant rbac |
900 | 993 | node ncube.js bump minor |
901 | 994 | `); |
902 | 995 | return; |
903 | 996 | } |
904 | 997 |
|
905 | 998 | switch (command) { |
| 999 | + case "init": |
| 1000 | + cmdInit(rest); |
| 1001 | + break; |
906 | 1002 | case "startdomain": |
907 | 1003 | cmdStartDomain(rest[0]); |
908 | 1004 | break; |
|
0 commit comments