dynamic-zone 1.0.6 → 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,122 +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
+ `);
39
+ process.exit(0);
40
+ }
41
+ if (args.includes("--version") || args.includes("-v")) {
42
+ const pkg = require(path.join(__dirname, "package.json"));
43
+ console.log(pkg.version);
11
44
  process.exit(0);
12
45
  }
13
46
 
14
- // --- 1️⃣ Check if package.json exists ---
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
+ }
52
+
15
53
  const packageJsonPath = path.join(basePath, "package.json");
16
54
  if (!fs.existsSync(packageJsonPath)) {
17
- console.log("❌ package.json not found. Are you in a Node.js project?");
55
+ console.log(
56
+ `${c.red}✖${c.reset} package.json not found. Run this from a Node.js project root.`
57
+ );
18
58
  process.exit(1);
19
59
  }
20
60
 
21
- // --- 2️⃣ Read package.json and check for next ---
22
- const packageJson = require(packageJsonPath);
23
- const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
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
+ }
24
68
 
69
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
25
70
  if (!deps.next) {
26
71
  console.log(
27
- "❌ This is not a Next.js project. dynamic-zone can only be used in Next.js."
72
+ `${c.red}✖${c.reset} Not a Next.js project. dynamic-zone requires Next.js.`
28
73
  );
29
74
  process.exit(1);
30
75
  }
31
76
 
32
- // --- 3️⃣ Check if app folder exists ---
33
77
  const appPath = path.join(basePath, "app");
34
78
  if (!fs.existsSync(appPath)) {
35
- console.log("⚠️ No 'app' folder found. Please create an 'app' folder first.");
79
+ console.log(
80
+ `${c.yellow}⚠${c.reset} No \`app\` directory found. Create it first (e.g. \`npx create-next-app@latest\`).`
81
+ );
36
82
  process.exit(1);
37
83
  }
38
84
 
39
- // Paths for folders
85
+ // ─── Paths ───────────────────────────────────────────────────────────────────
40
86
  const cmsPath = path.join(appPath, "cms");
41
87
  const componentsPath = path.join(cmsPath, "components");
42
88
  const dynamicZonePath = path.join(cmsPath, "dynamic-zone");
43
89
 
44
- // Create folders
45
- [cmsPath, componentsPath, dynamicZonePath].forEach((dir) => {
46
- if (!fs.existsSync(dir)) {
47
- fs.mkdirSync(dir);
48
- console.log("✅ Created:", dir);
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}`);
49
111
  }
50
- });
51
-
52
- // Section Renderer file content
53
- const fileContent = `import dynamic from "next/dynamic";
54
- import { Suspense } from "react";
55
-
56
- type SectionContentType = {
57
- sections: { __component: string }[];
58
- };
59
- type SectionType = NonNullable<SectionContentType["sections"][number]>;
60
-
61
- interface Props {
62
- sections?: unknown;
63
- slug?: string;
64
112
  }
65
113
 
66
- type ComponentMapType = React.ComponentType<{
67
- index?: number;
68
- content?: unknown;
69
- }>;
70
-
71
- const dynamicImport = (path: string) =>
72
- dynamic(() => import(\`../components/\${path}\`), {
73
- loading: () => <div>Loading...</div>,
74
- });
75
-
76
- const componentMap: { [key: string]: ComponentMapType } = {};
77
-
78
- export default function SectionRenderer({ sections, slug }: Props) {
79
- if (!sections) return null;
80
-
81
- if (Array.isArray(sections) && sections.length === 0) {
82
- return (
83
- // This is the div for the sections that are not found DO W-FULL PX
84
- <div>No Sections Found for {slug}</div>
85
- );
86
- }
114
+ // ─── Section renderer template ───────────────────────────────────────────────
115
+ const templatePath = path.join(__dirname, "templates", "section-renderer.template");
116
+ const sectionRendererContent = fs.readFileSync(templatePath, "utf8");
87
117
 
88
- const renderSection = (section: SectionType, index: number) => {
89
- const sectionType = section.__component?.split(".")[1]?.replace(/-/g, "_");
90
- const Component = componentMap[sectionType];
91
-
92
- if (!Component) {
93
- return (
94
- // This is the div for the section that is not found DO W-FULL
95
- <div key={index}>No Section Found {sectionType}</div>
96
- );
97
- }
98
-
99
- return (
100
- <div key={index}>
101
- <Suspense fallback={<div>Loading...</div>}>
102
- <Component index={index} content={section} />
103
- </Suspense>
104
- </div>
105
- );
106
- };
107
-
108
- const sectionsArray = Array.isArray(sections) ? sections : [];
109
- return (
110
- // This is the root div for the dynamic zone DO FLEX FLEX-COL
111
- <div>{sectionsArray.map(renderSection)}</div>
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`
112
124
  );
113
- }
114
-
115
- `;
116
-
117
- // Write file
118
- const filePath = path.join(dynamicZonePath, "section-renderer.tsx");
119
- if (!fs.existsSync(filePath)) {
120
- fs.writeFileSync(filePath, fileContent);
121
- console.log("📄 Created file:", filePath);
122
125
  } else {
123
- 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
+ );
124
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.6",
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
+ }