create-nextjs-cms 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +395 -0
  6. package/dist/lib/utils.d.ts +11 -0
  7. package/dist/lib/utils.d.ts.map +1 -0
  8. package/dist/lib/utils.js +48 -0
  9. package/package.json +44 -0
  10. package/templates/default/.env +24 -0
  11. package/templates/default/.env.development +8 -0
  12. package/templates/default/.eslintrc.json +5 -0
  13. package/templates/default/.prettierignore +7 -0
  14. package/templates/default/.prettierrc.json +19 -0
  15. package/templates/default/CHANGELOG.md +77 -0
  16. package/templates/default/README.md +45 -0
  17. package/templates/default/app/(auth)/auth/login/LoginPage.tsx +175 -0
  18. package/templates/default/app/(auth)/auth/login/page.tsx +12 -0
  19. package/templates/default/app/(rootLayout)/admins/page.tsx +5 -0
  20. package/templates/default/app/(rootLayout)/advanced/page.tsx +5 -0
  21. package/templates/default/app/(rootLayout)/analytics/page.tsx +7 -0
  22. package/templates/default/app/(rootLayout)/browse/[section]/[page]/page.tsx +7 -0
  23. package/templates/default/app/(rootLayout)/categorized/[section]/page.tsx +7 -0
  24. package/templates/default/app/(rootLayout)/dashboard/page.tsx +7 -0
  25. package/templates/default/app/(rootLayout)/edit/[section]/[itemId]/page.tsx +7 -0
  26. package/templates/default/app/(rootLayout)/emails/page.tsx +6 -0
  27. package/templates/default/app/(rootLayout)/layout.tsx +5 -0
  28. package/templates/default/app/(rootLayout)/loading.tsx +10 -0
  29. package/templates/default/app/(rootLayout)/log/page.tsx +7 -0
  30. package/templates/default/app/(rootLayout)/new/[section]/page.tsx +7 -0
  31. package/templates/default/app/(rootLayout)/page.tsx +9 -0
  32. package/templates/default/app/(rootLayout)/section/[section]/page.tsx +7 -0
  33. package/templates/default/app/(rootLayout)/settings/page.tsx +7 -0
  34. package/templates/default/app/_trpc/client.ts +4 -0
  35. package/templates/default/app/api/auth/csrf/route.ts +25 -0
  36. package/templates/default/app/api/auth/refresh/route.ts +10 -0
  37. package/templates/default/app/api/auth/route.ts +23 -0
  38. package/templates/default/app/api/auth/session/route.ts +20 -0
  39. package/templates/default/app/api/editor/photo/route.ts +42 -0
  40. package/templates/default/app/api/photo/route.ts +27 -0
  41. package/templates/default/app/api/placeholder/route.ts +7 -0
  42. package/templates/default/app/api/submit/section/item/[slug]/route.ts +63 -0
  43. package/templates/default/app/api/submit/section/item/route.ts +53 -0
  44. package/templates/default/app/api/submit/section/simple/route.ts +54 -0
  45. package/templates/default/app/api/trpc/[trpc]/route.ts +33 -0
  46. package/templates/default/app/api/video/route.ts +174 -0
  47. package/templates/default/app/dictionaries.ts +14 -0
  48. package/templates/default/app/layout.tsx +28 -0
  49. package/templates/default/app/providers.tsx +151 -0
  50. package/templates/default/cli.ts +4 -0
  51. package/templates/default/components/AdminCard.tsx +163 -0
  52. package/templates/default/components/AdminEditPage.tsx +123 -0
  53. package/templates/default/components/AdminPrivilegeCard.tsx +184 -0
  54. package/templates/default/components/AdminsPage.tsx +43 -0
  55. package/templates/default/components/AdvancedSettingsPage.tsx +167 -0
  56. package/templates/default/components/AnalyticsPage.tsx +127 -0
  57. package/templates/default/components/BarChartBox.tsx +43 -0
  58. package/templates/default/components/BrowsePage.tsx +119 -0
  59. package/templates/default/components/CategorizedSectionPage.tsx +36 -0
  60. package/templates/default/components/CategoryDeleteConfirmPage.tsx +129 -0
  61. package/templates/default/components/CategorySectionSelectInput.tsx +139 -0
  62. package/templates/default/components/ConditionalFields.tsx +49 -0
  63. package/templates/default/components/ContainerBox.tsx +24 -0
  64. package/templates/default/components/DashboardPage.tsx +187 -0
  65. package/templates/default/components/DashboardPageAlt.tsx +43 -0
  66. package/templates/default/components/DefaultNavItems.tsx +3 -0
  67. package/templates/default/components/Dropzone.tsx +153 -0
  68. package/templates/default/components/EmailCard.tsx +137 -0
  69. package/templates/default/components/EmailPasswordForm.tsx +84 -0
  70. package/templates/default/components/EmailQuotaForm.tsx +72 -0
  71. package/templates/default/components/EmailsPage.tsx +48 -0
  72. package/templates/default/components/GalleryPhoto.tsx +93 -0
  73. package/templates/default/components/InfoCard.tsx +94 -0
  74. package/templates/default/components/ItemEditPage.tsx +217 -0
  75. package/templates/default/components/Layout.tsx +70 -0
  76. package/templates/default/components/LoadingSpinners.tsx +67 -0
  77. package/templates/default/components/LogPage.tsx +17 -0
  78. package/templates/default/components/Modal.tsx +99 -0
  79. package/templates/default/components/Navbar.tsx +29 -0
  80. package/templates/default/components/NavbarAlt.tsx +182 -0
  81. package/templates/default/components/NewAdminForm.tsx +172 -0
  82. package/templates/default/components/NewEmailForm.tsx +131 -0
  83. package/templates/default/components/NewPage.tsx +206 -0
  84. package/templates/default/components/NewVariantComponent.tsx +228 -0
  85. package/templates/default/components/PhotoGallery.tsx +35 -0
  86. package/templates/default/components/PieChartBox.tsx +101 -0
  87. package/templates/default/components/ProgressBar.tsx +24 -0
  88. package/templates/default/components/ProtectedDocument.tsx +78 -0
  89. package/templates/default/components/ProtectedImage.tsx +143 -0
  90. package/templates/default/components/ProtectedVideo.tsx +76 -0
  91. package/templates/default/components/SectionItemCard.tsx +143 -0
  92. package/templates/default/components/SectionItemStatusBadge.tsx +16 -0
  93. package/templates/default/components/SectionPage.tsx +124 -0
  94. package/templates/default/components/SelectBox.tsx +99 -0
  95. package/templates/default/components/SelectInputButtons.tsx +124 -0
  96. package/templates/default/components/SettingsPage.tsx +238 -0
  97. package/templates/default/components/Sidebar.tsx +209 -0
  98. package/templates/default/components/SidebarDropdownItem.tsx +74 -0
  99. package/templates/default/components/SidebarItem.tsx +19 -0
  100. package/templates/default/components/TempPage.tsx +12 -0
  101. package/templates/default/components/ThemeProvider.tsx +8 -0
  102. package/templates/default/components/TooltipComponent.tsx +27 -0
  103. package/templates/default/components/VariantCard.tsx +123 -0
  104. package/templates/default/components/VariantEditPage.tsx +229 -0
  105. package/templates/default/components/analytics/BounceRate.tsx +69 -0
  106. package/templates/default/components/analytics/LivePageViews.tsx +54 -0
  107. package/templates/default/components/analytics/LiveUsersCount.tsx +32 -0
  108. package/templates/default/components/analytics/MonthlyPageViews.tsx +41 -0
  109. package/templates/default/components/analytics/TopCountries.tsx +51 -0
  110. package/templates/default/components/analytics/TopDevices.tsx +45 -0
  111. package/templates/default/components/analytics/TopMediums.tsx +57 -0
  112. package/templates/default/components/analytics/TopSources.tsx +44 -0
  113. package/templates/default/components/analytics/TotalPageViews.tsx +40 -0
  114. package/templates/default/components/analytics/TotalSessions.tsx +40 -0
  115. package/templates/default/components/analytics/TotalUniqueUsers.tsx +40 -0
  116. package/templates/default/components/custom/RightHomeRoomVariantCard.tsx +137 -0
  117. package/templates/default/components/dndKit/Draggable.tsx +21 -0
  118. package/templates/default/components/dndKit/Droppable.tsx +20 -0
  119. package/templates/default/components/dndKit/SortableItem.tsx +18 -0
  120. package/templates/default/components/form/DateRangeFormInput.tsx +55 -0
  121. package/templates/default/components/form/Form.tsx +298 -0
  122. package/templates/default/components/form/FormInputElement.tsx +68 -0
  123. package/templates/default/components/form/FormInputs.tsx +108 -0
  124. package/templates/default/components/form/helpers/util.ts +20 -0
  125. package/templates/default/components/form/inputs/CheckboxFormInput.tsx +33 -0
  126. package/templates/default/components/form/inputs/ColorFormInput.tsx +44 -0
  127. package/templates/default/components/form/inputs/DateFormInput.tsx +107 -0
  128. package/templates/default/components/form/inputs/DocumentFormInput.tsx +124 -0
  129. package/templates/default/components/form/inputs/MapFormInput.tsx +139 -0
  130. package/templates/default/components/form/inputs/MultipleSelectFormInput.tsx +150 -0
  131. package/templates/default/components/form/inputs/NumberFormInput.tsx +42 -0
  132. package/templates/default/components/form/inputs/PasswordFormInput.tsx +47 -0
  133. package/templates/default/components/form/inputs/PhotoFormInput.tsx +218 -0
  134. package/templates/default/components/form/inputs/RichTextFormInput.tsx +133 -0
  135. package/templates/default/components/form/inputs/SelectFormInput.tsx +164 -0
  136. package/templates/default/components/form/inputs/TagsFormInput.tsx +63 -0
  137. package/templates/default/components/form/inputs/TextFormInput.tsx +48 -0
  138. package/templates/default/components/form/inputs/TextareaFormInput.tsx +47 -0
  139. package/templates/default/components/form/inputs/VideoFormInput.tsx +117 -0
  140. package/templates/default/components/pagination/Pagination.tsx +36 -0
  141. package/templates/default/components/pagination/PaginationButtons.tsx +145 -0
  142. package/templates/default/components/ui/accordion.tsx +57 -0
  143. package/templates/default/components/ui/alert.tsx +46 -0
  144. package/templates/default/components/ui/badge.tsx +33 -0
  145. package/templates/default/components/ui/button.tsx +57 -0
  146. package/templates/default/components/ui/calendar.tsx +68 -0
  147. package/templates/default/components/ui/card.tsx +76 -0
  148. package/templates/default/components/ui/checkbox.tsx +29 -0
  149. package/templates/default/components/ui/dropdown-menu.tsx +205 -0
  150. package/templates/default/components/ui/input.tsx +25 -0
  151. package/templates/default/components/ui/label.tsx +26 -0
  152. package/templates/default/components/ui/popover.tsx +31 -0
  153. package/templates/default/components/ui/scroll-area.tsx +42 -0
  154. package/templates/default/components/ui/select.tsx +164 -0
  155. package/templates/default/components/ui/sheet.tsx +107 -0
  156. package/templates/default/components/ui/switch.tsx +29 -0
  157. package/templates/default/components/ui/table.tsx +120 -0
  158. package/templates/default/components/ui/tabs.tsx +55 -0
  159. package/templates/default/components/ui/toast.tsx +113 -0
  160. package/templates/default/components/ui/toaster.tsx +35 -0
  161. package/templates/default/components/ui/tooltip.tsx +30 -0
  162. package/templates/default/components/ui/use-toast.ts +188 -0
  163. package/templates/default/components.json +16 -0
  164. package/templates/default/context/ModalProvider.tsx +53 -0
  165. package/templates/default/drizzle.config.ts +4 -0
  166. package/templates/default/dynamic-schemas/schema.ts +373 -0
  167. package/templates/default/env/env.js +130 -0
  168. package/templates/default/envConfig.ts +4 -0
  169. package/templates/default/hooks/useModal.ts +8 -0
  170. package/templates/default/lib/apiHelpers.ts +106 -0
  171. package/templates/default/lz.config.ts +40 -0
  172. package/templates/default/middleware.ts +33 -0
  173. package/templates/default/next.config.ts +46 -0
  174. package/templates/default/package.json +134 -0
  175. package/templates/default/postcss.config.js +6 -0
  176. package/templates/default/postinstall.js +14 -0
  177. package/templates/default/public/blank_avatar.png +0 -0
  178. package/templates/default/public/favicon.ico +0 -0
  179. package/templates/default/public/img/placeholder.svg +1 -0
  180. package/templates/default/public/lazemni_logo.png +0 -0
  181. package/templates/default/public/next.svg +1 -0
  182. package/templates/default/public/vercel.svg +1 -0
  183. package/templates/default/section-tests.ts +92 -0
  184. package/templates/default/styles/globals.css +88 -0
  185. package/templates/default/tailwind.config.js +95 -0
  186. package/templates/default/test.ts +77 -0
  187. package/templates/default/tsconfig.json +44 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 create-nextjs-cms
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Create Nextjs CMS
2
+
3
+ The best and official way to create a nextjs-cms project with all the necessary configuration and dependencies.
4
+
5
+ ## Installation
6
+
7
+ You can create a new cms project interactively by running:
8
+
9
+ ```bash
10
+ npx create-nextjs-cms
11
+ # or
12
+ yarn create nextjs-cms
13
+ # or
14
+ pnpm create nextjs-cms
15
+ # or
16
+ bun create nextjs-cms
17
+ ```
18
+
19
+ ## Project Structure
20
+
21
+ After running `create-nextjs-cms`, you'll have a fully configured nextjs-cms project with:
22
+
23
+ - ✅ Next.js application setup
24
+ - ✅ CMS configuration files
25
+ - ✅ Database configuration
26
+ - ✅ TypeScript configuration
27
+ - ✅ All necessary dependencies
28
+ - ✅ Example sections and fields
29
+ - ✅ Development scripts
30
+
31
+ ## Next Steps
32
+
33
+ After creating your project:
34
+
35
+ ```bash
36
+ cd my-cms-app
37
+ pnpm dev
38
+ ```
39
+
40
+ Then visit `http://localhost:3000` to see your CMS in action!
41
+
42
+ ## Requirements
43
+
44
+ - Node.js 18+
45
+ - A package manager (pnpm, npm, yarn, or bun)
46
+
47
+ ## Troubleshooting
48
+
49
+ ### Directory already exists
50
+
51
+ If you try to create a project in a directory that already exists, you'll get an error. Either:
52
+ - Choose a different directory name
53
+ - Use `.` to create in the current directory (must be empty)
54
+
55
+ ### Installation fails
56
+
57
+ If automatic dependency installation fails, you can manually install:
58
+
59
+ ```bash
60
+ cd my-cms-app
61
+ pnpm install
62
+ ```
63
+
64
+ ### Template not found
65
+
66
+ If you see an error about the template directory not being found, ensure you're using the published version of the package from npm, not a local development version.
67
+
68
+
69
+ ## License
70
+
71
+ MIT License - see LICENSE file for details.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs-extra';
3
+ import { execSync } from 'node:child_process';
4
+ import { fileURLToPath } from 'node:url';
5
+ import path, { dirname, resolve, relative, basename } from 'node:path';
6
+ import { Command } from 'commander';
7
+ import prompts from 'prompts';
8
+ import { expandHome, isValidPkgName, isEmptyDir, detectPackageManager, validateTemplate } from './lib/utils.js';
9
+ import { readFileSync } from 'node:fs';
10
+ import chalk from 'chalk';
11
+ /** Resolve __dirname for ESM */
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ const packageJson = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
15
+ /**
16
+ * Your build outputs to: dist/index.js
17
+ * Template is published at: templates/default/
18
+ * So at runtime we need ../templates/default from dist/index.js.
19
+ */
20
+ const templateDir = fileURLToPath(new URL('../templates/default/', import.meta.url));
21
+ const handleSigTerm = () => process.exit(0);
22
+ process.on('SIGINT', handleSigTerm);
23
+ process.on('SIGTERM', handleSigTerm);
24
+ const onPromptState = (state) => {
25
+ if (state.aborted) {
26
+ // If we don't re-enable the terminal cursor before exiting
27
+ // the program, the cursor will remain hidden
28
+ process.stdout.write('\x1B[?25h');
29
+ process.stdout.write('\n');
30
+ process.exit(1);
31
+ }
32
+ };
33
+ /**
34
+ * Creates a blog section file in the sections folder
35
+ */
36
+ async function createBlogSection(targetDir) {
37
+ const sectionsDir = path.join(targetDir, 'sections');
38
+ const blogSectionPath = path.join(sectionsDir, 'blog.section.ts');
39
+ // Ensure sections directory exists
40
+ await fs.ensureDir(sectionsDir);
41
+ const blogSectionContent = `import { dateField, photoField, richTextField, textField } from 'nextjs-cms/core/fields'
42
+ import { hasItemsSection } from 'nextjs-cms/core/sections'
43
+
44
+ const title = textField({
45
+ name: 'title',
46
+ label: 'Title',
47
+ required: true,
48
+ order: 1,
49
+ })
50
+
51
+ const slug = textField({
52
+ name: 'slug',
53
+ label: 'Slug',
54
+ required: false,
55
+ order: 2,
56
+ })
57
+
58
+ const coverPhotoField = photoField({
59
+ name: 'coverphoto',
60
+ label: 'Cover Photo',
61
+ watermark: false,
62
+ required: true,
63
+ order: 3,
64
+ size: {
65
+ width: 1200,
66
+ height: 628,
67
+ crop: true,
68
+ },
69
+ thumbnail: {
70
+ width: 400,
71
+ height: 209,
72
+ crop: true,
73
+ quality: 80,
74
+ },
75
+ type: ['jpg', 'jpeg', 'png', 'webp'],
76
+ })
77
+
78
+ const excerpt = textField({
79
+ name: 'excerpt',
80
+ label: 'Excerpt',
81
+ required: false,
82
+ order: 4,
83
+ })
84
+
85
+ const content = richTextField({
86
+ name: 'content',
87
+ label: 'Content',
88
+ required: true,
89
+ order: 5,
90
+ allowMedia: true,
91
+ allowImageUploads: {
92
+ publicURLPrefix: '/photos',
93
+ },
94
+ })
95
+
96
+ const publishedAt = dateField({
97
+ name: 'published_at',
98
+ label: 'Published At',
99
+ required: false,
100
+ order: 6,
101
+ })
102
+
103
+ const metaDescription = textField({
104
+ name: 'meta_description',
105
+ label: 'Meta Description',
106
+ required: false,
107
+ order: 7,
108
+ })
109
+
110
+ const metaKeywords = textField({
111
+ name: 'meta_keywords',
112
+ label: 'Meta Keywords',
113
+ required: false,
114
+ order: 8,
115
+ })
116
+
117
+ export default hasItemsSection({
118
+ name: 'blog',
119
+ order: 1,
120
+ icon: 'icon',
121
+ readonly: false,
122
+ headingField: title,
123
+ title: {
124
+ section: 'Blog',
125
+ singular: 'Post',
126
+ plural: 'Posts',
127
+ },
128
+ gallery: {
129
+ db: {
130
+ tableName: 'blog_gallery',
131
+ identifierField: 'reference_id',
132
+ photoNameField: 'photo',
133
+ metaField: 'meta',
134
+ },
135
+ },
136
+ db: {
137
+ table: 'blog',
138
+ fulltext: [
139
+ {
140
+ columns: [title],
141
+ name: 'title_fulltext',
142
+ },
143
+ {
144
+ columns: [content],
145
+ name: 'content_fulltext',
146
+ },
147
+ ],
148
+ unique: [
149
+ {
150
+ columns: [slug],
151
+ name: 'slug_unique',
152
+ },
153
+ ],
154
+ index: [
155
+ {
156
+ columns: [publishedAt],
157
+ name: 'published_at_index',
158
+ },
159
+ ],
160
+ },
161
+ search: {
162
+ searchFields: [title, content],
163
+ },
164
+ coverPhotoField: coverPhotoField,
165
+ generateQR: false,
166
+ fields: [title, slug, coverPhotoField, excerpt, content, publishedAt, metaDescription, metaKeywords],
167
+ })
168
+ `;
169
+ await fs.writeFile(blogSectionPath, blogSectionContent, 'utf-8');
170
+ console.log('✅ Blog section created successfully!');
171
+ }
172
+ async function createNextjsCms() {
173
+ console.log('🚀 Welcome to NextJS CMS!');
174
+ console.log('Creating your new CMS project...\n');
175
+ let projectPath = '';
176
+ const program = new Command(packageJson.name)
177
+ .version(packageJson.version, '-v, --version', 'Output the current version of create-nextjs-cms.')
178
+ .argument('[directory]')
179
+ .usage('[directory] [options]')
180
+ .helpOption('-h, --help', 'Display this help message.')
181
+ .action((name) => {
182
+ // Commander does not implicitly support negated options. When they are used
183
+ // by the user they will be interpreted as the positional argument (name) in
184
+ // the action handler.
185
+ if (name && typeof name === 'string' && !name.startsWith('--no-')) {
186
+ projectPath = name;
187
+ }
188
+ })
189
+ .allowUnknownOption()
190
+ .parse(process.argv);
191
+ const options = {
192
+ targetDir: '',
193
+ projectName: '',
194
+ templateDir: templateDir,
195
+ addBlogSection: false,
196
+ };
197
+ let targetIsCwd = false;
198
+ try {
199
+ // Ensure template folder is present in the published package
200
+ if (!(await fs.pathExists(templateDir))) {
201
+ console.error('❌ Template directory not found in the published package.');
202
+ console.error(' Make sure "templates/" is included in package.json#files and actually published.');
203
+ process.exit(1);
204
+ }
205
+ // Validate template structure
206
+ await validateTemplate(templateDir);
207
+ // If no directory was provided, prompt for project name
208
+ if (!projectPath) {
209
+ const res = await prompts({
210
+ onState: onPromptState,
211
+ type: 'text',
212
+ name: 'path',
213
+ message: 'What is your project named?',
214
+ initial: 'my-cms-app',
215
+ validate: (name) => {
216
+ const validation = isValidPkgName(basename(resolve(name)));
217
+ if (validation) {
218
+ return true;
219
+ }
220
+ return 'Invalid project name: Project name can only contain letters, numbers, hyphens, and underscores.';
221
+ },
222
+ });
223
+ if (typeof res.path === 'string') {
224
+ projectPath = res.path.trim();
225
+ }
226
+ }
227
+ if (!projectPath) {
228
+ console.log('\nPlease specify the project directory:\n' +
229
+ ` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}\n` +
230
+ 'For example:\n' +
231
+ ` ${chalk.cyan(program.name())} ${chalk.green('my-cms-app')}\n\n` +
232
+ `Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`);
233
+ process.exit(1);
234
+ }
235
+ // Resolve target path from the caller's CWD
236
+ const rawTarget = expandHome(projectPath);
237
+ options.targetDir = path.isAbsolute(rawTarget) ? rawTarget : resolve(process.cwd(), rawTarget);
238
+ // Derive package name from final path
239
+ options.projectName = basename(options.targetDir);
240
+ // Validate name
241
+ if (!isValidPkgName(options.projectName)) {
242
+ console.error(`Could not create a project called ${chalk.red(`"${options.projectName}"`)} because of npm naming restrictions:`);
243
+ console.error(` ${chalk.red('*')} Project name can only contain letters, numbers, hyphens, and underscores.`);
244
+ process.exit(1);
245
+ }
246
+ targetIsCwd = path.normalize(options.targetDir) === path.normalize(process.cwd());
247
+ if (targetIsCwd) {
248
+ // Using current directory (e.g., ".")
249
+ if (!(await fs.pathExists(options.targetDir))) {
250
+ await fs.ensureDir(options.targetDir);
251
+ }
252
+ else if (!(await isEmptyDir(options.targetDir))) {
253
+ console.error('❌ Current directory is not empty. Choose an empty folder or a new directory name.');
254
+ console.error(' Tip: pnpm create nextjs-cms my-app');
255
+ process.exit(1);
256
+ }
257
+ }
258
+ else {
259
+ if (await fs.pathExists(options.targetDir)) {
260
+ console.error(`❌ Directory "${options.targetDir}" already exists.`);
261
+ process.exit(1);
262
+ }
263
+ await fs.ensureDir(options.targetDir);
264
+ }
265
+ // Ask if user wants to add blog section
266
+ const blogRes = await prompts({
267
+ onState: onPromptState,
268
+ type: 'toggle',
269
+ name: 'addBlogSection',
270
+ message: 'Would you like to add a default blog section config?',
271
+ initial: false,
272
+ active: 'Yes',
273
+ inactive: 'No',
274
+ });
275
+ if (typeof blogRes.addBlogSection === 'boolean') {
276
+ options.addBlogSection = Boolean(blogRes.addBlogSection);
277
+ }
278
+ console.log(`📁 Creating project in: ${options.targetDir}`);
279
+ // Copy template → project dir with a filter to skip build/cache artifacts
280
+ console.log('📁 Copying template files...');
281
+ await fs.copy(options.templateDir, options.targetDir, {
282
+ filter: (src) => {
283
+ const rel = relative(options.templateDir, src);
284
+ if (!rel || rel === '.')
285
+ return true;
286
+ return (!rel.startsWith('node_modules') &&
287
+ !rel.startsWith('.next') &&
288
+ !rel.startsWith('dist') &&
289
+ !rel.startsWith('.turbo') &&
290
+ !rel.includes('tsconfig.tsbuildinfo'));
291
+ },
292
+ // Overwrite is safe since target is ensured empty (or new)
293
+ overwrite: true,
294
+ errorOnExist: false,
295
+ });
296
+ console.log('✅ Template copied successfully!');
297
+ // Create blog section if requested
298
+ if (options.addBlogSection) {
299
+ console.log('📝 Creating blog section...');
300
+ await createBlogSection(options.targetDir);
301
+ }
302
+ // Update package.json name (if template contains package.json)
303
+ const packageJsonPath = path.join(options.targetDir, 'package.json');
304
+ if (await fs.pathExists(packageJsonPath)) {
305
+ try {
306
+ const pkg = (await fs.readJson(packageJsonPath));
307
+ pkg.name = options.projectName;
308
+ await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
309
+ console.log(`📝 Updated package.json name to: ${options.projectName}`);
310
+ }
311
+ catch (e) {
312
+ console.warn('⚠️ Could not update package.json name automatically.');
313
+ console.warn(` Error: ${e instanceof Error ? e.message : 'Unknown error'}`);
314
+ }
315
+ }
316
+ else {
317
+ console.warn('⚠️ No package.json found in template root; skipping name update.');
318
+ }
319
+ console.log('📦 Installing dependencies...');
320
+ process.chdir(options.targetDir);
321
+ const preferredPM = detectPackageManager();
322
+ let installed = false;
323
+ // Try preferred package manager first
324
+ try {
325
+ if (preferredPM === 'pnpm') {
326
+ execSync('pnpm install', { stdio: 'inherit' });
327
+ installed = true;
328
+ console.log('✅ Dependencies installed with pnpm!');
329
+ }
330
+ else if (preferredPM === 'yarn') {
331
+ execSync('yarn install', { stdio: 'inherit' });
332
+ installed = true;
333
+ console.log('✅ Dependencies installed with yarn!');
334
+ }
335
+ else if (preferredPM === 'bun') {
336
+ execSync('bun install', { stdio: 'inherit' });
337
+ installed = true;
338
+ console.log('✅ Dependencies installed with bun!');
339
+ }
340
+ else {
341
+ execSync('npm install', { stdio: 'inherit' });
342
+ installed = true;
343
+ console.log('✅ Dependencies installed with npm!');
344
+ }
345
+ }
346
+ catch {
347
+ console.error(`❌ ${preferredPM} install failed. Trying alternatives...`);
348
+ // Fallback to other package managers
349
+ const fallbacks = preferredPM === 'pnpm'
350
+ ? ['npm', 'yarn', 'bun']
351
+ : preferredPM === 'yarn'
352
+ ? ['npm', 'pnpm', 'bun']
353
+ : ['pnpm', 'yarn', 'bun'];
354
+ for (const pm of fallbacks) {
355
+ try {
356
+ execSync(`${pm} install`, { stdio: 'inherit' });
357
+ installed = true;
358
+ console.log(`✅ Dependencies installed with ${pm}!`);
359
+ break;
360
+ }
361
+ catch {
362
+ console.error(`❌ ${pm} install failed.`);
363
+ }
364
+ }
365
+ if (!installed) {
366
+ console.error('❌ Failed to install dependencies automatically.');
367
+ console.error(' Please run "pnpm install", "npm install", "yarn install", or "bun install" manually.');
368
+ }
369
+ }
370
+ console.log('\n🎉 Your NextJS CMS project has been created successfully!');
371
+ console.log('\nNext steps:');
372
+ console.log(` cd ${options.projectName}`);
373
+ console.log(installed
374
+ ? ` ${preferredPM} dev # or: npm run dev`
375
+ : ` ${preferredPM} install && ${preferredPM} dev # or: npm install && npm run dev`);
376
+ console.log('\nHappy coding! 🚀');
377
+ }
378
+ catch (error) {
379
+ console.error('❌ Failed to create project:', error);
380
+ // Clean up partial installation on error
381
+ if ((await fs.pathExists(options.targetDir)) && !targetIsCwd) {
382
+ console.log('🧹 Cleaning up partial installation...');
383
+ try {
384
+ await fs.remove(options.targetDir);
385
+ console.log('✅ Cleanup completed.');
386
+ }
387
+ catch {
388
+ console.error('⚠️ Could not clean up partial installation.');
389
+ console.error(` Please manually remove: ${options.targetDir}`);
390
+ }
391
+ }
392
+ process.exit(1);
393
+ }
394
+ }
395
+ void createNextjsCms();
@@ -0,0 +1,11 @@
1
+ /** Expand ~ to home */
2
+ export declare function expandHome(p: string): string;
3
+ /** Validate npm package name (simple rule) */
4
+ export declare function isValidPkgName(name: string): boolean;
5
+ /** Check if a directory is empty */
6
+ export declare function isEmptyDir(dir: string): Promise<boolean>;
7
+ /** Detect preferred package manager */
8
+ export declare function detectPackageManager(): 'pnpm' | 'npm' | 'yarn' | 'bun';
9
+ /** Validate template structure */
10
+ export declare function validateTemplate(templateDir: string): Promise<void>;
11
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAIA,uBAAuB;AACvB,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG5C;AAED,8CAA8C;AAC9C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,oCAAoC;AACpC,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO9D;AAED,uCAAuC;AACvC,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAMtE;AAED,kCAAkC;AAClC,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAczE"}
@@ -0,0 +1,48 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ /** Expand ~ to home */
5
+ export function expandHome(p) {
6
+ if (p.startsWith('~'))
7
+ return path.join(os.homedir(), p.slice(1));
8
+ return p;
9
+ }
10
+ /** Validate npm package name (simple rule) */
11
+ export function isValidPkgName(name) {
12
+ return /^[a-zA-Z0-9-_]+$/.test(name);
13
+ }
14
+ /** Check if a directory is empty */
15
+ export async function isEmptyDir(dir) {
16
+ try {
17
+ const entries = await fs.readdir(dir);
18
+ return entries.length === 0;
19
+ }
20
+ catch {
21
+ return true;
22
+ }
23
+ }
24
+ /** Detect preferred package manager */
25
+ export function detectPackageManager() {
26
+ const userAgent = process.env.npm_config_user_agent;
27
+ if (userAgent?.includes('pnpm'))
28
+ return 'pnpm';
29
+ if (userAgent?.includes('yarn'))
30
+ return 'yarn';
31
+ if (userAgent?.includes('bun'))
32
+ return 'bun';
33
+ return 'npm';
34
+ }
35
+ /** Validate template structure */
36
+ export async function validateTemplate(templateDir) {
37
+ const requiredFiles = ['package.json', 'next.config.ts', 'app/layout.tsx'];
38
+ const missingFiles = [];
39
+ for (const file of requiredFiles) {
40
+ if (!(await fs.pathExists(path.join(templateDir, file)))) {
41
+ missingFiles.push(file);
42
+ }
43
+ }
44
+ if (missingFiles.length > 0) {
45
+ console.warn('⚠️ Template missing some files:');
46
+ missingFiles.forEach((file) => console.warn(` - ${file}`));
47
+ }
48
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "create-nextjs-cms",
3
+ "version": "0.5.8",
4
+ "private": false,
5
+ "bin": {
6
+ "create-nextjs-cms": "./dist/index.js"
7
+ },
8
+ "files": [
9
+ "dist",
10
+ "templates"
11
+ ],
12
+ "dependencies": {
13
+ "chalk": "^5.4.1",
14
+ "commander": "^14.0.2",
15
+ "fs-extra": "^11.3.0",
16
+ "glob": "^11.0.3",
17
+ "js-yaml": "^4.1.1",
18
+ "prompts": "^2.4.2",
19
+ "nextjs-cms": "0.5.8"
20
+ },
21
+ "devDependencies": {
22
+ "@types/fs-extra": "^11.0.4",
23
+ "@types/js-yaml": "^4.0.9",
24
+ "@types/minimatch": "^5.1.2",
25
+ "@types/prompts": "^2.4.9",
26
+ "eslint": "^9.12.0",
27
+ "prettier": "^3.3.3",
28
+ "tsx": "^4.20.6",
29
+ "typescript": "^5.9.2",
30
+ "@lzcms/prettier-config": "0.1.0",
31
+ "@lzcms/eslint-config": "0.3.0",
32
+ "@lzcms/tsconfig": "0.1.0"
33
+ },
34
+ "prettier": "@lzcms/prettier-config",
35
+ "scripts": {
36
+ "dev": "tsc",
37
+ "prebuild": "tsx scripts/build.ts",
38
+ "build": "pnpm prebuild && tsc",
39
+ "clean": "git clean -xdf .cache .turbo dist node_modules",
40
+ "format": "prettier --check . --ignore-path ../../.gitignore",
41
+ "lint": "eslint",
42
+ "typecheck": "tsc --noEmit"
43
+ }
44
+ }
@@ -0,0 +1,24 @@
1
+ ACCESS_TOKEN_SECRET=
2
+ REFRESH_TOKEN_SECRET=
3
+ CSRF_TOKEN_SECRET=
4
+ ACCESS_TOKEN_EXPIRATION=
5
+ REFRESH_TOKEN_EXPIRATION=
6
+
7
+ NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=
8
+ NEXT_PUBLIC_RICHTEXT_INLINE_PUBLIC_URL=
9
+
10
+ ####
11
+ # generated by the cpanel plugin script
12
+ ####
13
+ CPANEL_DOMAIN=
14
+ CPANEL_USER=
15
+ CPANEL_PASSWORD=
16
+
17
+ ####
18
+ # generated by the init script
19
+ ####
20
+ DB_HOST=
21
+ DB_PORT=
22
+ DB_NAME=
23
+ DB_USER=
24
+ DB_PASSWORD=
@@ -0,0 +1,8 @@
1
+ ####
2
+ # generated by the init script
3
+ ####
4
+ DB_HOST=
5
+ DB_PORT=
6
+ DB_NAME=
7
+ DB_USER=
8
+ DB_PASSWORD=
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": ["next/core-web-vitals", "prettier", "next", "plugin:drizzle/recommended"],
3
+ "parser": "@typescript-eslint/parser",
4
+ "plugins": ["@typescript-eslint", "prettier", "drizzle"]
5
+ }
@@ -0,0 +1,7 @@
1
+ build/*
2
+ dist/*
3
+ public/*
4
+ .next/*
5
+ node_modules/*
6
+ package.json
7
+ *.log
@@ -0,0 +1,19 @@
1
+ {
2
+ "arrowParens": "always",
3
+ "bracketSpacing": true,
4
+ "embeddedLanguageFormatting": "auto",
5
+ "htmlWhitespaceSensitivity": "css",
6
+ "insertPragma": false,
7
+ "jsxSingleQuote": true,
8
+ "printWidth": 120,
9
+ "proseWrap": "preserve",
10
+ "quoteProps": "as-needed",
11
+ "requirePragma": false,
12
+ "semi": false,
13
+ "singleQuote": true,
14
+ "tabWidth": 4,
15
+ "trailingComma": "all",
16
+ "useTabs": false,
17
+ "vueIndentScriptAndStyle": false,
18
+ "plugins": ["prettier-plugin-tailwindcss"]
19
+ }