create-nextjs-cms 0.5.66 → 0.5.67

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 (46) hide show
  1. package/dist/cli/index.d.ts +9 -0
  2. package/dist/cli/index.d.ts.map +1 -0
  3. package/dist/cli/index.js +77 -0
  4. package/dist/helpers/check-directory.d.ts +6 -0
  5. package/dist/helpers/check-directory.d.ts.map +1 -0
  6. package/dist/helpers/check-directory.js +47 -0
  7. package/dist/{lib → helpers}/exec-utils.d.ts +1 -0
  8. package/dist/helpers/exec-utils.d.ts.map +1 -0
  9. package/dist/{lib → helpers}/exec-utils.js +2 -1
  10. package/dist/helpers/get-version.d.ts +2 -0
  11. package/dist/helpers/get-version.d.ts.map +1 -0
  12. package/dist/helpers/get-version.js +11 -0
  13. package/dist/helpers/logger.d.ts.map +1 -0
  14. package/dist/helpers/render-title.d.ts +3 -0
  15. package/dist/helpers/render-title.d.ts.map +1 -0
  16. package/dist/{lib → helpers}/render-title.js +10 -0
  17. package/dist/{lib → helpers}/utils.d.ts +9 -0
  18. package/dist/helpers/utils.d.ts.map +1 -0
  19. package/dist/{lib → helpers}/utils.js +42 -0
  20. package/dist/index.js +49 -235
  21. package/dist/lib/cms-setup.d.ts +1 -2
  22. package/dist/lib/cms-setup.d.ts.map +1 -1
  23. package/dist/lib/cms-setup.js +9 -13
  24. package/dist/lib/create-project.d.ts +9 -0
  25. package/dist/lib/create-project.d.ts.map +1 -0
  26. package/dist/lib/create-project.js +69 -0
  27. package/dist/lib/git.d.ts +6 -0
  28. package/dist/lib/git.d.ts.map +1 -0
  29. package/dist/lib/git.js +116 -0
  30. package/dist/lib/install-deps.d.ts.map +1 -1
  31. package/dist/lib/install-deps.js +13 -14
  32. package/dist/lib/validate-app-name.d.ts +2 -0
  33. package/dist/lib/validate-app-name.d.ts.map +1 -0
  34. package/dist/lib/validate-app-name.js +10 -0
  35. package/package.json +1 -1
  36. package/templates/default/package.json +2 -1
  37. package/dist/lib/exec-utils.d.ts.map +0 -1
  38. package/dist/lib/global-package.d.ts +0 -10
  39. package/dist/lib/global-package.d.ts.map +0 -1
  40. package/dist/lib/global-package.js +0 -158
  41. package/dist/lib/logger.d.ts.map +0 -1
  42. package/dist/lib/render-title.d.ts +0 -2
  43. package/dist/lib/render-title.d.ts.map +0 -1
  44. package/dist/lib/utils.d.ts.map +0 -1
  45. /package/dist/{lib → helpers}/logger.d.ts +0 -0
  46. /package/dist/{lib → helpers}/logger.js +0 -0
@@ -0,0 +1,9 @@
1
+ interface CliResults {
2
+ appName: string;
3
+ sectionsToAdd: ('blog' | 'category' | 'simple')[];
4
+ git: boolean;
5
+ databaseProvider: 'mysql';
6
+ }
7
+ export declare const runCli: () => Promise<CliResults>;
8
+ export {};
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAOA,UAAU,UAAU;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,CAAC,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC,EAAE,CAAA;IACjD,GAAG,EAAE,OAAO,CAAA;IACZ,gBAAgB,EAAE,OAAO,CAAA;CAC5B;AASD,eAAO,MAAM,MAAM,QAAa,OAAO,CAAC,UAAU,CAwEjD,CAAA"}
@@ -0,0 +1,77 @@
1
+ import * as p from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ import { validateAppName } from '../lib/validate-app-name.js';
4
+ import { Command } from 'commander';
5
+ import { removeTrailingSlash } from '../helpers/utils.js';
6
+ import { getCreateNextjsCmsVersion } from '../helpers/get-version.js';
7
+ const defaultOptions = {
8
+ appName: 'my-cms-app',
9
+ sectionsToAdd: [],
10
+ git: false,
11
+ databaseProvider: 'mysql',
12
+ };
13
+ export const runCli = async () => {
14
+ const version = getCreateNextjsCmsVersion();
15
+ const program = new Command('create-nextjs-cms')
16
+ .version(version, '-v, --version', 'Output the current version of create-nextjs-cms.')
17
+ .argument('[directory]', 'The directory to create the project in')
18
+ .usage('[directory] [options]')
19
+ .helpOption('-h, --help', 'Display this help message.')
20
+ .allowUnknownOption()
21
+ .parse(process.argv);
22
+ let providedName = program.args[0];
23
+ if (providedName) {
24
+ providedName = removeTrailingSlash(providedName).trim();
25
+ // Validate name
26
+ const _notValid = validateAppName(providedName);
27
+ if (_notValid !== undefined) {
28
+ p.log.message(`${chalk.gray(`${_notValid}`)}`);
29
+ providedName = undefined;
30
+ }
31
+ }
32
+ const project = await p.group({
33
+ name: providedName
34
+ ? () => Promise.resolve(providedName)
35
+ : () => p.text({
36
+ message: 'What is your project named?',
37
+ placeholder: defaultOptions.appName,
38
+ defaultValue: defaultOptions.appName,
39
+ validate: validateAppName,
40
+ }),
41
+ sections: () => {
42
+ return p.multiselect({
43
+ message: `Add sample sections? ${chalk.gray('(hit enter with no selections to skip)')}`,
44
+ options: [
45
+ { value: 'blog', label: 'Blog' },
46
+ { value: 'category', label: 'Category' },
47
+ { value: 'simple', label: 'Settings' },
48
+ ],
49
+ required: false,
50
+ });
51
+ },
52
+ database: () => {
53
+ return p.select({
54
+ message: 'What database provider would you like to use?',
55
+ options: [{ value: 'mysql', label: 'MySQL', hint: 'Other database providers coming soon' }],
56
+ initialValue: 'mysql',
57
+ });
58
+ },
59
+ git: () => {
60
+ return p.confirm({
61
+ message: 'Should we initialize a Git repository and stage the changes?',
62
+ initialValue: false,
63
+ });
64
+ },
65
+ }, {
66
+ onCancel() {
67
+ p.outro('❌ Aborting installation');
68
+ process.exit(1);
69
+ },
70
+ });
71
+ return {
72
+ appName: project.name,
73
+ sectionsToAdd: project.sections,
74
+ git: project.git,
75
+ databaseProvider: project.database,
76
+ };
77
+ };
@@ -0,0 +1,6 @@
1
+ export declare const resolveDirectory: (appName: string) => Promise<{
2
+ targetDir: string;
3
+ projectName: string;
4
+ targetIsCwd: boolean;
5
+ }>;
6
+ //# sourceMappingURL=check-directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-directory.d.ts","sourceRoot":"","sources":["../../src/helpers/check-directory.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,gBAAgB,GACzB,SAAS,MAAM,KAChB,OAAO,CAAC;IACP,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,OAAO,CAAA;CACvB,CAwCA,CAAA"}
@@ -0,0 +1,47 @@
1
+ import { expandHome } from './utils.js';
2
+ import path from 'node:path';
3
+ import { resolve } from 'node:path';
4
+ import { basename } from 'node:path';
5
+ import fs from 'fs-extra';
6
+ import { isEmptyDir } from './utils.js';
7
+ import * as p from '@clack/prompts';
8
+ import chalk from 'chalk';
9
+ export const resolveDirectory = async (appName) => {
10
+ // Resolve target path from the caller's CWD
11
+ const rawTarget = expandHome(appName);
12
+ const targetDir = path.isAbsolute(rawTarget) ? rawTarget : resolve(process.cwd(), rawTarget);
13
+ // Derive package name from final path
14
+ const projectName = basename(targetDir);
15
+ const targetIsCwd = path.normalize(targetDir) === path.normalize(process.cwd());
16
+ if (targetIsCwd) {
17
+ // Using current directory (".")
18
+ if (!(await fs.pathExists(targetDir))) {
19
+ await fs.ensureDir(targetDir);
20
+ }
21
+ else if (!(await isEmptyDir(targetDir))) {
22
+ p.log.error('Current directory is not empty. Choose an empty folder or a new directory name.');
23
+ p.log.message(chalk.gray('Tip: You can also specify a directory name like this: \n') +
24
+ chalk.green('pnpm create nextjs-cms ') +
25
+ chalk.italic.magenta('my-app'));
26
+ p.log.message(' ');
27
+ process.exit(1);
28
+ }
29
+ }
30
+ else {
31
+ if (await fs.pathExists(targetDir)) {
32
+ if (!(await isEmptyDir(targetDir))) {
33
+ p.log.error(`Directory "${targetDir}" is not empty.`);
34
+ p.log.message('Please choose an empty directory or a different directory name.');
35
+ process.exit(1);
36
+ }
37
+ }
38
+ else {
39
+ await fs.ensureDir(targetDir);
40
+ }
41
+ }
42
+ return {
43
+ targetDir,
44
+ projectName,
45
+ targetIsCwd,
46
+ };
47
+ };
@@ -20,6 +20,7 @@ export declare const execWithSpinner: (options: ExecWithSpinnerOptions) => Promi
20
20
  */
21
21
  export declare const execWithoutSpinner: (command: string, args: string[], options?: {
22
22
  cwd?: string;
23
+ stdin?: "pipe" | "ignore" | "inherit";
23
24
  stdout?: "pipe" | "ignore" | "inherit";
24
25
  stderr?: "pipe" | "ignore" | "inherit";
25
26
  reject?: boolean;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-utils.d.ts","sourceRoot":"","sources":["../../src/helpers/exec-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAE9B,MAAM,WAAW,sBAAsB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACvD,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,aAAa,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,sBAAsB,KAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAsDzF,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC3B,SAAS,MAAM,EACf,MAAM,MAAM,EAAE,EACd,UAAS;IACL,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC1B,KACP,OAAO,CAAC,IAAI,CAWd,CAAA"}
@@ -50,9 +50,10 @@ export const execWithSpinner = async (options) => {
50
50
  * Execute a command without a spinner (for cases where we want direct output)
51
51
  */
52
52
  export const execWithoutSpinner = async (command, args, options = {}) => {
53
- const { cwd, stdout, stderr, reject = true, env } = options;
53
+ const { cwd, stdin, stdout, stderr, reject = true, env } = options;
54
54
  await execa(command, args, {
55
55
  cwd,
56
+ stdin: stdin ?? (stdout === 'inherit' || stderr === 'inherit' ? 'inherit' : undefined),
56
57
  stdout,
57
58
  stderr,
58
59
  reject,
@@ -0,0 +1,2 @@
1
+ export declare const getCreateNextjsCmsVersion: () => string;
2
+ //# sourceMappingURL=get-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-version.d.ts","sourceRoot":"","sources":["../../src/helpers/get-version.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,yBAAyB,QAAO,MAW5C,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname } from 'node:path';
4
+ import path from 'node:path';
5
+ export const getCreateNextjsCmsVersion = () => {
6
+ /** Resolve __dirname for ESM */
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ const packageJson = JSON.parse(readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
10
+ return packageJson.version;
11
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/helpers/logger.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM;qBACE,OAAO,EAAE;mBAGX,OAAO,EAAE;kBAGV,OAAO,EAAE;kBAGT,OAAO,EAAE;qBAGN,OAAO,EAAE;CAG7B,CAAA"}
@@ -0,0 +1,3 @@
1
+ export declare const renderTitle: () => void;
2
+ export declare const renderFooter: () => void;
3
+ //# sourceMappingURL=render-title.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-title.d.ts","sourceRoot":"","sources":["../../src/helpers/render-title.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,WAAW,YASvB,CAAA;AAED,eAAO,MAAM,YAAY,YAUxB,CAAA"}
@@ -19,3 +19,13 @@ export const renderTitle = () => {
19
19
  }
20
20
  console.log(titleGradient.multiline(TITLE_TEXT));
21
21
  };
22
+ export const renderFooter = () => {
23
+ const titleGradient = gradient(Object.values(poimandresTheme));
24
+ // resolves weird behavior where the ascii is offset
25
+ const pkgManager = detectPackageManager();
26
+ if (pkgManager === 'yarn' || pkgManager === 'pnpm') {
27
+ console.log('');
28
+ }
29
+ console.log();
30
+ console.log(titleGradient.multiline('Your NextJS CMS project has been created successfully!'));
31
+ };
@@ -1,6 +1,7 @@
1
1
  export declare const TITLE_TEXT = "\n _\n ___ _ __ ___ __ _| |_ ___\n / __| '__/ _ \\/ _\\` | __/ _ \\\n | (__| | | __/ (_| | || __/\n \\___|_| \\___|\\__,_|\\__\\___|\n _ _\n _ __ _____ _| |_ (_)___\n | '_ \\ / _ \\ \\/ / __|| / __|\n | | | | __/> <| |_ | \\__ \\\n |_| |_|\\___/_/\\_\\__|/ |___/\n |__/\n ___ _ __ ___ ___\n / __| '_ \\` _ \\/ __|\n | (__| | | | | \\__ \\\n \\___|_| |_| |_|___/\n";
2
2
  /** Expand ~ to home */
3
3
  export declare function expandHome(p: string): string;
4
+ export declare const removeTrailingSlash: (input: string) => string;
4
5
  /** Validate npm package name (simple rule) */
5
6
  export declare function isValidPkgName(name: string): boolean;
6
7
  /** Check if a directory is empty */
@@ -10,4 +11,12 @@ export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
10
11
  export declare function detectPackageManager(): 'pnpm' | 'npm' | 'yarn' | 'bun';
11
12
  /** Validate template structure */
12
13
  export declare function validateTemplate(templateDir: string): Promise<void>;
14
+ /**
15
+ * Generates a random secret string using crypto.randomBytes
16
+ */
17
+ export declare function generateSecret(): string;
18
+ /**
19
+ * Updates .env file with generated random secrets
20
+ */
21
+ export declare function updateEnvSecrets(targetDir: string): Promise<void>;
13
22
  //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/helpers/utils.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,UAAU,4jBAgBtB,CAAA;AAED,uBAAuB;AACvB,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG5C;AAED,eAAO,MAAM,mBAAmB,GAAI,OAAO,MAAM,WAMhD,CAAA;AAED,8CAA8C;AAC9C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGpD;AAED,oCAAoC;AACpC,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO9D;AAED,uCAAuC;AACvC,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;AAC5D,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAkBtE;AAED,kCAAkC;AAClC,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAczE;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBvE"}
@@ -1,6 +1,9 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
+ import { randomBytes } from 'node:crypto';
5
+ import { log } from '@clack/prompts';
6
+ import chalk from 'chalk';
4
7
  export const TITLE_TEXT = `
5
8
  _
6
9
  ___ _ __ ___ __ _| |_ ___
@@ -24,8 +27,16 @@ export function expandHome(p) {
24
27
  return path.join(os.homedir(), p.slice(1));
25
28
  return p;
26
29
  }
30
+ export const removeTrailingSlash = (input) => {
31
+ if (input.length > 1 && input.endsWith('/')) {
32
+ input = input.slice(0, -1);
33
+ }
34
+ return input;
35
+ };
27
36
  /** Validate npm package name (simple rule) */
28
37
  export function isValidPkgName(name) {
38
+ if (name === '.')
39
+ return true;
29
40
  return /^[a-zA-Z0-9-_]+$/.test(name);
30
41
  }
31
42
  /** Check if a directory is empty */
@@ -74,3 +85,34 @@ export async function validateTemplate(templateDir) {
74
85
  missingFiles.forEach((file) => console.warn(` - ${file}`));
75
86
  }
76
87
  }
88
+ /**
89
+ * Generates a random secret string using crypto.randomBytes
90
+ */
91
+ export function generateSecret() {
92
+ return randomBytes(32).toString('hex');
93
+ }
94
+ /**
95
+ * Updates .env file with generated random secrets
96
+ */
97
+ export async function updateEnvSecrets(targetDir) {
98
+ const envPath = path.join(targetDir, '.env');
99
+ if (!(await fs.pathExists(envPath))) {
100
+ log.warn('No .env file found; skipping secret generation.');
101
+ return;
102
+ }
103
+ try {
104
+ let envContent = await fs.readFile(envPath, 'utf-8');
105
+ // Replace placeholder secrets with generated ones
106
+ envContent = envContent.replace(/ACCESS_TOKEN_SECRET=.*/, `ACCESS_TOKEN_SECRET=${generateSecret()}`);
107
+ envContent = envContent.replace(/REFRESH_TOKEN_SECRET=.*/, `REFRESH_TOKEN_SECRET=${generateSecret()}`);
108
+ envContent = envContent.replace(/CSRF_TOKEN_SECRET=.*/, `CSRF_TOKEN_SECRET=${generateSecret()}`);
109
+ envContent = envContent.replace(/ACCESS_TOKEN_EXPIRATION=.*/, 'ACCESS_TOKEN_EXPIRATION=2h');
110
+ envContent = envContent.replace(/REFRESH_TOKEN_EXPIRATION=.*/, 'REFRESH_TOKEN_EXPIRATION=1y');
111
+ await fs.writeFile(envPath, envContent, 'utf-8');
112
+ log.message(` - Generated random secrets in root ${chalk.green('.env')} file`);
113
+ }
114
+ catch (e) {
115
+ log.warn('Could not update .env secrets automatically.');
116
+ log.error(` Error: ${e instanceof Error ? e.message : 'Unknown error'}`);
117
+ }
118
+ }
package/dist/index.js CHANGED
@@ -1,262 +1,76 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'fs-extra';
3
- import { fileURLToPath } from 'node:url';
4
- import { randomBytes } from 'node:crypto';
5
- import path, { dirname, resolve, relative, basename } from 'node:path';
6
- import { Command } from 'commander';
7
- import { text, log } from '@clack/prompts';
8
- import { expandHome, isValidPkgName, isEmptyDir, detectPackageManager, validateTemplate } from './lib/utils.js';
9
- import { readFileSync } from 'node:fs';
3
+ import * as p from '@clack/prompts';
4
+ import { detectPackageManager } from './helpers/utils.js';
10
5
  import chalk from 'chalk';
11
- import { renderTitle } from './lib/render-title.js';
6
+ import { renderFooter, renderTitle } from './helpers/render-title.js';
12
7
  import { installDependencies } from './lib/install-deps.js';
13
8
  import { runCmsSetup } from './lib/cms-setup.js';
14
- import { removeGlobalPackage, installGlobalPackage } from './lib/global-package.js';
15
- import { createBlogSection, createCategorySection, createSimpleSection } from './lib/section-creators.js';
16
- /** Resolve __dirname for ESM */
17
- const __filename = fileURLToPath(import.meta.url);
18
- const __dirname = dirname(__filename);
19
- const packageJson = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
20
- /**
21
- * Your build outputs to: dist/index.js
22
- * Template is published at: templates/default/
23
- * So at runtime we need ../templates/default from dist/index.js.
24
- */
25
- const templateDir = fileURLToPath(new URL('../templates/default/', import.meta.url));
9
+ import { runCli } from './cli/index.js';
10
+ import { initializeGit } from './lib/git.js';
11
+ import { resolveDirectory } from './helpers/check-directory.js';
12
+ import { createProject } from './lib/create-project.js';
13
+ import { logger } from './helpers/logger.js';
26
14
  const handleSigTerm = () => process.exit(0);
27
15
  process.on('SIGINT', handleSigTerm);
28
16
  process.on('SIGTERM', handleSigTerm);
29
- /**
30
- * Generates a random secret string using crypto.randomBytes
31
- */
32
- function generateSecret() {
33
- return randomBytes(32).toString('hex');
34
- }
35
- /**
36
- * Updates .env file with generated random secrets
37
- */
38
- async function updateEnvSecrets(targetDir) {
39
- const envPath = path.join(targetDir, '.env');
40
- if (!(await fs.pathExists(envPath))) {
41
- log.warn('No .env file found; skipping secret generation.');
42
- return;
43
- }
44
- try {
45
- let envContent = await fs.readFile(envPath, 'utf-8');
46
- // Replace placeholder secrets with generated ones
47
- envContent = envContent.replace(/ACCESS_TOKEN_SECRET=.*/, `ACCESS_TOKEN_SECRET=${generateSecret()}`);
48
- envContent = envContent.replace(/REFRESH_TOKEN_SECRET=.*/, `REFRESH_TOKEN_SECRET=${generateSecret()}`);
49
- envContent = envContent.replace(/CSRF_TOKEN_SECRET=.*/, `CSRF_TOKEN_SECRET=${generateSecret()}`);
50
- envContent = envContent.replace(/ACCESS_TOKEN_EXPIRATION=.*/, 'ACCESS_TOKEN_EXPIRATION=2h');
51
- envContent = envContent.replace(/REFRESH_TOKEN_EXPIRATION=.*/, 'REFRESH_TOKEN_EXPIRATION=1y');
52
- await fs.writeFile(envPath, envContent, 'utf-8');
53
- log.message('Generated random secrets in .env file');
54
- }
55
- catch (e) {
56
- log.warn('Could not update .env secrets automatically.');
57
- log.error(` Error: ${e instanceof Error ? e.message : 'Unknown error'}`);
58
- }
59
- }
60
17
  async function createNextjsCms() {
61
18
  renderTitle();
62
- log.info('Create your new NextJS CMS project');
63
- let projectPath = '';
64
- const program = new Command(packageJson.name)
65
- .version(packageJson.version, '-v, --version', 'Output the current version of create-nextjs-cms.')
66
- .argument('[directory]')
67
- .usage('[directory] [options]')
68
- .helpOption('-h, --help', 'Display this help message.')
69
- .action((name) => {
70
- // Commander does not implicitly support negated options. When they are used
71
- // by the user they will be interpreted as the positional argument (name) in
72
- // the action handler.
73
- if (name && typeof name === 'string' && !name.startsWith('--no-')) {
74
- projectPath = name;
75
- }
76
- })
77
- .allowUnknownOption()
78
- .parse(process.argv);
79
- const options = {
80
- targetDir: '',
81
- projectName: '',
82
- templateDir: templateDir,
83
- sectionsToAdd: [],
84
- };
85
- let targetIsCwd = false;
19
+ console.log();
20
+ p.intro(chalk.cyan('Create new project'));
21
+ // Run CLI to get user input
22
+ const { appName, sectionsToAdd, git /*, databaseProvider*/ } = await runCli();
23
+ // Resolve directory
24
+ const { targetDir, projectName, targetIsCwd } = await resolveDirectory(appName);
86
25
  try {
87
- // Ensure template folder is present in the published package
88
- if (!(await fs.pathExists(templateDir))) {
89
- log.error('Template directory not found in the published package.');
90
- log.message('Please open an issue on the create-nextjs-cms repository: https://github.com/next-cms/create-nextjs-cms/issues');
91
- process.exit(1);
92
- }
93
- // Validate template structure
94
- await validateTemplate(templateDir);
95
- // If no directory was provided, prompt for project name
96
- if (!projectPath) {
97
- const res = await text({
98
- message: 'What is your project named?',
99
- placeholder: 'my-cms-app',
100
- defaultValue: 'my-cms-app',
101
- validate: (name) => {
102
- const validation = isValidPkgName(basename(resolve(name)));
103
- if (validation) {
104
- return;
105
- }
106
- return 'Invalid project name: Project name can only contain letters, numbers, hyphens, and underscores.';
107
- },
108
- });
109
- if (typeof res === 'string') {
110
- projectPath = res.trim();
111
- }
112
- }
113
- if (!projectPath) {
114
- log.error('\nPlease specify the project directory:\n' +
115
- ` ${chalk.cyan(program.name())} ${chalk.green('<project-directory>')}\n` +
116
- 'For example:\n' +
117
- ` ${chalk.cyan(program.name())} ${chalk.green('my-cms-app')}\n\n` +
118
- `Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`);
119
- process.exit(1);
120
- }
121
- // Resolve target path from the caller's CWD
122
- const rawTarget = expandHome(projectPath);
123
- options.targetDir = path.isAbsolute(rawTarget) ? rawTarget : resolve(process.cwd(), rawTarget);
124
- // Derive package name from final path
125
- options.projectName = basename(options.targetDir);
126
- // Validate name
127
- if (!isValidPkgName(options.projectName)) {
128
- log.error(`Could not create a project called ${chalk.red(`"${options.projectName}"`)} because of npm naming restrictions:`);
129
- log.message(` ${chalk.red('*')} Project name can only contain letters, numbers, hyphens, and underscores.`);
130
- process.exit(1);
131
- }
132
- targetIsCwd = path.normalize(options.targetDir) === path.normalize(process.cwd());
133
- if (targetIsCwd) {
134
- // Using current directory (e.g., ".")
135
- if (!(await fs.pathExists(options.targetDir))) {
136
- await fs.ensureDir(options.targetDir);
137
- }
138
- else if (!(await isEmptyDir(options.targetDir))) {
139
- log.error('Current directory is not empty. Choose an empty folder or a new directory name.');
140
- log.message(chalk.gray('Tip: You can also specify a directory name like this: \n') +
141
- chalk.green('pnpm create nextjs-cms ') +
142
- chalk.italic.magenta('my-app'));
143
- log.message(' ');
144
- process.exit(1);
145
- }
26
+ // Detect package manager
27
+ const preferredPM = detectPackageManager();
28
+ // Create project
29
+ await createProject({ targetDir, sectionsToAdd, projectName, preferredPM });
30
+ // Change working directory to the project
31
+ process.chdir(targetDir);
32
+ // Install dependencies first
33
+ let depsInstalled = false;
34
+ try {
35
+ await installDependencies({ projectDir: targetDir });
36
+ depsInstalled = true;
146
37
  }
147
- else {
148
- if (await fs.pathExists(options.targetDir)) {
149
- if (!(await isEmptyDir(options.targetDir))) {
150
- log.error(`Directory "${options.targetDir}" is not empty.`);
151
- log.message('Please choose an empty directory or a different directory name.');
152
- process.exit(1);
153
- }
154
- }
155
- else {
156
- await fs.ensureDir(options.targetDir);
157
- }
38
+ catch {
39
+ depsInstalled = false;
158
40
  }
159
- // Ask which sections to add
160
- const { multiselect } = await import('@clack/prompts');
161
- const sectionsRes = await multiselect({
162
- message: 'Would you like to add default sections?',
163
- options: [
164
- { value: 'blog', label: 'Blog' },
165
- { value: 'category', label: 'Category' },
166
- { value: 'simple', label: 'Settings' },
167
- ],
168
- required: false,
169
- });
170
- options.sectionsToAdd = sectionsRes;
171
- log.step(`Creating project in: ${options.targetDir}`);
172
- // Copy template → project dir with a filter to skip build/cache artifacts
173
- await fs.copy(options.templateDir, options.targetDir, {
174
- filter: (src) => {
175
- const rel = relative(options.templateDir, src);
176
- if (!rel || rel === '.')
177
- return true;
178
- return (!rel.startsWith('node_modules') &&
179
- !rel.startsWith('.next') &&
180
- !rel.startsWith('dist') &&
181
- !rel.startsWith('.turbo') &&
182
- !rel.includes('tsconfig.tsbuildinfo'));
183
- },
184
- // Overwrite is safe since target is ensured empty (or new)
185
- overwrite: true,
186
- errorOnExist: false,
187
- });
188
- // Generate random secrets for .env file
189
- await updateEnvSecrets(options.targetDir);
190
- // npm excludes .gitignore from packages, so we rename it back from _gitignore
191
- const gitignoreTemplatePath = path.join(options.targetDir, '_gitignore');
192
- const gitignorePath = path.join(options.targetDir, '.gitignore');
193
- if (await fs.pathExists(gitignoreTemplatePath)) {
194
- await fs.move(gitignoreTemplatePath, gitignorePath);
41
+ // Initialize Git repository if requested
42
+ if (git) {
43
+ await initializeGit(targetDir);
195
44
  }
196
- // Create selected sections
197
- if (options.sectionsToAdd.length > 0) {
198
- for (const section of options.sectionsToAdd) {
199
- switch (section) {
200
- case 'blog':
201
- await createBlogSection(options.targetDir);
202
- break;
203
- case 'category':
204
- await createCategorySection(options.targetDir);
205
- break;
206
- case 'simple':
207
- await createSimpleSection(options.targetDir);
208
- break;
209
- }
210
- }
211
- log.message(`${options.sectionsToAdd.join(', ')} section${options.sectionsToAdd.length > 1 ? 's' : ''} added successfully`);
45
+ if (depsInstalled) {
46
+ // Finally run CMS setup
47
+ await runCmsSetup({ dir: targetDir, preferredPM });
212
48
  }
213
- // Update package.json name (if template contains package.json)
214
- const packageJsonPath = path.join(options.targetDir, 'package.json');
215
- if (await fs.pathExists(packageJsonPath)) {
216
- try {
217
- const pkg = (await fs.readJson(packageJsonPath));
218
- pkg.name = options.projectName;
219
- await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
220
- }
221
- catch {
222
- log.warn('Could not update package.json name automatically.');
223
- }
49
+ renderFooter();
50
+ logger.message('\nNext steps:');
51
+ logger.message(`cd ${targetDir}`);
52
+ if (depsInstalled) {
53
+ logger.message(`${preferredPM} dev`);
224
54
  }
225
55
  else {
226
- log.warn('No package.json found in template root; skipping name update.');
227
- }
228
- process.chdir(options.targetDir);
229
- const preferredPM = detectPackageManager();
230
- let installed = false;
231
- try {
232
- log.step(`Using ${chalk.green(preferredPM)} as the package manager...`);
233
- // Install dependencies first
234
- await installDependencies({ projectDir: options.targetDir });
235
- installed = true;
236
- // Then remove any existing global package
237
- await removeGlobalPackage('nextjs-cms-kit');
238
- // Install the global package
239
- await installGlobalPackage('nextjs-cms-kit', packageJson.version);
240
- // Finally run CMS setup
241
- await runCmsSetup({ dir: options.targetDir, installed, preferredPM });
242
- }
243
- catch (error) {
244
- log.error(`❌ Failed to complete project setup: ${error instanceof Error ? error.message : 'Unknown error'}`);
245
- process.exit(1);
56
+ logger.message(`${preferredPM} install`);
57
+ logger.message(`${preferredPM} nextjs-cms-kit --dev setup`);
58
+ logger.message(`${preferredPM} dev`);
246
59
  }
60
+ console.log('\n');
247
61
  }
248
62
  catch (error) {
249
- log.error(`❌ Failed to create project: ${error instanceof Error ? error.message : 'Unknown error'}`);
63
+ p.outro(chalk.red(`❌ Failed to create project: ${error instanceof Error ? error.message : 'Unknown error'}`));
250
64
  // Clean up partial installation on error
251
- if ((await fs.pathExists(options.targetDir)) && !targetIsCwd) {
252
- log.info('🧹 Cleaning up partial installation...');
65
+ if ((await fs.pathExists(targetDir)) && !targetIsCwd) {
66
+ p.log.info('🧹 Cleaning up partial installation...');
253
67
  try {
254
- await fs.remove(options.targetDir);
255
- log.success('✅ Cleanup completed.');
68
+ await fs.remove(targetDir);
69
+ p.log.success('✅ Cleanup completed.');
256
70
  }
257
71
  catch {
258
- log.error('⚠️ Could not clean up partial installation.');
259
- log.message(` Please manually remove: ${options.targetDir}`);
72
+ p.log.error('⚠️ Could not clean up partial installation.');
73
+ p.log.message(` Please manually remove: ${targetDir}`);
260
74
  }
261
75
  }
262
76
  process.exit(1);
@@ -1,6 +1,5 @@
1
- export declare const runCmsSetup: ({ dir, installed, preferredPM, }: {
1
+ export declare const runCmsSetup: ({ dir, preferredPM }: {
2
2
  dir: string;
3
- installed: boolean;
4
3
  preferredPM: string;
5
4
  }) => Promise<void>;
6
5
  //# sourceMappingURL=cms-setup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cms-setup.d.ts","sourceRoot":"","sources":["../../src/lib/cms-setup.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,WAAW,GAAU,kCAI/B;IACC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACtB,kBAcA,CAAA"}
1
+ {"version":3,"file":"cms-setup.d.ts","sourceRoot":"","sources":["../../src/lib/cms-setup.ts"],"names":[],"mappings":"AAwBA,eAAO,MAAM,WAAW,GAAU,sBAAsB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,kBAU3F,CAAA"}
@@ -1,29 +1,25 @@
1
1
  import chalk from 'chalk';
2
- import { logger } from './logger.js';
3
- import { execWithoutSpinner } from './exec-utils.js';
4
- import { log } from '@clack/prompts';
5
- const runCmsSetupCommand = async (projectDir) => {
2
+ import { logger } from '../helpers/logger.js';
3
+ import { execWithoutSpinner } from '../helpers/exec-utils.js';
4
+ import * as p from '@clack/prompts';
5
+ const runCmsSetupCommand = async ({ preferredPM, projectDir, }) => {
6
6
  // For all package managers, use inherit for stdout/stderr to avoid hanging issues
7
7
  // The command will show its own output and the spinner will just indicate progress
8
- await execWithoutSpinner('nextjs-cms-kit', ['--dev', 'setup'], {
8
+ await execWithoutSpinner(preferredPM, ['nextjs-cms-kit', '--dev', 'setup'], {
9
9
  cwd: projectDir,
10
10
  stdout: 'inherit',
11
11
  stderr: 'inherit',
12
12
  });
13
13
  return null;
14
14
  };
15
- export const runCmsSetup = async ({ dir, installed, preferredPM, }) => {
16
- log.info(chalk.cyan('Running CMS setup...'));
15
+ export const runCmsSetup = async ({ dir, preferredPM }) => {
16
+ p.log.info(chalk.cyan('Running CMS setup'));
17
17
  try {
18
- await runCmsSetupCommand(dir);
19
- logger.success('\n🎉 Your NextJS CMS project has been created successfully!');
20
- logger.message('\nNext steps:');
21
- logger.message(` cd ${dir}`);
22
- logger.message(installed ? ` ${preferredPM} dev` : ` ${preferredPM} install && ${preferredPM} dev`);
18
+ await runCmsSetupCommand({ preferredPM, projectDir: dir });
23
19
  }
24
20
  catch (error) {
25
21
  logger.error('⚠️ CMS setup failed. You may need to run it manually:');
26
- logger.warn(' nextjs-cms-kit --dev setup');
22
+ logger.warn(`${preferredPM} nextjs-cms-kit --dev setup`);
27
23
  logger.error(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
28
24
  throw error;
29
25
  }
@@ -0,0 +1,9 @@
1
+ interface CreateProjectOptions {
2
+ targetDir: string;
3
+ sectionsToAdd: ('blog' | 'category' | 'simple')[];
4
+ projectName: string;
5
+ preferredPM: string;
6
+ }
7
+ export declare const createProject: ({ targetDir, sectionsToAdd, projectName, preferredPM }: CreateProjectOptions) => Promise<void>;
8
+ export {};
9
+ //# sourceMappingURL=create-project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-project.d.ts","sourceRoot":"","sources":["../../src/lib/create-project.ts"],"names":[],"mappings":"AASA,UAAU,oBAAoB;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,CAAC,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC,EAAE,CAAA;IACjD,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;CACtB;AAED,eAAO,MAAM,aAAa,GAAU,wDAAwD,oBAAoB,kBAmE/G,CAAA"}
@@ -0,0 +1,69 @@
1
+ import { relative } from 'node:path';
2
+ import path from 'node:path';
3
+ import fs from 'fs-extra';
4
+ import * as p from '@clack/prompts';
5
+ import chalk from 'chalk';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { updateEnvSecrets } from '../helpers/utils.js';
8
+ import { createBlogSection, createCategorySection, createSimpleSection } from './section-creators.js';
9
+ export const createProject = async ({ targetDir, sectionsToAdd, projectName, preferredPM }) => {
10
+ p.log.info(`Creating project in: ${chalk.green(targetDir)}`);
11
+ p.log.message(` - Using ${chalk.green(preferredPM)}`);
12
+ const templateDir = fileURLToPath(new URL('../../templates/default/', import.meta.url));
13
+ // Copy template → project dir with a filter to skip build/cache artifacts
14
+ await fs.copy(templateDir, targetDir, {
15
+ filter: (src) => {
16
+ const rel = relative(templateDir, src);
17
+ if (!rel || rel === '.')
18
+ return true;
19
+ return (!rel.startsWith('node_modules') &&
20
+ !rel.startsWith('.next') &&
21
+ !rel.startsWith('dist') &&
22
+ !rel.startsWith('.turbo') &&
23
+ !rel.includes('tsconfig.tsbuildinfo'));
24
+ },
25
+ // Overwrite is safe since target is ensured empty (or new)
26
+ overwrite: true,
27
+ errorOnExist: false,
28
+ });
29
+ // Generate random secrets for .env file
30
+ await updateEnvSecrets(targetDir);
31
+ // npm excludes .gitignore from packages, so we rename it back from _gitignore
32
+ const gitignoreTemplatePath = path.join(targetDir, '_gitignore');
33
+ const gitignorePath = path.join(targetDir, '.gitignore');
34
+ if (await fs.pathExists(gitignoreTemplatePath)) {
35
+ await fs.move(gitignoreTemplatePath, gitignorePath);
36
+ }
37
+ // Create selected sections
38
+ if (sectionsToAdd.length > 0) {
39
+ for (const section of sectionsToAdd) {
40
+ switch (section) {
41
+ case 'blog':
42
+ await createBlogSection(targetDir);
43
+ break;
44
+ case 'category':
45
+ await createCategorySection(targetDir);
46
+ break;
47
+ case 'simple':
48
+ await createSimpleSection(targetDir);
49
+ break;
50
+ }
51
+ }
52
+ p.log.message(` - ${chalk.green(sectionsToAdd.join(', '))} section${sectionsToAdd.length > 1 ? 's' : ''} added successfully`);
53
+ }
54
+ // Update package.json name (if template contains package.json)
55
+ const packageJsonPath = path.join(targetDir, 'package.json');
56
+ if (await fs.pathExists(packageJsonPath)) {
57
+ try {
58
+ const pkg = (await fs.readJson(packageJsonPath));
59
+ pkg.name = projectName;
60
+ await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
61
+ }
62
+ catch {
63
+ p.log.warn('Could not update package.json name automatically.');
64
+ }
65
+ }
66
+ else {
67
+ p.log.warn('No package.json found in template root; skipping name update.');
68
+ }
69
+ };
@@ -0,0 +1,6 @@
1
+ /** @returns Whether or not the provided directory has a `.git` subdirectory in it. */
2
+ export declare const isRootGitRepo: (dir: string) => boolean;
3
+ /** @returns Whether or not this directory or a parent directory has a `.git` directory. */
4
+ export declare const isInsideGitRepo: (dir: string) => Promise<boolean>;
5
+ export declare const initializeGit: (projectDir: string) => Promise<void>;
6
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/lib/git.ts"],"names":[],"mappings":"AAiBA,sFAAsF;AACtF,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,KAAG,OAE3C,CAAA;AAED,2FAA2F;AAC3F,eAAO,MAAM,eAAe,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,OAAO,CAYlE,CAAA;AAkBD,eAAO,MAAM,aAAa,GAAU,YAAY,MAAM,kBA0ErD,CAAA"}
@@ -0,0 +1,116 @@
1
+ import { execSync } from 'child_process';
2
+ import path from 'path';
3
+ import * as p from '@clack/prompts';
4
+ import chalk from 'chalk';
5
+ import { execa } from 'execa';
6
+ import fs from 'fs-extra';
7
+ import ora from 'ora';
8
+ const isGitInstalled = (dir) => {
9
+ try {
10
+ execSync('git --version', { cwd: dir });
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ };
17
+ /** @returns Whether or not the provided directory has a `.git` subdirectory in it. */
18
+ export const isRootGitRepo = (dir) => {
19
+ return fs.existsSync(path.join(dir, '.git'));
20
+ };
21
+ /** @returns Whether or not this directory or a parent directory has a `.git` directory. */
22
+ export const isInsideGitRepo = async (dir) => {
23
+ try {
24
+ // If this command succeeds, we're inside a git repo
25
+ await execa('git', ['rev-parse', '--is-inside-work-tree'], {
26
+ cwd: dir,
27
+ stdout: 'ignore',
28
+ });
29
+ return true;
30
+ }
31
+ catch {
32
+ // Else, it will throw a git-error and we return false
33
+ return false;
34
+ }
35
+ };
36
+ const getGitVersion = () => {
37
+ const stdout = execSync('git --version').toString().trim();
38
+ const gitVersionTag = stdout.split(' ')[2];
39
+ const major = gitVersionTag?.split('.')[0];
40
+ const minor = gitVersionTag?.split('.')[1];
41
+ return { major: Number(major), minor: Number(minor) };
42
+ };
43
+ /** @returns The git config value of "init.defaultBranch". If it is not set, returns "main". */
44
+ const getDefaultBranch = () => {
45
+ const stdout = execSync('git config --global init.defaultBranch || echo main').toString().trim();
46
+ return stdout;
47
+ };
48
+ // This initializes the Git-repository for the project
49
+ export const initializeGit = async (projectDir) => {
50
+ p.log.info(chalk.cyan('Initializing Git'));
51
+ if (!isGitInstalled(projectDir)) {
52
+ p.log.warn(`❌ ${chalk.yellow('Git is not installed. Skipping Git initialization')}`);
53
+ return;
54
+ }
55
+ const spinner = ora('Creating a new git repo...\n').start();
56
+ const isRoot = isRootGitRepo(projectDir);
57
+ const isInside = await isInsideGitRepo(projectDir);
58
+ const dirName = path.parse(projectDir).name; // skip full path for logging
59
+ if (isInside && isRoot) {
60
+ // Dir is a root git repo
61
+ spinner.stop();
62
+ const overwriteGit = await p.confirm({
63
+ message: `${chalk.redBright.bold('Warning:')} Git is already initialized in "${dirName}". Initializing a new git repository would delete the previous history. Would you like to continue anyways?`,
64
+ initialValue: false,
65
+ });
66
+ if (!overwriteGit) {
67
+ p.log.message(` - ${chalk.gray('Skipping Git initialization')}`);
68
+ return;
69
+ }
70
+ // Deleting the .git folder
71
+ fs.removeSync(path.join(projectDir, '.git'));
72
+ }
73
+ else if (isInside && !isRoot) {
74
+ // Dir is inside a git worktree
75
+ spinner.stop();
76
+ const initializeChildGitRepo = await p.confirm({
77
+ message: `${chalk.redBright.bold('Warning:')} "${dirName}" is already in a git worktree. Would you still like to initialize a new git repository in this directory?`,
78
+ initialValue: false,
79
+ });
80
+ if (!initializeChildGitRepo) {
81
+ p.log.message(` - ${chalk.gray('Skipping Git initialization')}`);
82
+ return;
83
+ }
84
+ }
85
+ // We're good to go, initializing the git repo
86
+ try {
87
+ const branchName = getDefaultBranch();
88
+ // --initial-branch flag was added in git v2.28.0
89
+ const { major, minor } = getGitVersion();
90
+ if (major < 2 || (major == 2 && minor < 28)) {
91
+ await execa('git', ['init'], { cwd: projectDir });
92
+ // symbolic-ref is used here due to refs/heads/master not existing
93
+ // It is only created after the first commit
94
+ // https://superuser.com/a/1419674
95
+ await execa('git', ['symbolic-ref', 'HEAD', `refs/heads/${branchName}`], {
96
+ cwd: projectDir,
97
+ });
98
+ }
99
+ else {
100
+ await execa('git', ['init', `--initial-branch=${branchName}`], {
101
+ cwd: projectDir,
102
+ });
103
+ }
104
+ await execa('git', ['add', '.'], { cwd: projectDir });
105
+ spinner.stop();
106
+ p.log.message(`${chalk.green(' - ✓ Successfully initialized and staged')} ${chalk.green.bold('git')}`);
107
+ }
108
+ catch {
109
+ spinner.stop();
110
+ // Safeguard, should be unreachable
111
+ p.log.message(` - ❌ ${chalk.yellow('could not initialize git. Update git to the latest version!')}`);
112
+ }
113
+ finally {
114
+ spinner.stop();
115
+ }
116
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"install-deps.d.ts","sourceRoot":"","sources":["../../src/lib/install-deps.ts"],"names":[],"mappings":"AA4DA,eAAO,MAAM,mBAAmB,GAAU,gBAAgB;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,kBA6D/E,CAAA"}
1
+ {"version":3,"file":"install-deps.d.ts","sourceRoot":"","sources":["../../src/lib/install-deps.ts"],"names":[],"mappings":"AA4DA,eAAO,MAAM,mBAAmB,GAAU,gBAAgB;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,kBA8D/E,CAAA"}
@@ -1,9 +1,9 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { log } from '@clack/prompts';
4
- import { logger } from './logger.js';
5
- import { detectPackageManager } from './utils.js';
6
- import { execWithSpinner, execWithoutSpinner } from './exec-utils.js';
4
+ import { logger } from '../helpers/logger.js';
5
+ import { detectPackageManager } from '../helpers/utils.js';
6
+ import { execWithSpinner, execWithoutSpinner } from '../helpers/exec-utils.js';
7
7
  const runInstallCommand = async (pkgManager, projectDir) => {
8
8
  switch (pkgManager) {
9
9
  // When using npm, inherit both stdout and stderr so that the progress bar is shown
@@ -52,7 +52,7 @@ const runInstallCommand = async (pkgManager, projectDir) => {
52
52
  }
53
53
  };
54
54
  export const installDependencies = async ({ projectDir }) => {
55
- log.info(chalk.cyan('Installing dependencies...'));
55
+ log.info(chalk.cyan('Installing dependencies'));
56
56
  const pkgManager = detectPackageManager();
57
57
  try {
58
58
  const installSpinner = await runInstallCommand(pkgManager, projectDir);
@@ -60,11 +60,8 @@ export const installDependencies = async ({ projectDir }) => {
60
60
  // If not, create a new spinner for success message
61
61
  if (installSpinner) {
62
62
  installSpinner.stop();
63
- // log.message(chalk.green('Successfully installed dependencies!'))
64
- }
65
- else {
66
- // log.message(chalk.green('Successfully installed dependencies!'))
67
63
  }
64
+ log.message(chalk.green(' - ✓ Successfully installed dependencies'));
68
65
  }
69
66
  catch (error) {
70
67
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -76,7 +73,7 @@ export const installDependencies = async ({ projectDir }) => {
76
73
  // Run pnpm install -g to recreate global node_modules (non-interactive)
77
74
  try {
78
75
  const fixSpinner = ora('Fixing pnpm global store...').start();
79
- await execWithoutSpinner('pnpm', ['install', '-g', '--yes'], {
76
+ await execWithoutSpinner('pnpm', ['install', '-g', '--force'], {
80
77
  stdout: 'ignore',
81
78
  stderr: 'ignore',
82
79
  reject: false, // Don't fail if this doesn't work
@@ -84,20 +81,22 @@ export const installDependencies = async ({ projectDir }) => {
84
81
  CI: 'true', // Make pnpm non-interactive
85
82
  },
86
83
  });
87
- fixSpinner.succeed('Fixed pnpm global store');
84
+ fixSpinner.stop();
85
+ log.message(' - Fixed pnpm global store');
88
86
  // Retry the original install command
89
- log.message('Retrying pnpm install...');
87
+ log.message(' - Retrying pnpm install...');
90
88
  const retrySpinner = await runInstallCommand(pkgManager, projectDir);
91
89
  if (retrySpinner) {
92
- retrySpinner.succeed(chalk.green('Successfully installed dependencies!'));
90
+ retrySpinner.stop();
91
+ log.message(chalk.green(' - ✓ Successfully installed dependencies'));
93
92
  }
94
93
  else {
95
- ora().succeed(chalk.green('Successfully installed dependencies!'));
94
+ log.message(chalk.green(' - ✓ Successfully installed dependencies'));
96
95
  }
97
96
  return;
98
97
  }
99
98
  catch (retryError) {
100
- ora().fail(chalk.red('Failed to fix pnpm path length issue'));
99
+ ora().fail(chalk.red(' - ❌ Failed to fix pnpm path length issue'));
101
100
  logger.error('⚠️ Could not automatically fix the issue.');
102
101
  logger.message('');
103
102
  logger.message('Try one of these solutions:');
@@ -0,0 +1,2 @@
1
+ export declare const validateAppName: (rawInput: string) => "Invalid project name: Project name can only contain letters, numbers, hyphens, and underscores." | undefined;
2
+ //# sourceMappingURL=validate-app-name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-app-name.d.ts","sourceRoot":"","sources":["../../src/lib/validate-app-name.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,kHAQ/C,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { isValidPkgName } from '../helpers/utils.js';
2
+ export const validateAppName = (rawInput) => {
3
+ const valid = isValidPkgName(rawInput);
4
+ if (valid) {
5
+ return;
6
+ }
7
+ else {
8
+ return 'Invalid project name: Project name can only contain letters, numbers, hyphens, and underscores.';
9
+ }
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nextjs-cms",
3
- "version": "0.5.66",
3
+ "version": "0.5.67",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -64,7 +64,7 @@
64
64
  "nanoid": "^5.1.2",
65
65
  "next": "16.1.1",
66
66
  "next-themes": "^0.4.6",
67
- "nextjs-cms": "0.5.66",
67
+ "nextjs-cms": "0.5.67",
68
68
  "plaiceholder": "^3.0.0",
69
69
  "prettier-plugin-tailwindcss": "^0.7.2",
70
70
  "qrcode": "^1.5.4",
@@ -97,6 +97,7 @@
97
97
  "eslint-config-prettier": "^10.0.1",
98
98
  "eslint-plugin-prettier": "^5.2.3",
99
99
  "fs-extra": "^11.3.3",
100
+ "nextjs-cms-kit": "0.5.67",
100
101
  "postcss": "^8.5.1",
101
102
  "prettier": "3.5.0",
102
103
  "raw-loader": "^4.0.2",
@@ -1 +0,0 @@
1
- {"version":3,"file":"exec-utils.d.ts","sourceRoot":"","sources":["../../src/lib/exec-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAA;AAE9B,MAAM,WAAW,sBAAsB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACvD,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,aAAa,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,sBAAsB,KAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAsDzF,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC3B,SAAS,MAAM,EACf,MAAM,MAAM,EAAE,EACd,UAAS;IACL,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IACtC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC1B,KACP,OAAO,CAAC,IAAI,CAUd,CAAA"}
@@ -1,10 +0,0 @@
1
- /**
2
- * Remove a package globally from all package managers (to ensure clean state)
3
- * Silent - spinner shown but no output, no errors, no success messages
4
- */
5
- export declare const removeGlobalPackage: (packageName: string) => Promise<void>;
6
- /**
7
- * Install a package globally using the detected package manager
8
- */
9
- export declare const installGlobalPackage: (packageName: string, version?: string) => Promise<void>;
10
- //# sourceMappingURL=global-package.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"global-package.d.ts","sourceRoot":"","sources":["../../src/lib/global-package.ts"],"names":[],"mappings":"AAyIA;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAU,aAAa,MAAM,KAAG,OAAO,CAAC,IAAI,CAc3E,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAU,aAAa,MAAM,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,IAAI,CAsB9F,CAAA"}
@@ -1,158 +0,0 @@
1
- import chalk from 'chalk';
2
- import ora from 'ora';
3
- import { detectPackageManager } from './utils.js';
4
- import { execWithSpinner, execWithoutSpinner } from './exec-utils.js';
5
- import { log } from '@clack/prompts';
6
- const getGlobalRemoveCommand = (pkgManager, packageName) => {
7
- switch (pkgManager) {
8
- case 'pnpm':
9
- return ['pnpm', ['remove', '-g', packageName]];
10
- case 'npm':
11
- return ['npm', ['uninstall', '-g', packageName]];
12
- case 'yarn':
13
- return ['yarn', ['global', 'remove', packageName]];
14
- case 'bun':
15
- return ['bun', ['remove', '-g', packageName]];
16
- }
17
- };
18
- const getGlobalInstallCommand = (pkgManager, packageName, version) => {
19
- const packageSpec = version ? `${packageName}@${version}` : packageName;
20
- switch (pkgManager) {
21
- case 'pnpm':
22
- return ['pnpm', ['add', '-g', packageSpec]];
23
- case 'npm':
24
- return ['npm', ['install', '-g', packageSpec]];
25
- case 'yarn':
26
- return ['yarn', ['global', 'add', packageSpec]];
27
- case 'bun':
28
- return ['bun', ['add', '-g', packageSpec]];
29
- }
30
- };
31
- const runGlobalRemoveCommand = async (pkgManager, packageName) => {
32
- const [command, args] = getGlobalRemoveCommand(pkgManager, packageName);
33
- // Silent execution with spinner - no output, no errors that quit
34
- let stderrOutput = '';
35
- let spinner = await execWithSpinner({
36
- command,
37
- args,
38
- spinnerText: `Removing ${packageName} globally...`,
39
- stdout: 'ignore',
40
- stderr: 'pipe', // Capture stderr to detect path length errors
41
- onStderrHandle: (data) => {
42
- stderrOutput += data.toString();
43
- },
44
- checkExitCode: false, // Don't throw on errors - we want to silently ignore failures
45
- });
46
- // Check if we got the pnpm path length error
47
- if (pkgManager === 'pnpm' && stderrOutput.toUpperCase().includes('PNPM_VIRTUAL_STORE_DIR_MAX_LENGTH_DIFF')) {
48
- // Stop the current spinner before starting the fix
49
- spinner?.stop();
50
- // Run pnpm install -g to fix the issue (silently)
51
- spinner = await execWithSpinner({
52
- command: 'pnpm',
53
- args: ['install', '-g', '--yes'],
54
- stdout: 'ignore',
55
- stderr: 'ignore',
56
- checkExitCode: false,
57
- });
58
- spinner?.stop();
59
- // Retry the remove command
60
- spinner = await execWithSpinner({
61
- command,
62
- args,
63
- spinnerText: `Removing ${packageName} globally...`,
64
- stdout: 'ignore',
65
- stderr: 'ignore',
66
- checkExitCode: false,
67
- });
68
- }
69
- return spinner;
70
- };
71
- const runGlobalInstallCommand = async (pkgManager, packageName, version) => {
72
- const [command, args] = getGlobalInstallCommand(pkgManager, packageName, version);
73
- switch (pkgManager) {
74
- // When using npm, inherit the stderr stream so that the progress bar is shown
75
- case 'npm':
76
- await execWithoutSpinner(command, args, {
77
- stdout: 'inherit',
78
- stderr: 'inherit',
79
- });
80
- return null;
81
- // When using yarn or pnpm, use the stdout stream and ora spinner to show the progress
82
- case 'pnpm':
83
- return execWithSpinner({
84
- command,
85
- args,
86
- spinnerText: `Installing ${packageName} globally...`,
87
- onDataHandle: (spinner) => (data) => {
88
- const text = data.toString();
89
- if (text.includes('Progress')) {
90
- spinner.text = text.includes('|') ? (text.split(' | ')[1] ?? '') : text;
91
- }
92
- },
93
- });
94
- case 'yarn':
95
- return execWithSpinner({
96
- command,
97
- args,
98
- spinnerText: `Installing ${packageName} globally...`,
99
- onDataHandle: (spinner) => (data) => {
100
- spinner.text = data.toString();
101
- },
102
- });
103
- // When using bun, the stdout stream is ignored and the spinner is shown
104
- case 'bun':
105
- return execWithSpinner({
106
- command,
107
- args,
108
- spinnerText: `Installing ${packageName} globally...`,
109
- stdout: 'ignore',
110
- });
111
- }
112
- };
113
- /**
114
- * Remove a package globally from all package managers (to ensure clean state)
115
- * Silent - spinner shown but no output, no errors, no success messages
116
- */
117
- export const removeGlobalPackage = async (packageName) => {
118
- const allPMs = ['pnpm', 'npm', 'yarn', 'bun'];
119
- for (const pm of allPMs) {
120
- try {
121
- const spinner = await runGlobalRemoveCommand(pm, packageName);
122
- // Stop spinner silently without showing success/fail
123
- if (spinner) {
124
- spinner.stop();
125
- }
126
- }
127
- catch {
128
- // Silently ignore all errors - package might not be installed with this PM
129
- }
130
- }
131
- };
132
- /**
133
- * Install a package globally using the detected package manager
134
- */
135
- export const installGlobalPackage = async (packageName, version) => {
136
- log.info(chalk.cyan(`Installing ${packageName} globally...`));
137
- const pkgManager = detectPackageManager();
138
- try {
139
- const installSpinner = await runGlobalInstallCommand(pkgManager, packageName, version);
140
- // If the spinner was used to show the progress, use succeed method on it
141
- // If not, create a new spinner for success message
142
- if (installSpinner) {
143
- installSpinner.stop();
144
- // log.message(chalk.green(`Successfully installed ${packageName} globally!`))
145
- }
146
- else {
147
- // log.message(chalk.green(`Successfully installed ${packageName} globally!`))
148
- }
149
- }
150
- catch (error) {
151
- // Stop any existing spinner and show failure
152
- ora().fail(chalk.red(`Failed to install ${packageName} globally`));
153
- log.warn(`⚠️ Could not install ${packageName} globally.`);
154
- const [command, args] = getGlobalInstallCommand(pkgManager, packageName, version);
155
- log.message(` - Please run ${chalk.gray(`${command} ${args.join(' ')}`)} manually.`);
156
- throw error;
157
- }
158
- };
@@ -1 +0,0 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM;qBACE,OAAO,EAAE;mBAGX,OAAO,EAAE;kBAGV,OAAO,EAAE;kBAGT,OAAO,EAAE;qBAGN,OAAO,EAAE;CAG7B,CAAA"}
@@ -1,2 +0,0 @@
1
- export declare const renderTitle: () => void;
2
- //# sourceMappingURL=render-title.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"render-title.d.ts","sourceRoot":"","sources":["../../src/lib/render-title.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,WAAW,YASvB,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,UAAU,4jBAgBtB,CAAA;AAED,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,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAA;AAC5D,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAkBtE;AAED,kCAAkC;AAClC,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAczE"}
File without changes
File without changes