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
package/dist/lib/cms-setup.js
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { logger } from '
|
|
3
|
-
import { execWithoutSpinner } from '
|
|
4
|
-
import
|
|
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',
|
|
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,
|
|
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(
|
|
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"}
|
package/dist/lib/git.js
ADDED
|
@@ -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,
|
|
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"}
|
package/dist/lib/install-deps.js
CHANGED
|
@@ -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 '
|
|
5
|
-
import { detectPackageManager } from '
|
|
6
|
-
import { execWithSpinner, execWithoutSpinner } from '
|
|
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', '--
|
|
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.
|
|
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.
|
|
90
|
+
retrySpinner.stop();
|
|
91
|
+
log.message(chalk.green(' - ✓ Successfully installed dependencies'));
|
|
93
92
|
}
|
|
94
93
|
else {
|
|
95
|
-
|
|
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 @@
|
|
|
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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import dynamic from 'next/dynamic'
|
|
4
|
+
import React, { useMemo } from 'react'
|
|
5
|
+
import { trpc } from '@/app/_trpc/client'
|
|
6
|
+
import { getPluginClientLoader } from './plugin-registry'
|
|
7
|
+
|
|
8
|
+
type PluginClientProps = {
|
|
9
|
+
packageName: string
|
|
10
|
+
component: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const isComponentType = (value: unknown): value is React.ComponentType<any> => {
|
|
14
|
+
if (typeof value === 'function') return true
|
|
15
|
+
if (typeof value === 'object' && value !== null && '$$typeof' in value) return true
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function PluginClient({ packageName, component }: PluginClientProps) {
|
|
20
|
+
const loader = getPluginClientLoader(packageName)
|
|
21
|
+
if (!loader) {
|
|
22
|
+
return <div className='text-destructive p-6 text-sm'>Plugin client package not registered: {packageName}</div>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const Component = useMemo(
|
|
26
|
+
() =>
|
|
27
|
+
dynamic(
|
|
28
|
+
async () => {
|
|
29
|
+
const mod = await loader()
|
|
30
|
+
const resolved =
|
|
31
|
+
(mod.components && mod.components[component]) ||
|
|
32
|
+
(mod as Record<string, React.ComponentType<any>>)[component] ||
|
|
33
|
+
mod.default
|
|
34
|
+
|
|
35
|
+
if (!resolved || !isComponentType(resolved)) {
|
|
36
|
+
return {
|
|
37
|
+
default: () => (
|
|
38
|
+
<div className='text-destructive p-6 text-sm'>
|
|
39
|
+
Plugin component not found: {component}
|
|
40
|
+
</div>
|
|
41
|
+
),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { default: resolved }
|
|
46
|
+
},
|
|
47
|
+
{ ssr: false },
|
|
48
|
+
),
|
|
49
|
+
[component, loader],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return <Component trpc={trpc} />
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default PluginClient
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { notFound, redirect } from 'next/navigation'
|
|
2
|
+
import { api, HydrateClient } from 'nextjs-cms/api/trpc/server'
|
|
3
|
+
import { findPluginRouteByPath } from 'nextjs-cms/plugins/server'
|
|
4
|
+
import PluginClient from './PluginClient'
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic'
|
|
7
|
+
type Params = Promise<{ slug?: string[] }>
|
|
8
|
+
|
|
9
|
+
export default async function Page(props: { params: Params }) {
|
|
10
|
+
const params = await props.params
|
|
11
|
+
const slug = params.slug ?? []
|
|
12
|
+
|
|
13
|
+
if (slug[0] === 'plugin') {
|
|
14
|
+
const rest = slug.slice(1)
|
|
15
|
+
if (rest.length === 0) {
|
|
16
|
+
const routes = await api.plugins.routes()
|
|
17
|
+
const target = routes[0]?.path
|
|
18
|
+
if (target) {
|
|
19
|
+
redirect(target)
|
|
20
|
+
}
|
|
21
|
+
notFound()
|
|
22
|
+
}
|
|
23
|
+
redirect(`/${rest.join('/')}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const path = `/${slug.join('/')}`
|
|
27
|
+
const route = await findPluginRouteByPath(path)
|
|
28
|
+
if (!route) {
|
|
29
|
+
notFound()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (route.prefetch) {
|
|
33
|
+
await route.prefetch({ api })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<HydrateClient>
|
|
38
|
+
<PluginClient packageName={route.pluginId} component={route.component} />
|
|
39
|
+
</HydrateClient>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ComponentType } from 'react'
|
|
2
|
+
|
|
3
|
+
export type PluginClientComponent = ComponentType<any>
|
|
4
|
+
|
|
5
|
+
export type PluginClientModule = {
|
|
6
|
+
components?: Record<string, PluginClientComponent>
|
|
7
|
+
default?: unknown
|
|
8
|
+
[key: string]: unknown
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type PluginClientLoader = () => Promise<PluginClientModule>
|
|
12
|
+
|
|
13
|
+
// Keep this list in sync with cms.config.ts and apps/cms/package.json.
|
|
14
|
+
const pluginClientLoaders: Record<string, PluginClientLoader> = {}
|
|
15
|
+
|
|
16
|
+
export const getPluginClientLoader = (packageName: string) => pluginClientLoaders[packageName]
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import Dashboard from './dashboard/page'
|
|
1
|
+
import { redirect } from 'next/navigation'
|
|
2
|
+
import { getPluginRoutes } from 'nextjs-cms/plugins/server'
|
|
4
3
|
|
|
5
4
|
export const dynamic = 'force-dynamic'
|
|
6
5
|
|
|
7
|
-
export default function Home() {
|
|
8
|
-
|
|
6
|
+
export default async function Home() {
|
|
7
|
+
const routes = await getPluginRoutes()
|
|
8
|
+
const target = routes[0]?.path ?? '/admins'
|
|
9
|
+
redirect(target)
|
|
9
10
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
|
|
2
|
-
import {
|
|
2
|
+
import { createTRPCContext, getAppRouter } from 'nextjs-cms/api'
|
|
3
3
|
import { NextRequest } from 'next/server'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -13,11 +13,11 @@ const context = async (req: NextRequest) => {
|
|
|
13
13
|
})
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const handler = (req: NextRequest) =>
|
|
16
|
+
const handler = async (req: NextRequest) =>
|
|
17
17
|
fetchRequestHandler({
|
|
18
18
|
endpoint: '/api/trpc',
|
|
19
19
|
req,
|
|
20
|
-
router:
|
|
20
|
+
router: await getAppRouter(),
|
|
21
21
|
createContext: () => context(req),
|
|
22
22
|
/*onError(opts) {
|
|
23
23
|
return opts.error
|
|
@@ -2,8 +2,7 @@ import type { CMSConfig } from 'nextjs-cms/core/config'
|
|
|
2
2
|
import process from 'process'
|
|
3
3
|
import { resolve } from 'path'
|
|
4
4
|
|
|
5
|
-
export const config: CMSConfig = {
|
|
6
|
-
ui: {
|
|
5
|
+
export const config: CMSConfig = {
|
|
7
6
|
title: 'NEXT CMS Admin',
|
|
8
7
|
defaultTheme: 'dark',
|
|
9
8
|
logo: '/logo.svg',
|
|
@@ -9,7 +9,7 @@ import { Badge } from '@/components/ui/badge'
|
|
|
9
9
|
import { Button } from '@/components/ui/button'
|
|
10
10
|
import { trpc } from '@/app/_trpc/client'
|
|
11
11
|
import Image from 'next/image'
|
|
12
|
-
import { RouterOutputs } from 'nextjs-cms/api'
|
|
12
|
+
import type { RouterOutputs } from 'nextjs-cms/api'
|
|
13
13
|
|
|
14
14
|
export default function AdminCard({
|
|
15
15
|
admin,
|
|
@@ -5,7 +5,7 @@ import React, { useEffect } from 'react'
|
|
|
5
5
|
import getString from 'nextjs-cms/translations'
|
|
6
6
|
import { Badge } from '@/components/ui/badge'
|
|
7
7
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
|
8
|
-
import { RouterOutputs } from 'nextjs-cms/api'
|
|
8
|
+
import type { RouterOutputs } from 'nextjs-cms/api'
|
|
9
9
|
|
|
10
10
|
export default function AdminRoleCard({
|
|
11
11
|
privilege,
|
|
@@ -7,7 +7,7 @@ import SelectInputButtons from '@/components/SelectInputButtons'
|
|
|
7
7
|
import getString from 'nextjs-cms/translations'
|
|
8
8
|
import { trpc } from '@/app/_trpc/client'
|
|
9
9
|
import { SelectOption } from 'nextjs-cms/core/fields'
|
|
10
|
-
import { RouterOutputs } from 'nextjs-cms/api'
|
|
10
|
+
import type { RouterOutputs } from 'nextjs-cms/api'
|
|
11
11
|
import { nanoid } from 'nanoid'
|
|
12
12
|
|
|
13
13
|
type CategorySelect = RouterOutputs['categorySections']['get']['data']
|
|
@@ -11,7 +11,7 @@ import { trpc } from '@/app/_trpc/client'
|
|
|
11
11
|
ChartJS.register(ArcElement, Tooltip, Legend)
|
|
12
12
|
|
|
13
13
|
const DashboardPage = () => {
|
|
14
|
-
const { isLoading, isError, data, error } = trpc.
|
|
14
|
+
const { isLoading, isError, data, error } = trpc.cpanelDashboard.getData.useQuery()
|
|
15
15
|
|
|
16
16
|
useEffect(() => {}, [])
|
|
17
17
|
|
|
@@ -14,7 +14,7 @@ export default function EmailCard({ email, action }: { email: EmailItem; action:
|
|
|
14
14
|
const { setModal, modal, modalResponse, setModalResponse } = useModal()
|
|
15
15
|
const { toast } = useToast()
|
|
16
16
|
|
|
17
|
-
const deleteMutation = trpc.
|
|
17
|
+
const deleteMutation = trpc.cpanelEmails.deleteEmail.useMutation({
|
|
18
18
|
onError: (error) => {
|
|
19
19
|
setModalResponse({
|
|
20
20
|
message: error.message,
|
|
@@ -9,7 +9,7 @@ export default function EmailPasswordForm({ email, action }: { email: string; ac
|
|
|
9
9
|
const { setModal, modal, modalResponse, setModalResponse } = useModal()
|
|
10
10
|
|
|
11
11
|
const { toast } = useToast()
|
|
12
|
-
const quotaMutation = trpc.
|
|
12
|
+
const quotaMutation = trpc.cpanelEmails.passwordChange.useMutation({
|
|
13
13
|
onError: (error) => {
|
|
14
14
|
setModal({
|
|
15
15
|
title: getString('error'),
|
|
@@ -10,7 +10,7 @@ export default function EmailQuotaForm({ email, action }: { email: string; actio
|
|
|
10
10
|
const { setModal, modal, modalResponse, setModalResponse } = useModal()
|
|
11
11
|
const { toast } = useToast()
|
|
12
12
|
|
|
13
|
-
const quotaMutation = trpc.
|
|
13
|
+
const quotaMutation = trpc.cpanelEmails.quotaChange.useMutation({
|
|
14
14
|
onError: (error) => {
|
|
15
15
|
setModal({
|
|
16
16
|
title: getString('error'),
|
|
@@ -11,7 +11,7 @@ import { trpc } from '@/app/_trpc/client'
|
|
|
11
11
|
const EmailsPage = () => {
|
|
12
12
|
const controller = new AbortController()
|
|
13
13
|
|
|
14
|
-
const { isLoading, isError, data, error, refetch } = trpc.
|
|
14
|
+
const { isLoading, isError, data, error, refetch } = trpc.cpanelEmails.getEmails.useQuery()
|
|
15
15
|
|
|
16
16
|
useEffect(() => {
|
|
17
17
|
return () => {
|
|
@@ -6,7 +6,7 @@ import { useToast } from '@/components/ui/use-toast'
|
|
|
6
6
|
import { trpc } from '@/app/_trpc/client'
|
|
7
7
|
import AdminRoleCard from '@/components/AdminPrivilegeCard'
|
|
8
8
|
import { Button } from '@/components/ui/button'
|
|
9
|
-
import { RouterOutputs } from 'nextjs-cms/api'
|
|
9
|
+
import type { RouterOutputs } from 'nextjs-cms/api'
|
|
10
10
|
|
|
11
11
|
export default function NewAdminForm({
|
|
12
12
|
privileges,
|
|
@@ -10,7 +10,7 @@ export default function NewEmailForm({ action }: { action: any }) {
|
|
|
10
10
|
const { setModal, modal, modalResponse, setModalResponse } = useModal()
|
|
11
11
|
const formRef = React.useRef<HTMLFormElement>(null)
|
|
12
12
|
const { toast } = useToast()
|
|
13
|
-
const newEmailMutation = trpc.
|
|
13
|
+
const newEmailMutation = trpc.cpanelEmails.createEmail.useMutation({
|
|
14
14
|
onError: (error) => {
|
|
15
15
|
setModal({
|
|
16
16
|
title: getString('error'),
|
|
@@ -9,7 +9,7 @@ import { CardTitle, CardHeader, CardFooter, Card, CardContent } from '@/componen
|
|
|
9
9
|
import { Button } from '@/components/ui/button'
|
|
10
10
|
import { PersonIcon } from '@radix-ui/react-icons'
|
|
11
11
|
import { trpc } from '@/app/_trpc/client'
|
|
12
|
-
import { RouterOutputs } from 'nextjs-cms/api'
|
|
12
|
+
import type { RouterOutputs } from 'nextjs-cms/api'
|
|
13
13
|
|
|
14
14
|
// Used to get elements of array types as types
|
|
15
15
|
type ArrElement<ArrType> = ArrType extends readonly (infer ElementType)[] ? ElementType : never
|