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.
Files changed (70) 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/app/(rootLayout)/(plugins)/[...slug]/PluginClient.tsx +55 -0
  37. package/templates/default/app/(rootLayout)/(plugins)/[...slug]/page.tsx +41 -0
  38. package/templates/default/app/(rootLayout)/(plugins)/[...slug]/plugin-registry.ts +16 -0
  39. package/templates/default/app/(rootLayout)/page.tsx +6 -5
  40. package/templates/default/app/_trpc/client.ts +1 -1
  41. package/templates/default/app/api/trpc/[trpc]/route.ts +3 -3
  42. package/templates/default/cms.config.ts +1 -2
  43. package/templates/default/components/AdminCard.tsx +1 -1
  44. package/templates/default/components/AdminPrivilegeCard.tsx +1 -1
  45. package/templates/default/components/CategorySectionSelectInput.tsx +1 -1
  46. package/templates/default/components/DashboardPage.tsx +1 -1
  47. package/templates/default/components/EmailCard.tsx +1 -1
  48. package/templates/default/components/EmailPasswordForm.tsx +1 -1
  49. package/templates/default/components/EmailQuotaForm.tsx +1 -1
  50. package/templates/default/components/EmailsPage.tsx +1 -1
  51. package/templates/default/components/NewAdminForm.tsx +1 -1
  52. package/templates/default/components/NewEmailForm.tsx +1 -1
  53. package/templates/default/components/SectionItemCard.tsx +1 -1
  54. package/templates/default/components/Sidebar.tsx +1 -1
  55. package/templates/default/components/form/Form.tsx +1 -1
  56. package/templates/default/components/form/helpers/_section-hot-reload.js +1 -1
  57. package/templates/default/package.json +2 -1
  58. package/dist/lib/exec-utils.d.ts.map +0 -1
  59. package/dist/lib/global-package.d.ts +0 -10
  60. package/dist/lib/global-package.d.ts.map +0 -1
  61. package/dist/lib/global-package.js +0 -158
  62. package/dist/lib/logger.d.ts.map +0 -1
  63. package/dist/lib/render-title.d.ts +0 -2
  64. package/dist/lib/render-title.d.ts.map +0 -1
  65. package/dist/lib/utils.d.ts.map +0 -1
  66. package/templates/default/app/(rootLayout)/analytics/page.tsx +0 -7
  67. package/templates/default/app/(rootLayout)/dashboard/page.tsx +0 -7
  68. package/templates/default/app/(rootLayout)/emails/page.tsx +0 -6
  69. /package/dist/{lib → helpers}/logger.d.ts +0 -0
  70. /package/dist/{lib → helpers}/logger.js +0 -0
@@ -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.68",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 React from 'react'
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
- return <Dashboard />
6
+ export default async function Home() {
7
+ const routes = await getPluginRoutes()
8
+ const target = routes[0]?.path ?? '/admins'
9
+ redirect(target)
9
10
  }
@@ -1,4 +1,4 @@
1
1
  import type { AppRouter } from 'nextjs-cms/api'
2
- import { createTRPCReact} from '@trpc/react-query'
2
+ import { createTRPCReact } from '@trpc/react-query'
3
3
 
4
4
  export const trpc = createTRPCReact<AppRouter>()
@@ -1,5 +1,5 @@
1
1
  import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
2
- import { appRouter, createTRPCContext } from 'nextjs-cms/api'
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: appRouter,
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.cpanel.getData.useQuery()
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.cpanel.deleteEmail.useMutation({
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.cpanel.passwordChange.useMutation({
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.cpanel.quotaChange.useMutation({
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.cpanel.getEmails.useQuery()
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.cpanel.createEmail.useMutation({
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