@deneb-ui/create-template 2.0.0

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 ADDED
@@ -0,0 +1,65 @@
1
+ # @fivora/create-template
2
+
3
+ Scaffold a modern, pre-validated **Fivora** template in seconds (Vite-style initializer).
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@fivora/create-template.svg)](https://www.npmjs.com/package/@fivora/create-template)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ---
9
+
10
+ ## Quick Start
11
+
12
+ Create a new Fivora template with a single command:
13
+
14
+ ```bash
15
+ # Using npm
16
+ npm create @fivora/template my-store
17
+
18
+ # Or using npx
19
+ npx @fivora/create-template my-store
20
+
21
+ # Or yarn / pnpm
22
+ yarn create @fivora/template my-store
23
+ pnpm create @fivora/template my-store
24
+ ```
25
+
26
+ Then start developing:
27
+
28
+ ```bash
29
+ cd my-store
30
+ npm install
31
+ npm run dev # Start Next.js local development
32
+ npm run lab # Test in Fivora Visual Editing Lab
33
+ npm run validate # Run Fivora preflight compliance checks
34
+ npm run zip # Create upload-ready clean ZIP
35
+ ```
36
+
37
+ ---
38
+
39
+ ## What is Included?
40
+
41
+ Each generated template comes pre-configured with:
42
+ - ⚡ **Next.js App Router**: Modern React 19 / Next.js with static HTML export support
43
+ - 🎨 **`@fivora/editable-components`**: Pre-configured visual editing primitives and theme palettes
44
+ - 🛠️ **`@fivora/cli`**: Built-in developer tools (`lab`, `validate`, `zip`)
45
+ - 📋 **`fivora-template.json`**: Strict Version 2 template manifest
46
+ - 📦 **`src/data/site-data.json`**: Merchant defaults and editable content structure
47
+
48
+ ---
49
+
50
+ ## Command Options
51
+
52
+ ```bash
53
+ npx @fivora/create-template <project-name> [options]
54
+ ```
55
+
56
+ | Option | Description |
57
+ | :--- | :--- |
58
+ | `<project-name>` | Target directory name (e.g. `my-fashion-store`) |
59
+ | `--skip-install` | Do not run `npm install` automatically after scaffolding |
60
+
61
+ ---
62
+
63
+ ## License
64
+
65
+ MIT © [Fivora](https://fivora.com)
package/bin/index.js ADDED
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require('node:path');
4
+ const fs = require('node:fs');
5
+ const readline = require('node:readline');
6
+
7
+ const args = process.argv.slice(2);
8
+ let targetDirInput = args[0];
9
+
10
+ function printBanner() {
11
+ console.log(`
12
+ \x1b[36m╔═══════════════════════════════════════════════════════╗
13
+ ║ \x1b[1mDENEB TEMPLATE CREATOR\x1b[0m\x1b[36m ║
14
+ ║ \x1b[90mScaffold a fast, compliant commerce template\x1b[0m\x1b[36m ║
15
+ ║ \x1b[90mCreated by Chamika Gayashan (CEE G)\x1b[0m\x1b[36m ║
16
+ ╚═══════════════════════════════════════════════════════╝\x1b[0m
17
+ `);
18
+ }
19
+
20
+ function copyFolderSync(src, dest) {
21
+ if (!fs.existsSync(dest)) {
22
+ fs.mkdirSync(dest, { recursive: true });
23
+ }
24
+
25
+ const entries = fs.readdirSync(src, { withFileTypes: true });
26
+ for (const entry of entries) {
27
+ const srcPath = path.join(src, entry.name);
28
+ const destPath = path.join(dest, entry.name);
29
+
30
+ if (entry.isDirectory()) {
31
+ copyFolderSync(srcPath, destPath);
32
+ } else if (entry.isFile()) {
33
+ fs.copyFileSync(srcPath, destPath);
34
+ }
35
+ }
36
+ }
37
+
38
+ function runScaffolding(targetInput) {
39
+ const targetPath = path.resolve(process.cwd(), targetInput.trim());
40
+ const folderName = path.basename(targetPath);
41
+ const sanitizedPkgName = folderName.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
42
+ const templateDir = path.resolve(__dirname, '..', 'template');
43
+
44
+ if (!fs.existsSync(templateDir)) {
45
+ console.error(`\n\x1b[31mError:\x1b[0m Template directory not found at ${templateDir}`);
46
+ process.exit(1);
47
+ }
48
+
49
+ if (fs.existsSync(targetPath) && fs.readdirSync(targetPath).length > 0) {
50
+ console.error(`\n\x1b[31mError:\x1b[0m Target folder "${folderName}" already exists and is not empty.\n`);
51
+ process.exit(1);
52
+ }
53
+
54
+ console.log(`\n\x1b[32mCreating DENEB template in:\x1b[0m ${targetPath}...\n`);
55
+ copyFolderSync(templateDir, targetPath);
56
+
57
+ // Restore .gitignore if preserved as _gitignore
58
+ const gitignorePath = path.join(targetPath, '_gitignore');
59
+ if (fs.existsSync(gitignorePath)) {
60
+ fs.renameSync(gitignorePath, path.join(targetPath, '.gitignore'));
61
+ }
62
+
63
+ // Update package.json name
64
+ const pkgPath = path.join(targetPath, 'package.json');
65
+ if (fs.existsSync(pkgPath)) {
66
+ try {
67
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
68
+ pkg.name = sanitizedPkgName;
69
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
70
+ } catch {
71
+ // ignore
72
+ }
73
+ }
74
+
75
+ console.log(`\x1b[32m✔ Template structure created!\x1b[0m`);
76
+ console.log(` \x1b[90m- Pre-configured @deneb-ui/ui (Visual editing & smart template components)\x1b[0m`);
77
+ console.log(` \x1b[90m- Pre-configured @deneb-ui/cli (lab, validate, zip tools)\x1b[0m\n`);
78
+
79
+ const skipInstall = process.argv.includes('--skip-install');
80
+
81
+ if (!skipInstall) {
82
+ console.log(`📦 Installing dependencies with npm (this may take a minute)...`);
83
+ try {
84
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
85
+ const installRes = require('node:child_process').spawnSync(npmCmd, ['install'], {
86
+ cwd: targetPath,
87
+ stdio: 'inherit',
88
+ });
89
+
90
+ if (installRes.status === 0) {
91
+ console.log(`\n\x1b[32m✔ All dependencies and DENEB UI packages installed successfully!\x1b[0m\n`);
92
+ } else {
93
+ console.log(`\n\x1b[33mNote: npm install exited with code ${installRes.status}. You can run 'npm install' inside the folder manually.\x1b[0m\n`);
94
+ }
95
+ } catch {
96
+ console.log(`\n\x1b[33mNote: Could not run npm install automatically. Run 'npm install' inside the folder.\x1b[0m\n`);
97
+ }
98
+ }
99
+
100
+ console.log(`Ready to develop! Run:`);
101
+ console.log(` \x1b[36mcd ${folderName}\x1b[0m`);
102
+ if (skipInstall) {
103
+ console.log(` \x1b[36mnpm install\x1b[0m`);
104
+ }
105
+ console.log(` \x1b[36mnpm run dev\x1b[0m \x1b[90m# Start local Next.js development\x1b[0m`);
106
+ console.log(` \x1b[36mnpm run lab\x1b[0m \x1b[90m# Start Visual Editing Lab\x1b[0m`);
107
+ console.log(` \x1b[36mnpm run validate\x1b[0m \x1b[90m# Verify contract compliance\x1b[0m`);
108
+ console.log(` \x1b[36mnpm run zip\x1b[0m \x1b[90m# Package clean ZIP for 1-click upload\x1b[0m\n`);
109
+ console.log(`Happy coding with DENEB UI! 🚀\n`);
110
+ }
111
+
112
+ printBanner();
113
+
114
+ if (targetDirInput) {
115
+ runScaffolding(targetDirInput);
116
+ } else {
117
+ const rl = readline.createInterface({
118
+ input: process.stdin,
119
+ output: process.stdout,
120
+ });
121
+
122
+ rl.question('Project name (e.g. my-deneb-store): ', (answer) => {
123
+ rl.close();
124
+ const name = answer.trim() || 'deneb-template';
125
+ runScaffolding(name);
126
+ });
127
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@deneb-ui/create-template",
3
+ "version": "2.0.0",
4
+ "description": "Scaffold a modern, pre-validated DENEB UI storefront template in seconds. Created by Chamika Gayashan (CEE G).",
5
+ "bin": {
6
+ "create-deneb-template": "bin/index.js",
7
+ "create-deneb": "bin/index.js",
8
+ "create-fivora-template": "bin/index.js",
9
+ "create-fivora": "bin/index.js",
10
+ "create-template": "bin/index.js"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "files": [
16
+ "bin",
17
+ "template",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "sync-template": "node ./scripts/sync-template.js",
22
+ "prepublishOnly": "npm run sync-template"
23
+ },
24
+ "keywords": [
25
+ "deneb",
26
+ "deneb-ui",
27
+ "fivora",
28
+ "create",
29
+ "template",
30
+ "scaffold",
31
+ "nextjs",
32
+ "react"
33
+ ],
34
+ "author": "Chamika Gayashan (CEE G)",
35
+ "license": "MIT",
36
+ "dependencies": {}
37
+ }
@@ -0,0 +1,126 @@
1
+ # Fivora Store Template
2
+
3
+ A high-performance, conversion-engineered e-commerce storefront built with **Next.js (App Router)** and **`@fivora/editable-components`**, ready for **Fivora**.
4
+
5
+ ---
6
+
7
+ ## Quick Start
8
+
9
+ ### 1. Install Dependencies
10
+ ```bash
11
+ npm install
12
+ ```
13
+
14
+ ### 2. Start Local Development
15
+ ```bash
16
+ npm run dev
17
+ ```
18
+ Open [http://localhost:3000](http://localhost:3000) in your browser to view your store.
19
+
20
+ ---
21
+
22
+ ## Developer Commands
23
+
24
+ This template is pre-configured with official Fivora developer tools:
25
+
26
+ | Command | Description |
27
+ | :--- | :--- |
28
+ | **`npm run dev`** | Starts the Next.js local development server with hot reload |
29
+ | **`npm run lab`** | Launches the **Fivora Visual Editing Lab** to test live merchant editing |
30
+ | **`npm run validate`** | Runs strict preflight compliance checks against Fivora contract rules |
31
+ | **`npm run zip`** | Packages your template into a clean, upload-ready `.zip` archive |
32
+ | **`npm run build`** | Generates the static production export in the `out/` directory |
33
+
34
+ ---
35
+
36
+ ## Project Structure
37
+
38
+ ```text
39
+ ├── fivora-template.json # Fivora template manifest (Version 2 strict contract)
40
+ ├── package.json # Dependencies and Fivora scripts
41
+ ├── next.config.ts # Next.js configuration (static export enabled)
42
+ ├── src/
43
+ │ ├── app/ # Next.js App Router pages (Home, About, Services, Contact)
44
+ │ │ ├── page.tsx # Home page
45
+ │ │ ├── about_us/ # About Us route
46
+ │ │ ├── services/ # Services route
47
+ │ │ └── contact/ # Contact form (integrated with Fivora endpoint)
48
+ │ ├── components/ # Reusable UI components & layouts
49
+ │ ├── data/
50
+ │ │ └── site-data.json # Merchant defaults, theme colors, navigation, & site content
51
+ │ └── lib/ # Site data context & live preview bridge
52
+ └── public/ # Static assets & logos
53
+ ```
54
+
55
+ ---
56
+
57
+ ## How Visual Editing Works
58
+
59
+ Fivora allows merchants to click and visually edit any text, image, or product on their website.
60
+
61
+ To make an element visually editable, use **`data-preview-field-path`** pointing to its key in `src/data/site-data.json`:
62
+
63
+ ```tsx
64
+ import { EditableHeading, EditableText } from '@fivora/editable-components';
65
+
66
+ // In your component:
67
+ <EditableHeading
68
+ level={1}
69
+ data-preview-field-path="home.heroTitle"
70
+ defaultValue={content.home.heroTitle}
71
+ />
72
+ ```
73
+
74
+ When you run **`npm run lab`**, you can click directly on any element with a `data-preview-field-path` to test editing in real-time.
75
+
76
+ ---
77
+
78
+ ## How to Customize
79
+
80
+ ### 1. Change Theme & Colors
81
+ Edit `src/data/site-data.json` under `template.structure.theme`:
82
+ ```json
83
+ "theme": {
84
+ "primaryColor": "#016a7e",
85
+ "secondaryColor": "#0a1931",
86
+ "accentColor": "#00adb5",
87
+ "backgroundColor": "#ffffff",
88
+ "textColor": "#0f172a",
89
+ "headingFont": "Inter",
90
+ "bodyFont": "Inter"
91
+ }
92
+ ```
93
+
94
+ ### 2. Add New Pages or Routes
95
+ 1. Create your page route in `src/app/your-page/page.tsx`.
96
+ 2. Declare the page in `fivora-template.json` under `"pages"`:
97
+ ```json
98
+ { "id": "your_page", "label": "Your Page", "route": "/your-page" }
99
+ ```
100
+ 3. Add the page content to `src/data/site-data.json`.
101
+
102
+ ---
103
+
104
+ ## Packaging for Fivora
105
+
106
+ When your template design is ready:
107
+
108
+ 1. **Validate Compliance:**
109
+ ```bash
110
+ npm run validate
111
+ ```
112
+ Ensures all preview markers, empty states, and required routes pass the Fivora preflight contract.
113
+
114
+ 2. **Generate Upload ZIP:**
115
+ ```bash
116
+ npm run zip
117
+ ```
118
+ This generates a clean `fivora-template.zip` in your root folder with all cache, build, and `node_modules` files automatically excluded.
119
+
120
+ 3. **Upload:** Go to your **Fivora Merchant/Developer Portal** and upload `fivora-template.zip`.
121
+
122
+ ---
123
+
124
+ ## License
125
+
126
+ MIT © [Fivora](https://fivora.com)
@@ -0,0 +1,13 @@
1
+ node_modules/
2
+ .next/
3
+ out/
4
+ .cache/
5
+ .turbo/
6
+ dist/
7
+ build/
8
+ coverage/
9
+ .env*
10
+ *.log
11
+ *.tsbuildinfo
12
+ *.zip
13
+ .DS_Store
@@ -0,0 +1,13 @@
1
+ import { dirname } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import { FlatCompat } from '@eslint/eslintrc';
4
+
5
+ const currentDirectory = dirname(fileURLToPath(import.meta.url));
6
+ const compat = new FlatCompat({ baseDirectory: currentDirectory });
7
+
8
+ const eslintConfig = [
9
+ ...compat.extends('next/core-web-vitals'),
10
+ { rules: { '@next/next/no-img-element': 'off' } },
11
+ ];
12
+
13
+ export default eslintConfig;
@@ -0,0 +1,259 @@
1
+ {
2
+ "framework": "nextjs-static-export",
3
+ "version": 2,
4
+ "visualEditing": {
5
+ "contractVersion": 1,
6
+ "mode": "strict",
7
+ "controlOnlyPaths": ["services[*].id"]
8
+ },
9
+ "siteDataFile": "src/data/site-data.json",
10
+ "outputDirectory": "out",
11
+ "installCommand": "npm install",
12
+ "buildCommand": "npm run build",
13
+ "basePathEnvVar": "NEXT_PUBLIC_SITE_BASE_PATH",
14
+ "pages": [
15
+ { "id": "home", "label": "Home", "route": "/", "required": true },
16
+ { "id": "about_us", "label": "About Us", "route": "/about_us" },
17
+ { "id": "services", "label": "Services", "route": "/services" },
18
+ {
19
+ "id": "contact",
20
+ "label": "Contact",
21
+ "route": "/contact",
22
+ "required": true
23
+ }
24
+ ],
25
+ "editorSchema": {
26
+ "version": 1,
27
+ "sections": [
28
+ {
29
+ "id": "common",
30
+ "path": "common",
31
+ "type": "object",
32
+ "label": "Shared Website Content",
33
+ "fields": [
34
+ {
35
+ "key": "websiteTitle",
36
+ "type": "text",
37
+ "label": "Website title",
38
+ "required": true
39
+ },
40
+ {
41
+ "key": "shortDescription",
42
+ "type": "textarea",
43
+ "label": "Short description"
44
+ },
45
+ {
46
+ "key": "logoUrl",
47
+ "type": "image",
48
+ "label": "Website logo",
49
+ "recommendedWidth": 512,
50
+ "recommendedHeight": 512
51
+ },
52
+ {
53
+ "key": "headerCtaLabel",
54
+ "type": "text",
55
+ "label": "Header contact button"
56
+ },
57
+ {
58
+ "key": "navLabels",
59
+ "type": "object",
60
+ "label": "Navigation labels",
61
+ "fields": [
62
+ { "key": "home", "type": "text", "label": "Home label" },
63
+ { "key": "about_us", "type": "text", "label": "About label" },
64
+ { "key": "services", "type": "text", "label": "Services label" },
65
+ { "key": "contact", "type": "text", "label": "Contact label" }
66
+ ]
67
+ },
68
+ { "key": "footerHeading", "type": "text", "label": "Footer heading" },
69
+ { "key": "copyright", "type": "text", "label": "Copyright" }
70
+ ]
71
+ },
72
+ {
73
+ "id": "home",
74
+ "path": "home",
75
+ "pageKey": "home",
76
+ "type": "object",
77
+ "label": "Home Page",
78
+ "fields": [
79
+ { "key": "heroEyebrow", "type": "text", "label": "Hero eyebrow" },
80
+ {
81
+ "key": "heroTitle",
82
+ "type": "text",
83
+ "label": "Hero title",
84
+ "required": true
85
+ },
86
+ { "key": "heroSummary", "type": "textarea", "label": "Hero summary" },
87
+ {
88
+ "key": "bannerImageUrl",
89
+ "type": "image",
90
+ "label": "Hero image",
91
+ "recommendedWidth": 1600,
92
+ "recommendedHeight": 900
93
+ },
94
+ {
95
+ "key": "primaryCtaLabel",
96
+ "type": "text",
97
+ "label": "Contact button"
98
+ },
99
+ {
100
+ "key": "secondaryCtaLabel",
101
+ "type": "text",
102
+ "label": "About button"
103
+ },
104
+ {
105
+ "key": "introTitle",
106
+ "type": "text",
107
+ "label": "Introduction title"
108
+ },
109
+ {
110
+ "key": "introBody",
111
+ "type": "textarea",
112
+ "label": "Introduction body"
113
+ },
114
+ {
115
+ "key": "featuresHeading",
116
+ "type": "text",
117
+ "label": "Features heading"
118
+ },
119
+ {
120
+ "key": "features",
121
+ "type": "list",
122
+ "label": "Features",
123
+ "minItems": 0,
124
+ "maxItems": 8,
125
+ "fields": [
126
+ {
127
+ "key": "title",
128
+ "type": "text",
129
+ "label": "Title",
130
+ "required": true
131
+ },
132
+ { "key": "body", "type": "textarea", "label": "Description" }
133
+ ]
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ "id": "about",
139
+ "path": "about",
140
+ "pageKey": "about_us",
141
+ "type": "object",
142
+ "label": "About Page",
143
+ "fields": [
144
+ {
145
+ "key": "heading",
146
+ "type": "text",
147
+ "label": "Heading",
148
+ "required": true
149
+ },
150
+ { "key": "body", "type": "textarea", "label": "About text" },
151
+ {
152
+ "key": "imageUrl",
153
+ "type": "image",
154
+ "label": "About image",
155
+ "recommendedWidth": 1200,
156
+ "recommendedHeight": 900
157
+ }
158
+ ]
159
+ },
160
+ {
161
+ "id": "servicesPage",
162
+ "path": "servicesPage",
163
+ "pageKey": "services",
164
+ "type": "object",
165
+ "label": "Services Page",
166
+ "fields": [
167
+ {
168
+ "key": "heading",
169
+ "type": "text",
170
+ "label": "Heading",
171
+ "required": true
172
+ },
173
+ { "key": "intro", "type": "textarea", "label": "Introduction" }
174
+ ]
175
+ },
176
+ {
177
+ "id": "services",
178
+ "path": "services",
179
+ "pageKey": "services",
180
+ "type": "list",
181
+ "label": "Services",
182
+ "minItems": 0,
183
+ "maxItems": 12,
184
+ "fields": [
185
+ { "key": "id", "type": "text", "label": "System ID" },
186
+ { "key": "name", "type": "text", "label": "Name", "required": true },
187
+ { "key": "description", "type": "textarea", "label": "Description" },
188
+ {
189
+ "key": "imageUrl",
190
+ "type": "image",
191
+ "label": "Image",
192
+ "recommendedWidth": 800,
193
+ "recommendedHeight": 600
194
+ },
195
+ {
196
+ "key": "features",
197
+ "type": "list",
198
+ "label": "Included features",
199
+ "itemField": { "type": "text", "label": "Feature" }
200
+ }
201
+ ]
202
+ },
203
+ {
204
+ "id": "contact",
205
+ "path": "contact",
206
+ "pageKey": "contact",
207
+ "type": "object",
208
+ "label": "Contact Page",
209
+ "fields": [
210
+ {
211
+ "key": "heading",
212
+ "type": "text",
213
+ "label": "Heading",
214
+ "required": true
215
+ },
216
+ { "key": "intro", "type": "textarea", "label": "Introduction" },
217
+ { "key": "phone", "type": "tel", "label": "Phone" },
218
+ { "key": "email", "type": "email", "label": "Email" },
219
+ { "key": "address", "type": "textarea", "label": "Address" },
220
+ { "key": "formTitle", "type": "text", "label": "Form heading" },
221
+ { "key": "submitLabel", "type": "text", "label": "Submit button" }
222
+ ]
223
+ }
224
+ ]
225
+ },
226
+ "themeSchema": {
227
+ "version": 1,
228
+ "tokens": [
229
+ "colors.primary",
230
+ "colors.secondary",
231
+ "colors.accent",
232
+ "colors.background",
233
+ "colors.text",
234
+ "typography.headingFont",
235
+ "typography.bodyFont",
236
+ "typography.baseSize",
237
+ "spacing.heroMinHeight",
238
+ "spacing.sectionPadding"
239
+ ],
240
+ "defaults": {
241
+ "colors": {
242
+ "primary": "#2563eb",
243
+ "secondary": "#0f172a",
244
+ "accent": "#14b8a6",
245
+ "background": "#ffffff",
246
+ "text": "#0f172a"
247
+ },
248
+ "typography": {
249
+ "headingFont": "Inter",
250
+ "bodyFont": "Inter",
251
+ "baseSize": "16px"
252
+ },
253
+ "spacing": {
254
+ "heroMinHeight": "72vh",
255
+ "sectionPadding": "5rem"
256
+ }
257
+ }
258
+ }
259
+ }
@@ -0,0 +1,6 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ /// <reference path="./.next/types/routes.d.ts" />
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,17 @@
1
+ import type { NextConfig } from 'next';
2
+
3
+ const basePath = (process.env.NEXT_PUBLIC_SITE_BASE_PATH ?? '').replace(
4
+ /\/$/,
5
+ '',
6
+ );
7
+
8
+ const nextConfig: NextConfig = {
9
+ output: 'export',
10
+ trailingSlash: true,
11
+ outputFileTracingRoot: process.cwd(),
12
+ basePath,
13
+ assetPrefix: basePath || undefined,
14
+ images: { unoptimized: true },
15
+ };
16
+
17
+ export default nextConfig;