create-nextjs-cms 0.5.66 → 0.5.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +77 -0
- package/dist/helpers/check-directory.d.ts +6 -0
- package/dist/helpers/check-directory.d.ts.map +1 -0
- package/dist/helpers/check-directory.js +47 -0
- package/dist/{lib → helpers}/exec-utils.d.ts +1 -0
- package/dist/helpers/exec-utils.d.ts.map +1 -0
- package/dist/{lib → helpers}/exec-utils.js +2 -1
- package/dist/helpers/get-version.d.ts +2 -0
- package/dist/helpers/get-version.d.ts.map +1 -0
- package/dist/helpers/get-version.js +11 -0
- package/dist/helpers/logger.d.ts.map +1 -0
- package/dist/helpers/render-title.d.ts +3 -0
- package/dist/helpers/render-title.d.ts.map +1 -0
- package/dist/{lib → helpers}/render-title.js +10 -0
- package/dist/{lib → helpers}/utils.d.ts +9 -0
- package/dist/helpers/utils.d.ts.map +1 -0
- package/dist/{lib → helpers}/utils.js +42 -0
- package/dist/index.js +49 -235
- package/dist/lib/cms-setup.d.ts +1 -2
- package/dist/lib/cms-setup.d.ts.map +1 -1
- package/dist/lib/cms-setup.js +9 -13
- package/dist/lib/create-project.d.ts +9 -0
- package/dist/lib/create-project.d.ts.map +1 -0
- package/dist/lib/create-project.js +69 -0
- package/dist/lib/git.d.ts +6 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +116 -0
- package/dist/lib/install-deps.d.ts.map +1 -1
- package/dist/lib/install-deps.js +13 -14
- package/dist/lib/validate-app-name.d.ts +2 -0
- package/dist/lib/validate-app-name.d.ts.map +1 -0
- package/dist/lib/validate-app-name.js +10 -0
- package/package.json +1 -1
- package/templates/default/app/(rootLayout)/(plugins)/[...slug]/PluginClient.tsx +55 -0
- package/templates/default/app/(rootLayout)/(plugins)/[...slug]/page.tsx +41 -0
- package/templates/default/app/(rootLayout)/(plugins)/[...slug]/plugin-registry.ts +16 -0
- package/templates/default/app/(rootLayout)/page.tsx +6 -5
- package/templates/default/app/_trpc/client.ts +1 -1
- package/templates/default/app/api/trpc/[trpc]/route.ts +3 -3
- package/templates/default/cms.config.ts +1 -2
- package/templates/default/components/AdminCard.tsx +1 -1
- package/templates/default/components/AdminPrivilegeCard.tsx +1 -1
- package/templates/default/components/CategorySectionSelectInput.tsx +1 -1
- package/templates/default/components/DashboardPage.tsx +1 -1
- package/templates/default/components/EmailCard.tsx +1 -1
- package/templates/default/components/EmailPasswordForm.tsx +1 -1
- package/templates/default/components/EmailQuotaForm.tsx +1 -1
- package/templates/default/components/EmailsPage.tsx +1 -1
- package/templates/default/components/NewAdminForm.tsx +1 -1
- package/templates/default/components/NewEmailForm.tsx +1 -1
- package/templates/default/components/SectionItemCard.tsx +1 -1
- package/templates/default/components/Sidebar.tsx +1 -1
- package/templates/default/components/form/Form.tsx +1 -1
- package/templates/default/components/form/helpers/_section-hot-reload.js +1 -1
- package/templates/default/package.json +2 -1
- package/dist/lib/exec-utils.d.ts.map +0 -1
- package/dist/lib/global-package.d.ts +0 -10
- package/dist/lib/global-package.d.ts.map +0 -1
- package/dist/lib/global-package.js +0 -158
- package/dist/lib/logger.d.ts.map +0 -1
- package/dist/lib/render-title.d.ts +0 -2
- package/dist/lib/render-title.d.ts.map +0 -1
- package/dist/lib/utils.d.ts.map +0 -1
- package/templates/default/app/(rootLayout)/analytics/page.tsx +0 -7
- package/templates/default/app/(rootLayout)/dashboard/page.tsx +0 -7
- package/templates/default/app/(rootLayout)/emails/page.tsx +0 -6
- /package/dist/{lib → helpers}/logger.d.ts +0 -0
- /package/dist/{lib → helpers}/logger.js +0 -0
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|
|
4
|
-
import {
|
|
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 './
|
|
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 {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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: ${
|
|
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);
|
package/dist/lib/cms-setup.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cms-setup.d.ts","sourceRoot":"","sources":["../../src/lib/cms-setup.ts"],"names":[],"mappings":"
|
|
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"}
|