@meghpatel-007/next-starter 0.0.0-stage → 1.0.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/LICENSE +27 -0
- package/README.md +131 -2
- package/bin/index.js +8 -0
- package/package.json +44 -4
- package/src/cli.js +242 -0
- package/src/git.js +25 -0
- package/src/install.js +55 -0
- package/src/scaffold.js +110 -0
- package/src/utils.js +70 -0
- package/template/AGENTS.md +9 -0
- package/template/LICENSE +28 -0
- package/template/README.md +36 -0
- package/template/_gitignore +41 -0
- package/template/components.json +25 -0
- package/template/eslint.config.mjs +11 -0
- package/template/next-env.d.ts +7 -0
- package/template/next.config.ts +59 -0
- package/template/package.json +49 -0
- package/template/postcss.config.mjs +6 -0
- package/template/public/apps.png +0 -0
- package/template/public/customizer.png +0 -0
- package/template/public/dashboard-dark.png +0 -0
- package/template/public/dashboard-light.png +0 -0
- package/template/public/dashboard.png +0 -0
- package/template/public/favicon-dark.png +0 -0
- package/template/public/favicon.png +0 -0
- package/template/public/feature-1-dark.png +0 -0
- package/template/public/feature-1-light.png +0 -0
- package/template/public/feature-2-dark.png +0 -0
- package/template/public/feature-2-light.png +0 -0
- package/template/public/file.svg +1 -0
- package/template/public/globe.svg +1 -0
- package/template/public/hero-images-container.png +0 -0
- package/template/public/next.svg +1 -0
- package/template/public/vercel.svg +1 -0
- package/template/public/window.svg +1 -0
- package/template/src/app/(auth)/errors/forbidden/components/forbidden-error.tsx +36 -0
- package/template/src/app/(auth)/errors/forbidden/page.tsx +5 -0
- package/template/src/app/(auth)/errors/internal-server-error/components/internal-server-error.tsx +36 -0
- package/template/src/app/(auth)/errors/internal-server-error/page.tsx +5 -0
- package/template/src/app/(auth)/errors/not-found/components/not-found-error.tsx +36 -0
- package/template/src/app/(auth)/errors/not-found/page.tsx +5 -0
- package/template/src/app/(auth)/errors/unauthorized/components/unauthorized-error.tsx +36 -0
- package/template/src/app/(auth)/errors/unauthorized/page.tsx +5 -0
- package/template/src/app/(auth)/errors/under-maintenance/components/under-maintenance-error.tsx +33 -0
- package/template/src/app/(auth)/errors/under-maintenance/page.tsx +5 -0
- package/template/src/app/(auth)/forgot-password/components/forgot-password-form-1.tsx +61 -0
- package/template/src/app/(auth)/forgot-password/page.tsx +22 -0
- package/template/src/app/(auth)/forgot-password-2/components/forgot-password-form-2.tsx +38 -0
- package/template/src/app/(auth)/forgot-password-2/page.tsx +25 -0
- package/template/src/app/(auth)/forgot-password-3/components/forgot-password-form-3.tsx +66 -0
- package/template/src/app/(auth)/forgot-password-3/page.tsx +9 -0
- package/template/src/app/(auth)/layout.tsx +14 -0
- package/template/src/app/(auth)/sign-in/components/login-form-1.tsx +143 -0
- package/template/src/app/(auth)/sign-in/page.tsx +22 -0
- package/template/src/app/(auth)/sign-in-2/components/login-form-2.tsx +78 -0
- package/template/src/app/(auth)/sign-in-2/page.tsx +25 -0
- package/template/src/app/(auth)/sign-in-3/components/login-form-3.tsx +130 -0
- package/template/src/app/(auth)/sign-in-3/page.tsx +11 -0
- package/template/src/app/(auth)/sign-up/components/signup-form-1.tsx +245 -0
- package/template/src/app/(auth)/sign-up/page.tsx +22 -0
- package/template/src/app/(auth)/sign-up-2/components/signup-form-2.tsx +89 -0
- package/template/src/app/(auth)/sign-up-2/page.tsx +25 -0
- package/template/src/app/(auth)/sign-up-3/components/signup-form-3.tsx +152 -0
- package/template/src/app/(auth)/sign-up-3/page.tsx +9 -0
- package/template/src/app/(dashboard)/calendar/components/calendar-main.tsx +446 -0
- package/template/src/app/(dashboard)/calendar/components/calendar-sidebar.tsx +78 -0
- package/template/src/app/(dashboard)/calendar/components/calendar-unified.tsx +431 -0
- package/template/src/app/(dashboard)/calendar/components/calendar.tsx +90 -0
- package/template/src/app/(dashboard)/calendar/components/calendars.tsx +265 -0
- package/template/src/app/(dashboard)/calendar/components/date-picker.tsx +56 -0
- package/template/src/app/(dashboard)/calendar/components/event-form.tsx +468 -0
- package/template/src/app/(dashboard)/calendar/components/quick-actions.tsx +159 -0
- package/template/src/app/(dashboard)/calendar/data/calendars.json +37 -0
- package/template/src/app/(dashboard)/calendar/data/event-dates.json +30 -0
- package/template/src/app/(dashboard)/calendar/data/events.json +62 -0
- package/template/src/app/(dashboard)/calendar/data.ts +55 -0
- package/template/src/app/(dashboard)/calendar/page.tsx +10 -0
- package/template/src/app/(dashboard)/calendar/types.ts +20 -0
- package/template/src/app/(dashboard)/calendar/use-calendar.ts +93 -0
- package/template/src/app/(dashboard)/chat/components/chat-header.tsx +266 -0
- package/template/src/app/(dashboard)/chat/components/chat.tsx +212 -0
- package/template/src/app/(dashboard)/chat/components/conversation-list-new.tsx +248 -0
- package/template/src/app/(dashboard)/chat/components/conversation-list.tsx +242 -0
- package/template/src/app/(dashboard)/chat/components/message-input.tsx +235 -0
- package/template/src/app/(dashboard)/chat/components/message-list.tsx +325 -0
- package/template/src/app/(dashboard)/chat/data/conversations.json +92 -0
- package/template/src/app/(dashboard)/chat/data/messages.json +224 -0
- package/template/src/app/(dashboard)/chat/data/users.json +47 -0
- package/template/src/app/(dashboard)/chat/page.tsx +49 -0
- package/template/src/app/(dashboard)/chat/use-chat.ts +149 -0
- package/template/src/app/(dashboard)/dashboard/components/chart-area-interactive.tsx +304 -0
- package/template/src/app/(dashboard)/dashboard/components/data-table.tsx +1156 -0
- package/template/src/app/(dashboard)/dashboard/components/section-cards.tsx +102 -0
- package/template/src/app/(dashboard)/dashboard/data/data.json +614 -0
- package/template/src/app/(dashboard)/dashboard/data/focus-documents-data.json +47 -0
- package/template/src/app/(dashboard)/dashboard/data/key-personnel-data.json +47 -0
- package/template/src/app/(dashboard)/dashboard/data/past-performance-data.json +47 -0
- package/template/src/app/(dashboard)/dashboard/page.tsx +37 -0
- package/template/src/app/(dashboard)/dashboard/schemas/task-schema.ts +13 -0
- package/template/src/app/(dashboard)/dashboard-2/components/customer-insights.tsx +390 -0
- package/template/src/app/(dashboard)/dashboard-2/components/dashboard-header.tsx +103 -0
- package/template/src/app/(dashboard)/dashboard-2/components/metrics-overview.tsx +95 -0
- package/template/src/app/(dashboard)/dashboard-2/components/quick-actions.tsx +50 -0
- package/template/src/app/(dashboard)/dashboard-2/components/recent-transactions.tsx +164 -0
- package/template/src/app/(dashboard)/dashboard-2/components/revenue-breakdown.tsx +269 -0
- package/template/src/app/(dashboard)/dashboard-2/components/sales-chart.tsx +170 -0
- package/template/src/app/(dashboard)/dashboard-2/components/top-products.tsx +140 -0
- package/template/src/app/(dashboard)/dashboard-2/data/dashboard-data.json +39 -0
- package/template/src/app/(dashboard)/dashboard-2/page.tsx +49 -0
- package/template/src/app/(dashboard)/faqs/components/faq-list.tsx +148 -0
- package/template/src/app/(dashboard)/faqs/components/features-grid.tsx +46 -0
- package/template/src/app/(dashboard)/faqs/data/categories.json +10 -0
- package/template/src/app/(dashboard)/faqs/data/faqs.json +278 -0
- package/template/src/app/(dashboard)/faqs/data/features.ts +37 -0
- package/template/src/app/(dashboard)/faqs/page.tsx +16 -0
- package/template/src/app/(dashboard)/layout.tsx +83 -0
- package/template/src/app/(dashboard)/mail/components/account-switcher.tsx +69 -0
- package/template/src/app/(dashboard)/mail/components/mail-display.tsx +290 -0
- package/template/src/app/(dashboard)/mail/components/mail-list.tsx +94 -0
- package/template/src/app/(dashboard)/mail/components/mail.tsx +226 -0
- package/template/src/app/(dashboard)/mail/components/nav.tsx +83 -0
- package/template/src/app/(dashboard)/mail/data.tsx +300 -0
- package/template/src/app/(dashboard)/mail/index.tsx +6 -0
- package/template/src/app/(dashboard)/mail/page.tsx +18 -0
- package/template/src/app/(dashboard)/mail/use-mail.ts +20 -0
- package/template/src/app/(dashboard)/pricing/components/faq-section.tsx +79 -0
- package/template/src/app/(dashboard)/pricing/components/features-grid.tsx +51 -0
- package/template/src/app/(dashboard)/pricing/data/faqs.json +32 -0
- package/template/src/app/(dashboard)/pricing/data/features.ts +61 -0
- package/template/src/app/(dashboard)/pricing/page.tsx +24 -0
- package/template/src/app/(dashboard)/settings/account/page.tsx +276 -0
- package/template/src/app/(dashboard)/settings/appearance/page.tsx +313 -0
- package/template/src/app/(dashboard)/settings/billing/components/billing-history-card.tsx +53 -0
- package/template/src/app/(dashboard)/settings/billing/components/current-plan-card.tsx +82 -0
- package/template/src/app/(dashboard)/settings/billing/data/billing-history.json +23 -0
- package/template/src/app/(dashboard)/settings/billing/data/current-plan.json +12 -0
- package/template/src/app/(dashboard)/settings/billing/page.tsx +57 -0
- package/template/src/app/(dashboard)/settings/connections/page.tsx +366 -0
- package/template/src/app/(dashboard)/settings/notifications/page.tsx +845 -0
- package/template/src/app/(dashboard)/settings/user/page.tsx +415 -0
- package/template/src/app/(dashboard)/tasks/components/add-task-modal.tsx +291 -0
- package/template/src/app/(dashboard)/tasks/components/columns.tsx +159 -0
- package/template/src/app/(dashboard)/tasks/components/data-table-column-header.tsx +46 -0
- package/template/src/app/(dashboard)/tasks/components/data-table-faceted-filter.tsx +164 -0
- package/template/src/app/(dashboard)/tasks/components/data-table-pagination.tsx +107 -0
- package/template/src/app/(dashboard)/tasks/components/data-table-row-actions.tsx +72 -0
- package/template/src/app/(dashboard)/tasks/components/data-table-toolbar.tsx +189 -0
- package/template/src/app/(dashboard)/tasks/components/data-table-view-options.tsx +67 -0
- package/template/src/app/(dashboard)/tasks/components/data-table.tsx +122 -0
- package/template/src/app/(dashboard)/tasks/components/user-nav.tsx +70 -0
- package/template/src/app/(dashboard)/tasks/data/data.tsx +66 -0
- package/template/src/app/(dashboard)/tasks/data/schema.ts +13 -0
- package/template/src/app/(dashboard)/tasks/data/tasks.json +352 -0
- package/template/src/app/(dashboard)/tasks/page.tsx +204 -0
- package/template/src/app/(dashboard)/users/components/data-table.tsx +600 -0
- package/template/src/app/(dashboard)/users/components/stat-cards.tsx +91 -0
- package/template/src/app/(dashboard)/users/components/user-form-dialog.tsx +291 -0
- package/template/src/app/(dashboard)/users/data.json +182 -0
- package/template/src/app/(dashboard)/users/page.tsx +84 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +193 -0
- package/template/src/app/landing/components/about-section.tsx +125 -0
- package/template/src/app/landing/components/blog-section.tsx +86 -0
- package/template/src/app/landing/components/contact-section.tsx +299 -0
- package/template/src/app/landing/components/cta-section.tsx +121 -0
- package/template/src/app/landing/components/faq-section.tsx +125 -0
- package/template/src/app/landing/components/features-section.tsx +221 -0
- package/template/src/app/landing/components/footer.tsx +273 -0
- package/template/src/app/landing/components/hero-section.tsx +121 -0
- package/template/src/app/landing/components/landing-theme-customizer.tsx +563 -0
- package/template/src/app/landing/components/logo-carousel.tsx +130 -0
- package/template/src/app/landing/components/navbar.tsx +367 -0
- package/template/src/app/landing/components/pricing-section.tsx +214 -0
- package/template/src/app/landing/components/stats-section.tsx +71 -0
- package/template/src/app/landing/components/team-section.tsx +224 -0
- package/template/src/app/landing/components/testimonials-section.tsx +148 -0
- package/template/src/app/landing/landing-page-content.tsx +60 -0
- package/template/src/app/landing/page.tsx +6 -0
- package/template/src/app/layout.tsx +31 -0
- package/template/src/app/loading.tsx +10 -0
- package/template/src/app/not-found.tsx +20 -0
- package/template/src/app/page.tsx +32 -0
- package/template/src/assets/react.svg +1 -0
- package/template/src/components/app-sidebar.tsx +241 -0
- package/template/src/components/color-picker.tsx +91 -0
- package/template/src/components/command-search.tsx +251 -0
- package/template/src/components/dot-pattern.tsx +131 -0
- package/template/src/components/dynamic-imports.ts +28 -0
- package/template/src/components/icons/brand-icons.tsx +114 -0
- package/template/src/components/image-3d.tsx +90 -0
- package/template/src/components/landing/mega-menu.tsx +143 -0
- package/template/src/components/layouts/base-layout.tsx +109 -0
- package/template/src/components/logo.tsx +44 -0
- package/template/src/components/mode-toggle.tsx +76 -0
- package/template/src/components/nav-main.tsx +121 -0
- package/template/src/components/nav-secondary.tsx +43 -0
- package/template/src/components/nav-user.tsx +122 -0
- package/template/src/components/pricing-plans.tsx +181 -0
- package/template/src/components/sidebar-notification.tsx +59 -0
- package/template/src/components/site-footer.tsx +30 -0
- package/template/src/components/site-header.tsx +87 -0
- package/template/src/components/theme-customizer/circular-transition.css +115 -0
- package/template/src/components/theme-customizer/import-modal.tsx +128 -0
- package/template/src/components/theme-customizer/index.tsx +229 -0
- package/template/src/components/theme-customizer/layout-tab.tsx +274 -0
- package/template/src/components/theme-customizer/main.tsx +7 -0
- package/template/src/components/theme-customizer/theme-tab.tsx +403 -0
- package/template/src/components/theme-customizer.tsx +6 -0
- package/template/src/components/theme-provider.tsx +62 -0
- package/template/src/components/ui/accordion.tsx +77 -0
- package/template/src/components/ui/alert.tsx +75 -0
- package/template/src/components/ui/avatar.tsx +108 -0
- package/template/src/components/ui/badge.tsx +51 -0
- package/template/src/components/ui/breadcrumb.tsx +121 -0
- package/template/src/components/ui/button.tsx +57 -0
- package/template/src/components/ui/calendar.tsx +228 -0
- package/template/src/components/ui/card-decorator.tsx +20 -0
- package/template/src/components/ui/card.tsx +102 -0
- package/template/src/components/ui/chart.tsx +372 -0
- package/template/src/components/ui/checkbox.tsx +27 -0
- package/template/src/components/ui/collapsible.tsx +21 -0
- package/template/src/components/ui/command.tsx +193 -0
- package/template/src/components/ui/dialog.tsx +156 -0
- package/template/src/components/ui/drawer.tsx +224 -0
- package/template/src/components/ui/dropdown-menu.tsx +271 -0
- package/template/src/components/ui/field.tsx +238 -0
- package/template/src/components/ui/hover-card.tsx +50 -0
- package/template/src/components/ui/input-group.tsx +158 -0
- package/template/src/components/ui/input.tsx +19 -0
- package/template/src/components/ui/kbd.tsx +26 -0
- package/template/src/components/ui/label.tsx +19 -0
- package/template/src/components/ui/loading-spinner.tsx +31 -0
- package/template/src/components/ui/navigation-menu.tsx +170 -0
- package/template/src/components/ui/popover.tsx +89 -0
- package/template/src/components/ui/progress.tsx +82 -0
- package/template/src/components/ui/radio-group.tsx +37 -0
- package/template/src/components/ui/resizable.tsx +49 -0
- package/template/src/components/ui/scroll-area.tsx +54 -0
- package/template/src/components/ui/select.tsx +201 -0
- package/template/src/components/ui/separator.tsx +24 -0
- package/template/src/components/ui/sheet.tsx +134 -0
- package/template/src/components/ui/sidebar.tsx +726 -0
- package/template/src/components/ui/skeleton.tsx +13 -0
- package/template/src/components/ui/switch.tsx +31 -0
- package/template/src/components/ui/table.tsx +115 -0
- package/template/src/components/ui/tabs.tsx +81 -0
- package/template/src/components/ui/textarea.tsx +17 -0
- package/template/src/components/ui/toast.tsx +229 -0
- package/template/src/components/ui/toggle-group.tsx +89 -0
- package/template/src/components/ui/toggle.tsx +44 -0
- package/template/src/components/ui/tooltip.tsx +65 -0
- package/template/src/components/upgrade-to-pro-button.tsx +107 -0
- package/template/src/config/site.ts +8 -0
- package/template/src/config/theme-customizer-constants.ts +56 -0
- package/template/src/config/theme-data.ts +29 -0
- package/template/src/contexts/sidebar-config.ts +16 -0
- package/template/src/contexts/sidebar-context.tsx +33 -0
- package/template/src/contexts/theme-context.ts +16 -0
- package/template/src/hooks/use-circular-transition.ts +83 -0
- package/template/src/hooks/use-fullscreen.ts +43 -0
- package/template/src/hooks/use-mobile.ts +18 -0
- package/template/src/hooks/use-sidebar-config.ts +15 -0
- package/template/src/hooks/use-theme-manager.ts +218 -0
- package/template/src/hooks/use-theme.ts +11 -0
- package/template/src/lib/data-table.ts +54 -0
- package/template/src/lib/fonts.ts +8 -0
- package/template/src/lib/utils.ts +7 -0
- package/template/src/proxy.ts +38 -0
- package/template/src/types/theme-customizer.ts +45 -0
- package/template/src/types/theme.ts +58 -0
- package/template/src/utils/shadcn-ui-theme-presets.ts +741 -0
- package/template/src/utils/tweakcn-theme-presets.ts +3425 -0
- package/template/tsconfig.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ShadcnStore
|
|
4
|
+
Copyright (c) 2026 Megh Patel
|
|
5
|
+
|
|
6
|
+
The dashboard code included in this package's template/ directory is derived from
|
|
7
|
+
shadcn-dashboard-landing-template by ShadcnStore, published under the MIT License
|
|
8
|
+
(https://github.com/shadcnstore/shadcn-dashboard-landing-template). Megh Patel's
|
|
9
|
+
copyright covers the CLI scaffolding tool and modifications to the template.
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,132 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @meghpatel-007/next-starter
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A modern Next.js dashboard starter with shadcn/ui, TypeScript, and Tailwind CSS — scaffolded from one command.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @meghpatel-007/next-starter my-app
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Creates a complete, independently installable Next.js 16 project: app router, dashboard pages, auth page layouts, theme customizer, landing page, and a full shadcn/ui component set.
|
|
10
|
+
|
|
11
|
+
## Screenshots
|
|
12
|
+
|
|
13
|
+
Light and dark dashboards, plus landing-page previews (hosted in the repo):
|
|
14
|
+
|
|
15
|
+
- [Dark dashboard](https://github.com/MeghPatel-007/Next-starter/raw/main/template/public/dashboard.png)
|
|
16
|
+
- [Light dashboard](https://github.com/MeghPatel-007/Next-starter/raw/main/template/public/dashboard-light.png)
|
|
17
|
+
- [Dashboard 2](https://github.com/MeghPatel-007/Next-starter/raw/main/template/public/dashboard-dark.png)
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Node.js **20.12 or newer** (`node --version`)
|
|
22
|
+
- Optional: Git for repo initialization, and npm/pnpm/Bun for dependency installation
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# npm
|
|
28
|
+
npx @meghpatel-007/next-starter my-app
|
|
29
|
+
|
|
30
|
+
# pnpm
|
|
31
|
+
pnpm dlx @meghpatel-007/next-starter my-app
|
|
32
|
+
|
|
33
|
+
# Bun
|
|
34
|
+
bunx @meghpatel-007/next-starter my-app
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Interactive setup asks three questions when not passed as flags:
|
|
38
|
+
|
|
39
|
+
1. Whether to install dependencies (default: yes).
|
|
40
|
+
2. Which package manager to use — npm, pnpm, or Bun. Your choice is pre-selected when it matches the runner (`npx` → npm, `pnpm dlx` → pnpm, `bunx` → Bun).
|
|
41
|
+
3. Whether to initialize a Git repository (default: yes, `git init` only — no auto commit).
|
|
42
|
+
|
|
43
|
+
Non-interactive use:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx @meghpatel-007/next-starter my-app --no-install --no-git
|
|
47
|
+
npx @meghpatel-007/next-starter my-app --install --pm pnpm --git
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Full options:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
--pm <npm|pnpm|bun> install dependencies with this package manager
|
|
54
|
+
--install / --no-install install template dependencies
|
|
55
|
+
--git / --no-git initialize a Git repository
|
|
56
|
+
--version, -v
|
|
57
|
+
--help, -h
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Project names must be valid npm package names (lowercase, no spaces, no leading dot/underscore, scoped names like `@acme/my-app` are allowed). Invalid names are rejected with a reason; they are never silently rewritten.
|
|
61
|
+
|
|
62
|
+
## Destination rules
|
|
63
|
+
|
|
64
|
+
- The project is created inside your current working directory.
|
|
65
|
+
- An existing non-empty directory is **never** overwritten or deleted.
|
|
66
|
+
- Interrupted runs leave partial files behind; delete the folder yourself to retry.
|
|
67
|
+
|
|
68
|
+
## Generated project structure
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
my-app/
|
|
72
|
+
├── src/
|
|
73
|
+
│ ├── app/ # App Router: landing, (auth), (dashboard) route groups
|
|
74
|
+
│ ├── components/ # shadcn/ui components, theme customizer, app sidebar
|
|
75
|
+
│ ├── components/ui/ # shadcn primitives (button, chart, dialog, …)
|
|
76
|
+
│ ├── config/ # site branding (src/config/site.ts) + theme data
|
|
77
|
+
│ ├── contexts/ hooks/ lib/ types/ utils/
|
|
78
|
+
│ └── proxy.ts # middleware-style proxy (redirects /login, /register, /auth/*)
|
|
79
|
+
├── public/ # demo images and favicon
|
|
80
|
+
├── components.json # shadcn/ui configuration
|
|
81
|
+
├── next.config.ts · tsconfig.json · eslint.config.mjs · postcss.config.mjs
|
|
82
|
+
├── package.json # renamed to your project name; template deps intact
|
|
83
|
+
└── .gitignore
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Development commands (inside the generated project)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm run dev # or: pnpm dev / bun dev
|
|
90
|
+
npm run build # production build (Next.js 16 + Turbopack)
|
|
91
|
+
npm run start # serve the production build
|
|
92
|
+
npm run lint # ESLint
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## What's included
|
|
96
|
+
|
|
97
|
+
- **Dashboards**: two dashboard layouts with charts, data tables, and stat cards
|
|
98
|
+
- **Pages**: tasks, users, calendar, chat, mail, settings (account/appearance/billing/connections/notifications), pricing, FAQs
|
|
99
|
+
- **Auth pages**: three sign-in, sign-up, and forgot-password layouts plus error pages (forbidden, not-found, unauthorized, maintenance)
|
|
100
|
+
- **Landing page**: hero, features, pricing, testimonials, FAQ, contact, CTA
|
|
101
|
+
- **Theme system**: dark/light mode, sidebar themes, a theme customizer with preset palettes
|
|
102
|
+
|
|
103
|
+
All dashboard/chat/mail/user data behind these pages is **local JSON mock data** — no backend, no real authentication, and the sign-in/sign-up pages are layout demos only, not working integrations.
|
|
104
|
+
|
|
105
|
+
## Technology stack
|
|
106
|
+
|
|
107
|
+
- Next.js 16 (App Router, Turbopack), React 19.3
|
|
108
|
+
- Tailwind CSS 4 · shadcn/ui (base-nova style) · lucide-react icons
|
|
109
|
+
- TypeScript, ESLint, PostCSS
|
|
110
|
+
- @tanstack/react-table, recharts, react-hook-form + zod, @dnd-kit, zustand
|
|
111
|
+
|
|
112
|
+
## Customization
|
|
113
|
+
|
|
114
|
+
- Brand name, repo links and description: edit `src/config/site.ts` in the generated project.
|
|
115
|
+
- Colors/typography: `src/app/globals.css` and the theme presets in `src/utils/*-theme-presets.ts`.
|
|
116
|
+
- Sidebar items: `src/contexts/sidebar-config.ts`.
|
|
117
|
+
- Layouts and page content: `src/app/**` — plain React code, no hidden generators.
|
|
118
|
+
|
|
119
|
+
## License and attribution
|
|
120
|
+
|
|
121
|
+
MIT. The template derives from [shadcn-dashboard-landing-template](https://github.com/shadcnstore/shadcn-dashboard-landing-template) by ShadcnStore (MIT, © 2025); the CLI tooling and template modifications are © 2026 Megh Patel. The upstream [License.md](https://github.com/shadcnstore/shadcn-dashboard-landing-template/blob/main/License.md) is preserved in each generated project.
|
|
122
|
+
|
|
123
|
+
## Contributing
|
|
124
|
+
|
|
125
|
+
Issues and pull requests are welcome at the [repository](https://github.com/MeghPatel-007/Next-starter). For local development: `npm test` runs the package verification suite (CLI startup, name/destination validation, a full scaffold into a temp folder, tarball inspection, and a packed-tarball run).
|
|
126
|
+
|
|
127
|
+
## Known limitations
|
|
128
|
+
|
|
129
|
+
- Windows, macOS, and Linux are supported; the template itself is tested on Windows 11 with Node 24.
|
|
130
|
+
- Git initialization requires the `git` CLI on `PATH`.
|
|
131
|
+
- The bundled Bun/pnpm/npm lockfiles are deliberately absent — the first install in your project produces its own lockfile.
|
|
132
|
+
- Demo integrations (chat, mail, notifications, reports) run entirely on mock data.
|
package/bin/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meghpatel-007/next-starter",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A modern Next.js dashboard starter with shadcn/ui, TypeScript and Tailwind CSS, scaffolded straight into your project.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"nextjs",
|
|
7
|
+
"react",
|
|
8
|
+
"dashboard",
|
|
9
|
+
"shadcn",
|
|
10
|
+
"shadcn-ui",
|
|
11
|
+
"typescript",
|
|
12
|
+
"tailwindcss",
|
|
13
|
+
"starter-template",
|
|
14
|
+
"create-next-app"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/MeghPatel-007/Next-starter#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/MeghPatel-007/Next-starter/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/MeghPatel-007/Next-starter.git"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Megh Patel",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"bin": {
|
|
28
|
+
"next-starter": "bin/index.js"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20.12.0"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin/",
|
|
35
|
+
"src/",
|
|
36
|
+
"template/",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"test": "node scripts/verify-package.js"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@clack/prompts": "^1.8.1"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import * as clack from "@clack/prompts";
|
|
6
|
+
import { validateProjectName } from "./utils.js";
|
|
7
|
+
import {
|
|
8
|
+
assertFreshDestination,
|
|
9
|
+
applyProjectName,
|
|
10
|
+
copyTemplate,
|
|
11
|
+
restoreGitignore,
|
|
12
|
+
} from "./scaffold.js";
|
|
13
|
+
import {
|
|
14
|
+
PACKAGE_MANAGERS,
|
|
15
|
+
isPackageManagerAvailable,
|
|
16
|
+
installDependencies,
|
|
17
|
+
devCommand,
|
|
18
|
+
} from "./install.js";
|
|
19
|
+
import { initGit, gitInitInstructions } from "./git.js";
|
|
20
|
+
|
|
21
|
+
const PACKAGE_ROOT = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
22
|
+
|
|
23
|
+
async function getVersion() {
|
|
24
|
+
const pkg = JSON.parse(await fs.readFile(path.join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
25
|
+
return pkg.version;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const usage = `
|
|
29
|
+
Usage: npx @meghpatel-007/next-starter <project-name> [options]
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--pm <npm|pnpm|bun> Install dependencies with this package manager
|
|
33
|
+
--install / --no-install Install template dependencies (default: ask)
|
|
34
|
+
--git / --no-git Initialize a Git repository (default: ask)
|
|
35
|
+
--version, -v Print version
|
|
36
|
+
--help, -h Print this help
|
|
37
|
+
`.trim();
|
|
38
|
+
|
|
39
|
+
function parseArgs(argv) {
|
|
40
|
+
const args = { name: null, pm: null, install: null, git: null, help: false, version: false };
|
|
41
|
+
for (let i = 0; i < argv.length; i++) {
|
|
42
|
+
const arg = argv[i];
|
|
43
|
+
switch (arg) {
|
|
44
|
+
case "help":
|
|
45
|
+
case "--help":
|
|
46
|
+
case "-h":
|
|
47
|
+
args.help = true;
|
|
48
|
+
break;
|
|
49
|
+
case "version":
|
|
50
|
+
case "--version":
|
|
51
|
+
case "-v":
|
|
52
|
+
args.version = true;
|
|
53
|
+
break;
|
|
54
|
+
case "--pm": {
|
|
55
|
+
const value = argv[++i];
|
|
56
|
+
if (!value || !PACKAGE_MANAGERS.includes(value)) {
|
|
57
|
+
throw new Error(`--pm expects one of: ${PACKAGE_MANAGERS.join(", ")}`);
|
|
58
|
+
}
|
|
59
|
+
args.pm = value;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "--install":
|
|
63
|
+
args.install = true;
|
|
64
|
+
break;
|
|
65
|
+
case "--no-install":
|
|
66
|
+
args.install = false;
|
|
67
|
+
break;
|
|
68
|
+
case "--git":
|
|
69
|
+
args.git = true;
|
|
70
|
+
break;
|
|
71
|
+
case "--no-git":
|
|
72
|
+
args.git = false;
|
|
73
|
+
break;
|
|
74
|
+
default:
|
|
75
|
+
if (arg.startsWith("--")) throw new Error(`Unknown option: ${arg}\n\n${usage}`);
|
|
76
|
+
if (args.name !== null) throw new Error(`Unexpected argument: ${arg}\n\n${usage}`);
|
|
77
|
+
args.name = arg;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return args;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const PACKAGE_MANAGER_LABELS = { npm: "npm", pnpm: "pnpm", bun: "Bun" };
|
|
84
|
+
|
|
85
|
+
/** Detect which package manager invoked us (npx / pnpm dlx / bunx). */
|
|
86
|
+
function detectPackageManager() {
|
|
87
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
88
|
+
const agent = userAgent.split(" ")[0] ?? "";
|
|
89
|
+
const match = agent.match(/^(npm|pnpm|bun)\//);
|
|
90
|
+
return match ? match[1] : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function cancelled(clack, value) {
|
|
94
|
+
if (clack.isCancel(value)) {
|
|
95
|
+
clack.cancel("Cancelled.");
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function bail() {
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
106
|
+
let args;
|
|
107
|
+
try {
|
|
108
|
+
args = parseArgs(argv);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error(error.message);
|
|
111
|
+
bail();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (args.help) {
|
|
116
|
+
console.log(usage);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (args.version) {
|
|
120
|
+
console.log(await getVersion());
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
clack.intro("next-starter");
|
|
125
|
+
|
|
126
|
+
let name = args.name;
|
|
127
|
+
if (name !== null) {
|
|
128
|
+
const invalid = validateProjectName(name);
|
|
129
|
+
if (invalid) {
|
|
130
|
+
clack.cancel(invalid);
|
|
131
|
+
bail();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Without a TTY we cannot ask questions; tell the user which flags to pass instead.
|
|
137
|
+
const canAsk = Boolean(process.stdout.isTTY);
|
|
138
|
+
const needsAsk = args.install === null || args.git === null || (args.install && args.pm === null);
|
|
139
|
+
|
|
140
|
+
if (name === null) {
|
|
141
|
+
if (!canAsk) {
|
|
142
|
+
clack.cancel(`No project name given.\n\n${usage}`);
|
|
143
|
+
bail();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
name = await clack.text({
|
|
147
|
+
message: "What should the project be called?",
|
|
148
|
+
placeholder: "my-app",
|
|
149
|
+
validate: (value) => {
|
|
150
|
+
if (!value || value.trim().length === 0) return "Project name cannot be empty.";
|
|
151
|
+
const invalid = validateProjectName(value.trim());
|
|
152
|
+
return invalid ?? undefined;
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
if (cancelled(clack, name)) return;
|
|
156
|
+
name = name.trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (needsAsk && !canAsk) {
|
|
160
|
+
// Fall back to sensible defaults instead of failing: prefer the package
|
|
161
|
+
// manager whose agent launched us (npx -> npm, bunx -> bun, pnpm dlx -> pnpm).
|
|
162
|
+
if (install === true && pm === null) pm = detectPackageManager() ?? "npm";
|
|
163
|
+
if (install === null) install = true;
|
|
164
|
+
if (doGit === null) doGit = true;
|
|
165
|
+
clack.log.info("Non-interactive shell: installing with default answers.");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let install = args.install;
|
|
169
|
+
let pm = args.pm;
|
|
170
|
+
let doGit = args.git;
|
|
171
|
+
|
|
172
|
+
if (install === null) {
|
|
173
|
+
install = await clack.confirm({ message: "Install dependencies after scaffolding?", initialValue: true });
|
|
174
|
+
if (cancelled(clack, install)) return;
|
|
175
|
+
}
|
|
176
|
+
if (install && pm === null) {
|
|
177
|
+
const preferred = detectPackageManager();
|
|
178
|
+
pm = await clack.select({
|
|
179
|
+
message: "Which package manager should install dependencies?",
|
|
180
|
+
options: PACKAGE_MANAGERS.map((value) => ({
|
|
181
|
+
value,
|
|
182
|
+
label: PACKAGE_MANAGER_LABELS[value],
|
|
183
|
+
hint: value === preferred ? "detected" : undefined,
|
|
184
|
+
disabled: !isPackageManagerAvailable(value) ? "not installed" : undefined,
|
|
185
|
+
})),
|
|
186
|
+
});
|
|
187
|
+
if (cancelled(clack, pm)) return;
|
|
188
|
+
}
|
|
189
|
+
if (doGit === null) {
|
|
190
|
+
doGit = await clack.confirm({ message: "Initialize a Git repository?", initialValue: true });
|
|
191
|
+
if (cancelled(clack, doGit)) return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let dest;
|
|
195
|
+
try {
|
|
196
|
+
dest = await assertFreshDestination(process.cwd(), name);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
clack.cancel(error.message);
|
|
199
|
+
bail();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const scaffoldSpinner = clack.spinner();
|
|
204
|
+
try {
|
|
205
|
+
scaffoldSpinner.start(`Scaffolding "${name}" from template`);
|
|
206
|
+
await copyTemplate(dest);
|
|
207
|
+
await restoreGitignore(dest);
|
|
208
|
+
await applyProjectName(dest, name);
|
|
209
|
+
scaffoldSpinner.stop("Template copied.");
|
|
210
|
+
} catch (error) {
|
|
211
|
+
scaffoldSpinner.stop("Scaffolding failed.", 1);
|
|
212
|
+
clack.cancel(error.message ?? String(error));
|
|
213
|
+
bail();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (doGit) {
|
|
218
|
+
if (initGit(dest)) {
|
|
219
|
+
clack.log.step("Git repository initialized.");
|
|
220
|
+
} else {
|
|
221
|
+
clack.log.warn(gitInitInstructions());
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const nextSteps = [` cd ${name}`];
|
|
226
|
+
if (install) {
|
|
227
|
+
const result = await installDependencies(dest, pm);
|
|
228
|
+
if (!result.ok) {
|
|
229
|
+
clack.log.error(result.reason);
|
|
230
|
+
nextSteps.push(` ${pm} install`);
|
|
231
|
+
bail();
|
|
232
|
+
} else {
|
|
233
|
+
clack.log.step(`Dependencies installed with ${pm}.`);
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
clack.log.info("Dependencies were not installed. Run these when ready:");
|
|
237
|
+
nextSteps.push(` ${PACKAGE_MANAGERS.map((m) => `${m} install`).join(" or ")}`);
|
|
238
|
+
}
|
|
239
|
+
nextSteps.push(` ${devCommand(pm ?? detectPackageManager() ?? "npm")}`, "");
|
|
240
|
+
|
|
241
|
+
clack.outro(`Project "${name}" created successfully.\n${nextSteps.join("\n")}`);
|
|
242
|
+
}
|
package/src/git.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Run `git init` in the generated project. Argument arrays only - never shell
|
|
6
|
+
* string concatenation - so paths with spaces stay safe.
|
|
7
|
+
*/
|
|
8
|
+
export function initGit(dest) {
|
|
9
|
+
const result = spawnSync("git", ["init"], {
|
|
10
|
+
cwd: dest,
|
|
11
|
+
stdio: "inherit",
|
|
12
|
+
shell: false,
|
|
13
|
+
windowsHide: true,
|
|
14
|
+
});
|
|
15
|
+
if (result.error) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return result.status === 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function gitInitInstructions() {
|
|
22
|
+
return "Git was not initialized. You can do it later with:\n cd <project>\n git init";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default { initGit };
|
package/src/install.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { spawnSync, spawn } from "node:child_process";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
|
|
4
|
+
export const PACKAGE_MANAGERS = ["npm", "pnpm", "bun"];
|
|
5
|
+
|
|
6
|
+
export function isPackageManagerAvailable(pm) {
|
|
7
|
+
if (!PACKAGE_MANAGERS.includes(pm)) return false;
|
|
8
|
+
// pm comes from an internal whitelist, so this string is fixed; on Windows
|
|
9
|
+
// npm/pnpm are .cmd shims which Node refuses to spawn without a shell.
|
|
10
|
+
const result = spawnSync(`${pm} --version`, {
|
|
11
|
+
stdio: "ignore",
|
|
12
|
+
encoding: "utf8",
|
|
13
|
+
shell: true,
|
|
14
|
+
windowsHide: true,
|
|
15
|
+
});
|
|
16
|
+
return !result.error && result.status === 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Install template dependencies. Returns { ok } or { ok:false, reason }. */
|
|
20
|
+
export async function installDependencies(dest, pm) {
|
|
21
|
+
if (!PACKAGE_MANAGERS.includes(pm)) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
reason: `Unknown package manager "${pm}". Supported: ${PACKAGE_MANAGERS.join(", ")}.`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (!isPackageManagerAvailable(pm)) {
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
reason: `"${pm}" was not found on PATH. Install it, or run the install step manually.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return await new Promise((resolve) => {
|
|
34
|
+
const child = spawn(`${pm} install`, {
|
|
35
|
+
cwd: dest,
|
|
36
|
+
stdio: "inherit",
|
|
37
|
+
shell: true,
|
|
38
|
+
windowsHide: true,
|
|
39
|
+
});
|
|
40
|
+
child.on("close", (code) => {
|
|
41
|
+
if (code === 0) return resolve({ ok: true });
|
|
42
|
+
resolve({
|
|
43
|
+
ok: false,
|
|
44
|
+
reason: `"${pm} install" exited with code ${code}. The project files were kept; run "${pm} install" inside the project to retry.`,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
child.on("error", (error) => {
|
|
48
|
+
resolve({ ok: false, reason: error.message });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function devCommand(pm) {
|
|
54
|
+
return pm === "pnpm" ? "pnpm dev" : pm === "bun" ? "bun dev" : "npm run dev";
|
|
55
|
+
}
|
package/src/scaffold.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
|
|
6
|
+
const packageRoot = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
7
|
+
export const TEMPLATE_DIR = path.join(packageRoot, "template");
|
|
8
|
+
|
|
9
|
+
const EXCLUDED_FILE_NAMES = new Set([
|
|
10
|
+
"bun.lock",
|
|
11
|
+
"package-lock.json",
|
|
12
|
+
"pnpm-lock.yaml",
|
|
13
|
+
"yarn.lock",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const EXCLUDED_DIR_NAMES = new Set([
|
|
17
|
+
"node_modules",
|
|
18
|
+
".next",
|
|
19
|
+
".git",
|
|
20
|
+
".vercel",
|
|
21
|
+
"coverage",
|
|
22
|
+
".turbo",
|
|
23
|
+
".cache",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
function isExcludedFile(sourcePath) {
|
|
27
|
+
const base = path.basename(sourcePath);
|
|
28
|
+
if (base.toLowerCase().startsWith(".env")) return true;
|
|
29
|
+
// .npmignore only controls npm packaging of the shipped package
|
|
30
|
+
if (base === ".npmignore") return true;
|
|
31
|
+
if (base.endsWith(".tsbuildinfo")) return true;
|
|
32
|
+
return EXCLUDED_FILE_NAMES.has(base);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Recursively copy the template into dest, skipping build artifacts,
|
|
37
|
+
* lockfiles, dependencies, caches and local env secrets.
|
|
38
|
+
*/
|
|
39
|
+
export async function copyTemplate(dest) {
|
|
40
|
+
await fs.cp(TEMPLATE_DIR, dest, {
|
|
41
|
+
recursive: true,
|
|
42
|
+
filter: async (source) => {
|
|
43
|
+
if (path.relative(TEMPLATE_DIR, source) === "") return true;
|
|
44
|
+
const stat = await fs.stat(source);
|
|
45
|
+
if (stat.isDirectory()) {
|
|
46
|
+
return !EXCLUDED_DIR_NAMES.has(path.basename(source));
|
|
47
|
+
}
|
|
48
|
+
return !isExcludedFile(source);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Restore the npm-safe `_gitignore` sentinel as `.gitignore` in the generated project. */
|
|
54
|
+
export async function restoreGitignore(dest) {
|
|
55
|
+
const sentinel = path.join(dest, "_gitignore");
|
|
56
|
+
const gitignore = path.join(dest, ".gitignore");
|
|
57
|
+
try {
|
|
58
|
+
const content = await fs.readFile(sentinel, "utf8");
|
|
59
|
+
await fs.writeFile(gitignore, content);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// "Running from a repo checkout": template/.gitignore is present as the real
|
|
62
|
+
// file and no _gitignore sentinel is shipped, so fs.cp already wrote .gitignore.
|
|
63
|
+
try {
|
|
64
|
+
await fs.access(gitignore);
|
|
65
|
+
} catch {
|
|
66
|
+
throw new Error("Could not restore .gitignore in the generated project.", { cause: error });
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
await fs.rm(sentinel, { force: true });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The generated project keeps the template's deps/scripts; only its name changes. */
|
|
74
|
+
export async function applyProjectName(dest, name) {
|
|
75
|
+
const pkgPath = path.join(dest, "package.json");
|
|
76
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
|
|
77
|
+
pkg.name = name;
|
|
78
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function assertFreshDestination(root, folder) {
|
|
82
|
+
const dest = path.resolve(root, folder);
|
|
83
|
+
if (path.parse(dest).root === path.normalize(dest)) {
|
|
84
|
+
throw new Error(`"${folder}" resolves to a drive root. Choose a project folder name, not a disk root.`);
|
|
85
|
+
}
|
|
86
|
+
if (dest === process.cwd()) {
|
|
87
|
+
throw new Error("Destination resolves to the current directory. Use a new folder name.");
|
|
88
|
+
}
|
|
89
|
+
let entries;
|
|
90
|
+
try {
|
|
91
|
+
entries = await fs.readdir(dest);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error.code === "ENOENT") return dest;
|
|
94
|
+
if (error.code === "ENOTDIR") {
|
|
95
|
+
throw new Error(`"${dest}" exists and is a file. Choose a different project name.`);
|
|
96
|
+
}
|
|
97
|
+
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Permission denied reading "${dest}". Check folder permissions or pick another location.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
if (entries.length > 0) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Destination "${dest}" already exists and is not empty. Refusing to overwrite existing files.`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return dest;
|
|
110
|
+
}
|