dynamic-zone 1.0.5 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,78 @@
1
- # Dynamic-zone package
1
+ # dynamic-zone
2
+
3
+ Scaffold a CMS dynamic zone for Next.js apps. Zero dependencies, zero config.
4
+
5
+ ## Requirements
6
+
7
+ - Next.js (App Router)
8
+ - An `app` directory in your project
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install dynamic-zone
14
+ # or
15
+ pnpm add dynamic-zone
16
+ # or
17
+ yarn add dynamic-zone
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ Run from your Next.js project root:
23
+
24
+ ```bash
25
+ npx dynamic-zone
26
+ ```
27
+
28
+ Or if installed globally:
29
+
30
+ ```bash
31
+ dynamic-zone
32
+ ```
33
+
34
+ ## What It Creates
35
+
36
+ ```
37
+ app/
38
+ └── cms/
39
+ ├── components/ # Add your section components here
40
+ └── dynamic-zone/
41
+ └── section-renderer.tsx
42
+ ```
43
+
44
+ ## Next Steps
45
+
46
+ 1. **Add section components** to `app/cms/components/` (e.g. `hero.tsx`, `features.tsx`).
47
+
48
+ 2. **Register them** in the `componentMap` in `section-renderer.tsx`:
49
+
50
+ ```tsx
51
+ const componentMap: { [key: string]: ComponentMapType } = {
52
+ hero: dynamicImport("hero"),
53
+ features: dynamicImport("features"),
54
+ };
55
+ ```
56
+
57
+ 3. **Use the renderer** in your pages:
58
+
59
+ ```tsx
60
+ import SectionRenderer from "@/app/cms/dynamic-zone/section-renderer";
61
+
62
+ export default function Page({ page }) {
63
+ return <SectionRenderer sections={page.sections} slug={page.slug} />;
64
+ }
65
+ ```
66
+
67
+ The `__component` field from your CMS (e.g. `sections.hero`) is parsed to match the component key (`hero` → `hero`).
68
+
69
+ ## CLI Options
70
+
71
+ | Flag | Description |
72
+ | --------------- | ------------ |
73
+ | `-h, --help` | Show help |
74
+ | `-v, --version` | Show version |
75
+
76
+ ## License
77
+
78
+ ISC
package/index.js CHANGED
@@ -3,105 +3,131 @@
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
5
 
6
+ // ─── ANSI escape codes (no external deps) ─────────────────────────────────────
7
+ const c = {
8
+ reset: "\x1b[0m",
9
+ dim: "\x1b[2m",
10
+ bold: "\x1b[1m",
11
+ green: "\x1b[32m",
12
+ red: "\x1b[31m",
13
+ yellow: "\x1b[33m",
14
+ cyan: "\x1b[36m",
15
+ blue: "\x1b[34m",
16
+ };
17
+
6
18
  const basePath = process.cwd();
7
19
 
8
- // Prevent creating inside node_modules
9
- if (basePath.includes("node_modules")) {
10
- console.log(" Do not run inside node_modules");
20
+ // ─── CLI flags ───────────────────────────────────────────────────────────────
21
+ const args = process.argv.slice(2);
22
+ if (args.includes("--help") || args.includes("-h")) {
23
+ console.log(`
24
+ ${c.bold}dynamic-zone${c.reset} — Scaffold CMS dynamic zone for Next.js
25
+
26
+ ${c.dim}Usage:${c.reset}
27
+ dynamic-zone
28
+ npx dynamic-zone
29
+
30
+ ${c.dim}Options:${c.reset}
31
+ -h, --help Show this help
32
+ -v, --version Show version
33
+
34
+ ${c.dim}Creates:${c.reset}
35
+ app/cms/
36
+ app/cms/components/
37
+ app/cms/dynamic-zone/section-renderer.tsx
38
+ `);
11
39
  process.exit(0);
12
40
  }
13
-
14
- // Ensure src folder exists
15
- const srcPath = path.join(basePath, "app");
16
- if (!fs.existsSync(srcPath)) {
17
- console.log("⚠️ No app folder found. Please create a 'app' folder first.");
41
+ if (args.includes("--version") || args.includes("-v")) {
42
+ const pkg = require(path.join(__dirname, "package.json"));
43
+ console.log(pkg.version);
18
44
  process.exit(0);
19
45
  }
20
46
 
21
- // Paths for folders
22
- const cmsPath = path.join(srcPath, "cms");
23
- const componentsPath = path.join(cmsPath, "components");
24
- const dynamicZonePath = path.join(cmsPath, "dynamic-zone");
25
-
26
- // Create folders
27
- [cmsPath, componentsPath, dynamicZonePath].forEach((dir) => {
28
- if (!fs.existsSync(dir)) {
29
- fs.mkdirSync(dir);
30
- console.log("✅ Created:", dir);
31
- }
32
- });
33
-
34
- // Section Renderer file content
35
- const fileContent = `import dynamic from "next/dynamic";
36
- import { Suspense } from "react";
37
-
38
- type SectionContentType = {
39
- sections: { __component: string }[];
40
- };
41
- type SectionType = NonNullable<SectionContentType["sections"][number]>;
47
+ // ─── Guards ─────────────────────────────────────────────────────────────────
48
+ if (basePath.includes("node_modules")) {
49
+ console.log(`${c.red}✖${c.reset} Do not run inside node_modules`);
50
+ process.exit(1);
51
+ }
42
52
 
43
- interface Props {
44
- sections?: unknown;
45
- slug?: string;
53
+ const packageJsonPath = path.join(basePath, "package.json");
54
+ if (!fs.existsSync(packageJsonPath)) {
55
+ console.log(
56
+ `${c.red}✖${c.reset} package.json not found. Run this from a Node.js project root.`
57
+ );
58
+ process.exit(1);
46
59
  }
47
60
 
48
- type ComponentMapType = React.ComponentType<{
49
- index?: number;
50
- content?: unknown;
51
- }>;
61
+ let packageJson;
62
+ try {
63
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
64
+ } catch {
65
+ console.log(`${c.red}✖${c.reset} Invalid package.json`);
66
+ process.exit(1);
67
+ }
52
68
 
53
- const dynamicImport = (path: string) =>
54
- dynamic(() => import(\`../components/\${path}\`), {
55
- loading: () => <div>Loading...</div>,
56
- });
69
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
70
+ if (!deps.next) {
71
+ console.log(
72
+ `${c.red}✖${c.reset} Not a Next.js project. dynamic-zone requires Next.js.`
73
+ );
74
+ process.exit(1);
75
+ }
57
76
 
58
- const componentMap: { [key: string]: ComponentMapType } = {};
77
+ const appPath = path.join(basePath, "app");
78
+ if (!fs.existsSync(appPath)) {
79
+ console.log(
80
+ `${c.yellow}⚠${c.reset} No \`app\` directory found. Create it first (e.g. \`npx create-next-app@latest\`).`
81
+ );
82
+ process.exit(1);
83
+ }
59
84
 
60
- export default function SectionRenderer({ sections, slug }: Props) {
61
- if (!sections) return null;
85
+ // ─── Paths ───────────────────────────────────────────────────────────────────
86
+ const cmsPath = path.join(appPath, "cms");
87
+ const componentsPath = path.join(cmsPath, "components");
88
+ const dynamicZonePath = path.join(cmsPath, "dynamic-zone");
62
89
 
63
- if (Array.isArray(sections) && sections.length === 0) {
64
- return (
65
- <div className="w-full px-2 md:px-4 py-4">
66
- No Sections Found for {slug}
67
- </div>
68
- );
90
+ // ─── Banner ──────────────────────────────────────────────────────────────────
91
+ console.log(`
92
+ ${c.cyan}${c.bold} dynamic-zone${c.reset} ${c.dim}v${
93
+ require(path.join(__dirname, "package.json")).version
94
+ }${c.reset}
95
+ ${c.dim} Scaffolding CMS dynamic zone for Next.js${c.reset}
96
+ `);
97
+
98
+ // ─── Create directories ─────────────────────────────────────────────────────
99
+ const dirs = [
100
+ [cmsPath, "app/cms"],
101
+ [componentsPath, "app/cms/components"],
102
+ [dynamicZonePath, "app/cms/dynamic-zone"],
103
+ ];
104
+
105
+ for (const [fullPath, label] of dirs) {
106
+ if (!fs.existsSync(fullPath)) {
107
+ fs.mkdirSync(fullPath, { recursive: true });
108
+ console.log(` ${c.green}✓${c.reset} ${label}`);
109
+ } else {
110
+ console.log(` ${c.dim}○${c.reset} ${label} ${c.dim}(exists)${c.reset}`);
69
111
  }
70
-
71
- const renderSection = (section: SectionType, index: number) => {
72
- const sectionType = section.__component?.split(".")[1]?.replace(/-/g, "_");
73
- const Component = componentMap[sectionType];
74
-
75
- if (!Component) {
76
- return (
77
- <div className="w-full" key={index}>
78
- No Section Found {sectionType}
79
- </div>
80
- );
81
- }
82
-
83
- return (
84
- <div key={index}>
85
- <Suspense fallback={<div>Loading...</div>}>
86
- <Component index={index} content={section} />
87
- </Suspense>
88
- </div>
89
- );
90
- };
91
-
92
- return (
93
- <div className="flex flex-col">
94
- {(sections || []).map(renderSection)}
95
- </div>
96
- );
97
112
  }
98
- `;
99
113
 
100
- // Write file
101
- const filePath = path.join(dynamicZonePath, "section-renderer.tsx");
102
- if (!fs.existsSync(filePath)) {
103
- fs.writeFileSync(filePath, fileContent);
104
- console.log("📄 Created file:", filePath);
114
+ // ─── Section renderer template ───────────────────────────────────────────────
115
+ const templatePath = path.join(__dirname, "templates", "section-renderer.template");
116
+ const sectionRendererContent = fs.readFileSync(templatePath, "utf8");
117
+
118
+ // ─── Write section-renderer.tsx ──────────────────────────────────────────────
119
+ const sectionRendererPath = path.join(dynamicZonePath, "section-renderer.tsx");
120
+ if (!fs.existsSync(sectionRendererPath)) {
121
+ fs.writeFileSync(sectionRendererPath, sectionRendererContent);
122
+ console.log(
123
+ ` ${c.green}✓${c.reset} app/cms/dynamic-zone/section-renderer.tsx`
124
+ );
105
125
  } else {
106
- console.log("⚠️ File already exists:", filePath);
126
+ console.log(
127
+ ` ${c.dim}○${c.reset} app/cms/dynamic-zone/section-renderer.tsx ${c.dim}(exists)${c.reset}`
128
+ );
107
129
  }
130
+
131
+ console.log(`
132
+ ${c.green}${c.bold}Done.${c.reset} ${c.dim}Add your section components to app/cms/components/ and register them in the componentMap.${c.reset}
133
+ `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynamic-zone",
3
- "version": "1.0.5",
3
+ "version": "1.0.8",
4
4
  "description": "Dynamic zone scaffolding for Next.js apps",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -0,0 +1,52 @@
1
+ import dynamic from "next/dynamic";
2
+ import { Suspense } from "react";
3
+
4
+ type SectionContentType = {
5
+ sections: { __component: string }[];
6
+ };
7
+ type SectionType = NonNullable<SectionContentType["sections"][number]>;
8
+
9
+ interface Props {
10
+ sections?: unknown;
11
+ slug?: string;
12
+ }
13
+
14
+ type ComponentMapType = React.ComponentType<{
15
+ index?: number;
16
+ content?: unknown;
17
+ }>;
18
+
19
+ const dynamicImport = (path: string) =>
20
+ dynamic(() => import(`../components/${path}`), {
21
+ loading: () => <div>Loading...</div>,
22
+ });
23
+
24
+ const componentMap: { [key: string]: ComponentMapType } = {};
25
+
26
+ export default function SectionRenderer({ sections, slug }: Props) {
27
+ if (!sections) return null;
28
+
29
+ if (Array.isArray(sections) && sections.length === 0) {
30
+ return <div>No Sections Found for {slug}</div>;
31
+ }
32
+
33
+ const renderSection = (section: SectionType, index: number) => {
34
+ const sectionType = section.__component?.split(".")[1]?.replace(/-/g, "_");
35
+ const Component = componentMap[sectionType];
36
+
37
+ if (!Component) {
38
+ return <div key={index}>No Section Found {sectionType}</div>;
39
+ }
40
+
41
+ return (
42
+ <div key={index}>
43
+ <Suspense fallback={<div>Loading...</div>}>
44
+ <Component index={index} content={section} />
45
+ </Suspense>
46
+ </div>
47
+ );
48
+ };
49
+
50
+ const sectionsArray = Array.isArray(sections) ? sections : [];
51
+ return <div>{sectionsArray.map(renderSection)}</div>;
52
+ }