@dyrected/core 1.0.5 → 1.0.6

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/LICENSE.md CHANGED
@@ -2,7 +2,7 @@ Business Source License 1.1
2
2
 
3
3
  Parameters
4
4
 
5
- Licensor: Ajola Technologies Ltd
5
+ Licensor: Dyrected
6
6
  Licensed Work: Dyrected
7
7
  Additional Use Grant: Commercial use is permitted as long as it is not used to provide a hosted or managed service that competes with Dyrected.
8
8
  Change Date: 2030-05-11
package/dist/index.cjs CHANGED
@@ -31,206 +31,109 @@ module.exports = __toCommonJS(index_exports);
31
31
  // src/utils/setup-prompt.ts
32
32
  function generateAIPrompt(activeTab, config) {
33
33
  const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt" : activeTab.charAt(0).toUpperCase() + activeTab.slice(1);
34
- const backendPkg = activeTab === "nuxt" ? "@dyrected/nuxt" : "@dyrected/next";
35
34
  const isSelfHosted = config.isSelfHosted ?? (config.baseUrl?.includes("localhost") || !config.apiKey);
36
- const baseIntro = isSelfHosted ? `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a ${config.siteName || "new"} project using ${frameworkLabel}. This is a SELF-HOSTED installation.
37
- The backend is already configured via ${backendPkg}.
38
- Your priority is DATA PRESERVATION and creating a CMS that empowers marketing teams.` : `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a ${config.siteName || "new"} project using ${frameworkLabel}. Complete the entire setup automatically, including embedding the Admin UI and syncing the schema. Your priority is DATA PRESERVATION and marketing independence.`;
39
- const credentials = `
35
+ const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
36
+ const mission = `You are a Senior Content Architect. Your mission is to create a robust CMS integration plan for a ${frameworkLabel} website using Dyrected CMS.
37
+
40
38
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
41
- SITE CREDENTIALS
39
+ 1. ENVIRONMENT
42
40
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
43
- ${config.siteName ? `Site Name : ${config.siteName}
44
- ` : ""}${isSelfHosted ? "" : `Site ID : ${config.siteId}
45
- API Key : ${config.apiKey}
46
- `}API URL : ${config.baseUrl || "http://localhost:3000"}
47
- `;
48
- const importantNotes = `
41
+ - Framework: ${frameworkLabel}
42
+ - Host Type: ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
43
+ - API Base : ${config.baseUrl || "http://localhost:3000"}
44
+ ${isSelfHosted ? "" : `- Site ID : ${config.siteId}
45
+ - API Key : ${config.apiKey}`}
46
+
49
47
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
50
- IMPORTANT NOTES \u2014 READ BEFORE ACTING
48
+ 2. ARCHITECTURE & CONSTRAINTS
51
49
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
52
- - \`client.collection(slug)\` is the primary API entrypoint. Do NOT use \`client.collections\`.
53
- - Blocks are stored as \`[{ blockType: '<slug>', ...fields }]\` \u2014 always switch on \`blockType\` when rendering.
54
- - MARKETING INDEPENDENCE: Always use a dynamic \`pages\` collection with a catch-all route. Marketing should create pages without a developer. (Exceptions: Special pages like Contact or those with complex forms may use static routes).
55
- - AUTO-SEEDING: Use \`initialData\` in all data fetches to ensure the site is never empty on first load.
56
- - Globals use \`client.global(slug).get()\` and \`client.global(slug).update(data)\`.
57
- - Relationship fields are populated to the specified \`depth\` (default: 1). Set \`depth: 0\` for IDs only.
58
- - SYNC SCHEMA: After creating the content model, you MUST execute \`npx @dyrected/cli sync:schema\` immediately. This is non-negotiable for a working setup.
59
- - EMBED ADMIN: You MUST create a route (e.g. \`/admin\`) and mount the Admin UI components described below.
60
- - ALL-IN-ONE: Your goal is to give the user a project that is READY TO SHIP. Do the setup, do the embedding, and do the sync in one go.
61
- `;
62
- const strategy = `
50
+ - API ACCESS: Use \`client.collection(slug)\` as the primary entry point. Do NOT use \`client.collections\`.
51
+ - ZERO-STATE ROBUSTNESS: Always use \`initialData\` in all data fetches to ensure the site renders correctly on first load.
52
+ - MARKETING INDEPENDENCE: Use a dynamic \`pages\` collection with a catch-all route for marketing-managed pages.
53
+ - BLOCKS-BASED DESIGN: Use \`blocks\` for flexible page builders. Iterate the array and switch on \`blockType\` in your frontend.
54
+ - DATA PRESERVATION: Do NOT modify or overwrite existing pages without first extracting and preserving their data.
55
+ - NO DEPRECATIONS: Use the framework-specific \`DyrectedAdmin\` components (Next/Nuxt) which handle routing and CSS automatically.
56
+
57
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
58
+ 3. REQUIRED DELIVERABLES
59
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
60
+ Your final response MUST include:
61
+ 1. A complete content model plan (\`dyrected.config.ts\`).
62
+ 2. The Admin UI mounting route (e.g. \`/admin\`) using the specialized \`DyrectedAdmin\` component.
63
+ 3. A catch-all frontend page route for CMS-managed dynamic pages.
64
+ 4. A block type list covering existing site sections (Hero, Content, CTA, etc.).
65
+ 5. A migration/fallback strategy for current static pages.
66
+ 6. A safe schema sync step using \`npx @dyrected/cli sync:schema\`.
67
+
63
68
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
64
- PHASE 0 \u2014 DATA PRESERVATION & STRATEGY
69
+ 4. PHASE 0 \u2014 DISCOVERY & PRESERVATION
65
70
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
66
- 1. BACKUP: If an existing site is provided, you MUST extract all current content (text, assets, structure) and save it into a \`migration/\` folder as structured .md files BEFORE modifying any code. Never lose data.
67
- 2. DISCOVERY: If NO existing site is provided, STOP and ask the user:
68
- - "What are your core content types (e.g. Services, Team, Blog)?"
69
- - "How do you want your marketing team to manage the page layouts?"
70
- 3. ARCHITECTURAL CREATIVITY: Design the CMS for longevity. Use \`blocks\` for flexible page builders, \`globals\` for site settings, and \`collections\` for repeated content.
71
+ Before writing any code, you MUST:
72
+ 1. BACKUP: Propose a plan to save current site content into a \`migration/\` folder.
73
+ 2. ASK QUESTIONS: If the site content types are unknown, ask the user:
74
+ - "What are your core content types (Services, Team, Projects)?"
75
+ - "Which existing pages must remain static vs. becoming dynamic?"
76
+ - "What layouts should marketing be able to manage with blocks?"
77
+ - "What existing hardcoded sections must be preserved?"
71
78
 
72
- STEP 1 \u2014 CONTENT MODEL (dyrected.config.ts)
79
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
80
+ 5. TECHNICAL REFERENCE (Field Types & Syntax)
73
81
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
74
82
  Use \`defineCollection\`, \`defineGlobal\`, and \`defineConfig\` from '@dyrected/core'.
75
83
 
76
- SUPPORTED FIELD TYPES:
77
- Primitive : text | textarea | richText | number | boolean | date | email | url | json
78
- Choice : select | multiSelect (requires \`options: [{ label, value }]\`)
79
- Structural : array | object (requires nested \`fields: [...]\`)
80
- Relation : relationship (requires \`collection: '<slug>'\`)
81
- Media : image (use a relationship to an upload collection)
82
- Blocks : blocks (requires \`blocks: [{ slug, labels, fields }]\`)
84
+ FIELD TYPES:
85
+ - Primitive : text | textarea | richText | number | boolean | date | email | url | json
86
+ - Choice : select | multiSelect (requires \`options: [{ label, value }]\`)
87
+ - Structural : array | object (requires \`fields: [...]\`)
88
+ - Relation : relationship (requires \`collection: '<slug>'\`)
89
+ - Media : relationship to an upload collection (e.g. 'media')
90
+ - Blocks : blocks (requires \`blocks: [{ slug, labels, fields }]\`)
83
91
 
84
92
  COLLECTION OPTIONS:
85
- \`upload: true\` \u2014 turns this collection into a media library (file uploads)
86
- \`auth: true\` \u2014 adds login/register/me endpoints; password field is auto-added
87
- \`admin.group\` \u2014 groups this collection under a sidebar heading
88
- \`admin.useAsTitle\` \u2014 field to use as the display title in the admin list view
89
- \`admin.hidden\` \u2014 hide from sidebar (useful for internal/system collections)
90
-
91
- FIELD OPTIONS:
92
- \`required\` \u2014 validation
93
- \`unique\` \u2014 database-level uniqueness
94
- \`defaultValue\` \u2014 fallback value
95
- \`admin.condition\` \u2014 "expression" \u2014 Jexl string expression to show/hide field (e.g. "status == \\"published\\"")
96
- \`admin.layout\` \u2014 "radio" | "dropdown" \u2014 Visual layout for select/multiSelect
97
- \`admin.direction\` \u2014 "vertical" | "horizontal" \u2014 Layout direction for radio groups
98
- \`admin.readOnly\` \u2014 display-only in the form
99
- \`admin.hidden\` \u2014 completely hidden from editor UI
100
- \`access.read\` \u2014 ({ user }) => boolean \u2014 field-level read access
101
- \`access.update\` \u2014 ({ user }) => boolean \u2014 field-level write access
102
- \`hooks.beforeChange\` \u2014 [async (value) => newValue] \u2014 transform value before save
103
- \`hooks.afterRead\` \u2014 [async (value) => newValue] \u2014 transform value after read
93
+ - \`upload: true\`: Use for media libraries.
94
+ - \`auth: true\`: Adds auth endpoints (login/me) and an auto-managed password field.
95
+ - \`admin.useAsTitle\`: Field to display in admin lists.
104
96
 
105
- BLOCKS EXPLAINED:
106
- A \`blocks\` field stores an ordered array of typed content blocks.
107
- Each block has a \`blockType\` discriminator and its own set of fields.
108
- The admin UI renders a drag-and-drop block editor automatically.
109
- On the frontend, iterate the array and switch on \`block.blockType\`.
97
+ BLOCKS SYNTAX:
98
+ Blocks are stored as \`[{ blockType: 'slug', ...fields }]\`. The Admin UI renders a drag-and-drop editor for these automatically.
110
99
 
111
- COMPLETE EXAMPLE:
112
- \`\`\`typescript
113
- import { defineCollection, defineGlobal, defineConfig } from '@dyrected/core'
100
+ SCHEMA EXAMPLE:
101
+ \`\`\`ts
102
+ import { defineCollection, defineConfig } from '@dyrected/core'
114
103
 
115
- // \u2500\u2500 Media \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
116
104
  const media = defineCollection({
117
105
  slug: 'media',
118
- labels: { singular: 'Media Item', plural: 'Media' },
119
106
  upload: true,
120
- fields: [
121
- { name: 'alt', type: 'text', label: 'Alt Text' },
122
- { name: 'caption', type: 'textarea', label: 'Caption' },
123
- ],
124
- })
125
-
126
- // \u2500\u2500 Authentication collection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
127
- const customers = defineCollection({
128
- slug: 'customers',
129
- labels: { singular: 'Customer', plural: 'Customers' },
130
- auth: true, // adds /customers/login, /customers/me, etc.
131
- admin: { group: 'Membership' },
132
- fields: [
133
- { name: 'name', type: 'text', required: true },
134
- { name: 'email', type: 'email', required: true, unique: true },
135
- // 'password' is auto-added when auth: true
136
- { name: 'avatar', type: 'relationship', relationTo: 'media' },
137
- { name: 'role', type: 'select', admin: { layout: 'radio' }, options: [
138
- { label: 'Member', value: 'member' },
139
- { label: 'VIP', value: 'vip' },
140
- ]},
141
- ],
107
+ fields: [{ name: 'alt', type: 'text' }]
142
108
  })
143
109
 
144
- // \u2500\u2500 Pages with blocks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
145
110
  const pages = defineCollection({
146
111
  slug: 'pages',
147
- labels: { singular: 'Page', plural: 'Pages' },
148
- admin: { useAsTitle: 'title', group: 'Content' },
149
112
  fields: [
150
113
  { name: 'title', type: 'text', required: true },
151
- { name: 'slug', type: 'text', required: true, unique: true },
152
- { name: 'seo', type: 'object', fields: [
153
- { name: 'metaTitle', type: 'text' },
154
- { name: 'metaDescription', type: 'textarea' },
155
- { name: 'ogImage', type: 'relationship', relationTo: 'media' },
156
- ]},
157
- {
158
- name: 'layout',
159
- type: 'blocks',
160
- label: 'Page Layout',
161
- blocks: [
162
- {
163
- slug: 'hero',
164
- labels: { singular: 'Hero', plural: 'Heroes' },
165
- fields: [
166
- { name: 'heading', type: 'text', required: true },
167
- { name: 'subheading', type: 'textarea' },
168
- { name: 'image', type: 'relationship', relationTo: 'media' },
169
- { name: 'ctaLabel', type: 'text' },
170
- { name: 'ctaLink', type: 'url' },
171
- ],
172
- },
173
- {
174
- slug: 'richContent',
175
- labels: { singular: 'Rich Content', plural: 'Rich Content Blocks' },
176
- fields: [
177
- { name: 'content', type: 'richText', required: true },
178
- ],
179
- },
180
- ],
181
- },
182
- ],
114
+ { name: 'slug', type: 'text', required: true, unique: true },
115
+ { name: 'layout', type: 'blocks', blocks: [
116
+ { slug: 'hero', fields: [{ name: 'title', type: 'text' }] }
117
+ ]}
118
+ ]
183
119
  })
184
120
 
185
- export default defineConfig({
186
- collections: [media, customers, pages],
187
- admin: {
188
- branding: {
189
- primaryColor: '#4f46e5',
190
- logo: '/logo.png',
191
- }
192
- }
193
- })
121
+ export default defineConfig({ collections: [media, pages] })
194
122
  \`\`\`
195
123
 
196
- ${isSelfHosted ? "" : `
197
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
198
- STEP 2 \u2014 CHOOSE YOUR MODE
199
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
200
- The developer can choose between two modes:
201
-
202
- 1. CLOUD MODE (Managed)
203
- - Use the SITE CREDENTIALS above.
204
- - Point baseUrl to ${config.baseUrl}.
205
- - Content is stored in Dyrected Cloud.
206
-
207
- 2. SELF-HOSTED MODE (Core)
208
- - Do NOT use apiKey/siteId (unless for proxying).
209
- - Use a database adapter like \`SqliteAdapter\` from '@dyrected/db-sqlite'.
210
- - Content is stored locally in the developer's project.
211
- `}
212
-
213
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
214
- STEP ${isSelfHosted ? "2" : "3"} \u2014 MOUNTING THE ADMIN UI
215
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
216
- The Admin UI can be mounted on any path (e.g. /cms-admin).
217
- Pass the \`basename\` prop to the \`<AdminUI />\` component to match your route.
218
-
219
124
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
220
- STEP ${isSelfHosted ? "3" : "4"} \u2014 FRONTEND IMPLEMENTATION
125
+ 6. IMPLEMENTATION DETAILS (${frameworkLabel})
221
126
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
222
127
  `;
223
128
  const frameworks = {
224
- next: `Install \`@dyrected/next\` and \`@dyrected/admin\`.
225
-
226
- 1. **SDK Setup** (\`lib/dyrected.ts\`):
129
+ next: `1. **SDK Setup** (\`lib/dyrected.ts\`):
227
130
  \`\`\`ts
228
131
  import { createClient } from '@dyrected/sdk'
229
132
 
230
133
  export const dyrected = createClient({
231
- baseUrl: '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
232
- apiKey: '${config.apiKey}',
233
- siteId: '${config.siteId}',`}
134
+ baseUrl: process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
135
+ apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
136
+ siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`}
234
137
  })
235
138
  \`\`\`
236
139
 
@@ -239,23 +142,24 @@ export const dyrected = createClient({
239
142
  import { DyrectedAdmin } from '@dyrected/next/admin'
240
143
 
241
144
  export default function AdminPage() {
145
+ // DyrectedAdmin handles router, CSS, and "use client" for you
242
146
  return <DyrectedAdmin basename="/admin" />
243
147
  }
244
148
  \`\`\`
245
149
  `,
246
- nuxt: `Install \`@dyrected/nuxt\` and add it to \`nuxt.config.ts\`:
150
+ nuxt: `1. **Nuxt Config** (\`nuxt.config.ts\`):
247
151
  \`\`\`ts
248
152
  export default defineNuxtConfig({
249
153
  modules: ['@dyrected/nuxt'],
250
154
  dyrected: {
251
- baseUrl: '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
252
- apiKey: '${config.apiKey}',
253
- siteId: '${config.siteId}',`}
155
+ baseUrl: process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
156
+ apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
157
+ siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`}
254
158
  },
255
159
  })
256
160
  \`\`\`
257
161
 
258
- MOUNTING THE ADMIN DASHBOARD (\`pages/admin.vue\`):
162
+ 2. **Admin Dashboard** (\`pages/admin.vue\`):
259
163
  \`\`\`vue
260
164
  <script setup lang="ts">
261
165
  // DyrectedAdmin is auto-imported by the module
@@ -336,7 +240,7 @@ onMounted(() => {
336
240
  \`\`\`
337
241
  `
338
242
  };
339
- return baseIntro + credentials + importantNotes + strategy + (frameworks[activeTab] || frameworks.next) + `
243
+ return mission + (frameworks[activeTab] || frameworks.next) + `
340
244
  API Reference: https://docs.dyrected.com`;
341
245
  }
342
246
 
package/dist/index.js CHANGED
@@ -5,206 +5,109 @@ import {
5
5
  // src/utils/setup-prompt.ts
6
6
  function generateAIPrompt(activeTab, config) {
7
7
  const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt" : activeTab.charAt(0).toUpperCase() + activeTab.slice(1);
8
- const backendPkg = activeTab === "nuxt" ? "@dyrected/nuxt" : "@dyrected/next";
9
8
  const isSelfHosted = config.isSelfHosted ?? (config.baseUrl?.includes("localhost") || !config.apiKey);
10
- const baseIntro = isSelfHosted ? `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a ${config.siteName || "new"} project using ${frameworkLabel}. This is a SELF-HOSTED installation.
11
- The backend is already configured via ${backendPkg}.
12
- Your priority is DATA PRESERVATION and creating a CMS that empowers marketing teams.` : `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a ${config.siteName || "new"} project using ${frameworkLabel}. Complete the entire setup automatically, including embedding the Admin UI and syncing the schema. Your priority is DATA PRESERVATION and marketing independence.`;
13
- const credentials = `
9
+ const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
10
+ const mission = `You are a Senior Content Architect. Your mission is to create a robust CMS integration plan for a ${frameworkLabel} website using Dyrected CMS.
11
+
14
12
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
15
- SITE CREDENTIALS
13
+ 1. ENVIRONMENT
16
14
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
17
- ${config.siteName ? `Site Name : ${config.siteName}
18
- ` : ""}${isSelfHosted ? "" : `Site ID : ${config.siteId}
19
- API Key : ${config.apiKey}
20
- `}API URL : ${config.baseUrl || "http://localhost:3000"}
21
- `;
22
- const importantNotes = `
15
+ - Framework: ${frameworkLabel}
16
+ - Host Type: ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
17
+ - API Base : ${config.baseUrl || "http://localhost:3000"}
18
+ ${isSelfHosted ? "" : `- Site ID : ${config.siteId}
19
+ - API Key : ${config.apiKey}`}
20
+
23
21
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
24
- IMPORTANT NOTES \u2014 READ BEFORE ACTING
22
+ 2. ARCHITECTURE & CONSTRAINTS
25
23
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
26
- - \`client.collection(slug)\` is the primary API entrypoint. Do NOT use \`client.collections\`.
27
- - Blocks are stored as \`[{ blockType: '<slug>', ...fields }]\` \u2014 always switch on \`blockType\` when rendering.
28
- - MARKETING INDEPENDENCE: Always use a dynamic \`pages\` collection with a catch-all route. Marketing should create pages without a developer. (Exceptions: Special pages like Contact or those with complex forms may use static routes).
29
- - AUTO-SEEDING: Use \`initialData\` in all data fetches to ensure the site is never empty on first load.
30
- - Globals use \`client.global(slug).get()\` and \`client.global(slug).update(data)\`.
31
- - Relationship fields are populated to the specified \`depth\` (default: 1). Set \`depth: 0\` for IDs only.
32
- - SYNC SCHEMA: After creating the content model, you MUST execute \`npx @dyrected/cli sync:schema\` immediately. This is non-negotiable for a working setup.
33
- - EMBED ADMIN: You MUST create a route (e.g. \`/admin\`) and mount the Admin UI components described below.
34
- - ALL-IN-ONE: Your goal is to give the user a project that is READY TO SHIP. Do the setup, do the embedding, and do the sync in one go.
35
- `;
36
- const strategy = `
24
+ - API ACCESS: Use \`client.collection(slug)\` as the primary entry point. Do NOT use \`client.collections\`.
25
+ - ZERO-STATE ROBUSTNESS: Always use \`initialData\` in all data fetches to ensure the site renders correctly on first load.
26
+ - MARKETING INDEPENDENCE: Use a dynamic \`pages\` collection with a catch-all route for marketing-managed pages.
27
+ - BLOCKS-BASED DESIGN: Use \`blocks\` for flexible page builders. Iterate the array and switch on \`blockType\` in your frontend.
28
+ - DATA PRESERVATION: Do NOT modify or overwrite existing pages without first extracting and preserving their data.
29
+ - NO DEPRECATIONS: Use the framework-specific \`DyrectedAdmin\` components (Next/Nuxt) which handle routing and CSS automatically.
30
+
31
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
32
+ 3. REQUIRED DELIVERABLES
33
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
34
+ Your final response MUST include:
35
+ 1. A complete content model plan (\`dyrected.config.ts\`).
36
+ 2. The Admin UI mounting route (e.g. \`/admin\`) using the specialized \`DyrectedAdmin\` component.
37
+ 3. A catch-all frontend page route for CMS-managed dynamic pages.
38
+ 4. A block type list covering existing site sections (Hero, Content, CTA, etc.).
39
+ 5. A migration/fallback strategy for current static pages.
40
+ 6. A safe schema sync step using \`npx @dyrected/cli sync:schema\`.
41
+
37
42
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
38
- PHASE 0 \u2014 DATA PRESERVATION & STRATEGY
43
+ 4. PHASE 0 \u2014 DISCOVERY & PRESERVATION
39
44
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
40
- 1. BACKUP: If an existing site is provided, you MUST extract all current content (text, assets, structure) and save it into a \`migration/\` folder as structured .md files BEFORE modifying any code. Never lose data.
41
- 2. DISCOVERY: If NO existing site is provided, STOP and ask the user:
42
- - "What are your core content types (e.g. Services, Team, Blog)?"
43
- - "How do you want your marketing team to manage the page layouts?"
44
- 3. ARCHITECTURAL CREATIVITY: Design the CMS for longevity. Use \`blocks\` for flexible page builders, \`globals\` for site settings, and \`collections\` for repeated content.
45
+ Before writing any code, you MUST:
46
+ 1. BACKUP: Propose a plan to save current site content into a \`migration/\` folder.
47
+ 2. ASK QUESTIONS: If the site content types are unknown, ask the user:
48
+ - "What are your core content types (Services, Team, Projects)?"
49
+ - "Which existing pages must remain static vs. becoming dynamic?"
50
+ - "What layouts should marketing be able to manage with blocks?"
51
+ - "What existing hardcoded sections must be preserved?"
45
52
 
46
- STEP 1 \u2014 CONTENT MODEL (dyrected.config.ts)
53
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
54
+ 5. TECHNICAL REFERENCE (Field Types & Syntax)
47
55
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
48
56
  Use \`defineCollection\`, \`defineGlobal\`, and \`defineConfig\` from '@dyrected/core'.
49
57
 
50
- SUPPORTED FIELD TYPES:
51
- Primitive : text | textarea | richText | number | boolean | date | email | url | json
52
- Choice : select | multiSelect (requires \`options: [{ label, value }]\`)
53
- Structural : array | object (requires nested \`fields: [...]\`)
54
- Relation : relationship (requires \`collection: '<slug>'\`)
55
- Media : image (use a relationship to an upload collection)
56
- Blocks : blocks (requires \`blocks: [{ slug, labels, fields }]\`)
58
+ FIELD TYPES:
59
+ - Primitive : text | textarea | richText | number | boolean | date | email | url | json
60
+ - Choice : select | multiSelect (requires \`options: [{ label, value }]\`)
61
+ - Structural : array | object (requires \`fields: [...]\`)
62
+ - Relation : relationship (requires \`collection: '<slug>'\`)
63
+ - Media : relationship to an upload collection (e.g. 'media')
64
+ - Blocks : blocks (requires \`blocks: [{ slug, labels, fields }]\`)
57
65
 
58
66
  COLLECTION OPTIONS:
59
- \`upload: true\` \u2014 turns this collection into a media library (file uploads)
60
- \`auth: true\` \u2014 adds login/register/me endpoints; password field is auto-added
61
- \`admin.group\` \u2014 groups this collection under a sidebar heading
62
- \`admin.useAsTitle\` \u2014 field to use as the display title in the admin list view
63
- \`admin.hidden\` \u2014 hide from sidebar (useful for internal/system collections)
67
+ - \`upload: true\`: Use for media libraries.
68
+ - \`auth: true\`: Adds auth endpoints (login/me) and an auto-managed password field.
69
+ - \`admin.useAsTitle\`: Field to display in admin lists.
64
70
 
65
- FIELD OPTIONS:
66
- \`required\` \u2014 validation
67
- \`unique\` \u2014 database-level uniqueness
68
- \`defaultValue\` \u2014 fallback value
69
- \`admin.condition\` \u2014 "expression" \u2014 Jexl string expression to show/hide field (e.g. "status == \\"published\\"")
70
- \`admin.layout\` \u2014 "radio" | "dropdown" \u2014 Visual layout for select/multiSelect
71
- \`admin.direction\` \u2014 "vertical" | "horizontal" \u2014 Layout direction for radio groups
72
- \`admin.readOnly\` \u2014 display-only in the form
73
- \`admin.hidden\` \u2014 completely hidden from editor UI
74
- \`access.read\` \u2014 ({ user }) => boolean \u2014 field-level read access
75
- \`access.update\` \u2014 ({ user }) => boolean \u2014 field-level write access
76
- \`hooks.beforeChange\` \u2014 [async (value) => newValue] \u2014 transform value before save
77
- \`hooks.afterRead\` \u2014 [async (value) => newValue] \u2014 transform value after read
71
+ BLOCKS SYNTAX:
72
+ Blocks are stored as \`[{ blockType: 'slug', ...fields }]\`. The Admin UI renders a drag-and-drop editor for these automatically.
78
73
 
79
- BLOCKS EXPLAINED:
80
- A \`blocks\` field stores an ordered array of typed content blocks.
81
- Each block has a \`blockType\` discriminator and its own set of fields.
82
- The admin UI renders a drag-and-drop block editor automatically.
83
- On the frontend, iterate the array and switch on \`block.blockType\`.
84
-
85
- COMPLETE EXAMPLE:
86
- \`\`\`typescript
87
- import { defineCollection, defineGlobal, defineConfig } from '@dyrected/core'
74
+ SCHEMA EXAMPLE:
75
+ \`\`\`ts
76
+ import { defineCollection, defineConfig } from '@dyrected/core'
88
77
 
89
- // \u2500\u2500 Media \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
90
78
  const media = defineCollection({
91
79
  slug: 'media',
92
- labels: { singular: 'Media Item', plural: 'Media' },
93
80
  upload: true,
94
- fields: [
95
- { name: 'alt', type: 'text', label: 'Alt Text' },
96
- { name: 'caption', type: 'textarea', label: 'Caption' },
97
- ],
98
- })
99
-
100
- // \u2500\u2500 Authentication collection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
101
- const customers = defineCollection({
102
- slug: 'customers',
103
- labels: { singular: 'Customer', plural: 'Customers' },
104
- auth: true, // adds /customers/login, /customers/me, etc.
105
- admin: { group: 'Membership' },
106
- fields: [
107
- { name: 'name', type: 'text', required: true },
108
- { name: 'email', type: 'email', required: true, unique: true },
109
- // 'password' is auto-added when auth: true
110
- { name: 'avatar', type: 'relationship', relationTo: 'media' },
111
- { name: 'role', type: 'select', admin: { layout: 'radio' }, options: [
112
- { label: 'Member', value: 'member' },
113
- { label: 'VIP', value: 'vip' },
114
- ]},
115
- ],
81
+ fields: [{ name: 'alt', type: 'text' }]
116
82
  })
117
83
 
118
- // \u2500\u2500 Pages with blocks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
119
84
  const pages = defineCollection({
120
85
  slug: 'pages',
121
- labels: { singular: 'Page', plural: 'Pages' },
122
- admin: { useAsTitle: 'title', group: 'Content' },
123
86
  fields: [
124
87
  { name: 'title', type: 'text', required: true },
125
- { name: 'slug', type: 'text', required: true, unique: true },
126
- { name: 'seo', type: 'object', fields: [
127
- { name: 'metaTitle', type: 'text' },
128
- { name: 'metaDescription', type: 'textarea' },
129
- { name: 'ogImage', type: 'relationship', relationTo: 'media' },
130
- ]},
131
- {
132
- name: 'layout',
133
- type: 'blocks',
134
- label: 'Page Layout',
135
- blocks: [
136
- {
137
- slug: 'hero',
138
- labels: { singular: 'Hero', plural: 'Heroes' },
139
- fields: [
140
- { name: 'heading', type: 'text', required: true },
141
- { name: 'subheading', type: 'textarea' },
142
- { name: 'image', type: 'relationship', relationTo: 'media' },
143
- { name: 'ctaLabel', type: 'text' },
144
- { name: 'ctaLink', type: 'url' },
145
- ],
146
- },
147
- {
148
- slug: 'richContent',
149
- labels: { singular: 'Rich Content', plural: 'Rich Content Blocks' },
150
- fields: [
151
- { name: 'content', type: 'richText', required: true },
152
- ],
153
- },
154
- ],
155
- },
156
- ],
88
+ { name: 'slug', type: 'text', required: true, unique: true },
89
+ { name: 'layout', type: 'blocks', blocks: [
90
+ { slug: 'hero', fields: [{ name: 'title', type: 'text' }] }
91
+ ]}
92
+ ]
157
93
  })
158
94
 
159
- export default defineConfig({
160
- collections: [media, customers, pages],
161
- admin: {
162
- branding: {
163
- primaryColor: '#4f46e5',
164
- logo: '/logo.png',
165
- }
166
- }
167
- })
95
+ export default defineConfig({ collections: [media, pages] })
168
96
  \`\`\`
169
97
 
170
- ${isSelfHosted ? "" : `
171
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
172
- STEP 2 \u2014 CHOOSE YOUR MODE
173
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
174
- The developer can choose between two modes:
175
-
176
- 1. CLOUD MODE (Managed)
177
- - Use the SITE CREDENTIALS above.
178
- - Point baseUrl to ${config.baseUrl}.
179
- - Content is stored in Dyrected Cloud.
180
-
181
- 2. SELF-HOSTED MODE (Core)
182
- - Do NOT use apiKey/siteId (unless for proxying).
183
- - Use a database adapter like \`SqliteAdapter\` from '@dyrected/db-sqlite'.
184
- - Content is stored locally in the developer's project.
185
- `}
186
-
187
98
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
188
- STEP ${isSelfHosted ? "2" : "3"} \u2014 MOUNTING THE ADMIN UI
189
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
190
- The Admin UI can be mounted on any path (e.g. /cms-admin).
191
- Pass the \`basename\` prop to the \`<AdminUI />\` component to match your route.
192
-
193
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
194
- STEP ${isSelfHosted ? "3" : "4"} \u2014 FRONTEND IMPLEMENTATION
99
+ 6. IMPLEMENTATION DETAILS (${frameworkLabel})
195
100
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
196
101
  `;
197
102
  const frameworks = {
198
- next: `Install \`@dyrected/next\` and \`@dyrected/admin\`.
199
-
200
- 1. **SDK Setup** (\`lib/dyrected.ts\`):
103
+ next: `1. **SDK Setup** (\`lib/dyrected.ts\`):
201
104
  \`\`\`ts
202
105
  import { createClient } from '@dyrected/sdk'
203
106
 
204
107
  export const dyrected = createClient({
205
- baseUrl: '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
206
- apiKey: '${config.apiKey}',
207
- siteId: '${config.siteId}',`}
108
+ baseUrl: process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
109
+ apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
110
+ siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`}
208
111
  })
209
112
  \`\`\`
210
113
 
@@ -213,23 +116,24 @@ export const dyrected = createClient({
213
116
  import { DyrectedAdmin } from '@dyrected/next/admin'
214
117
 
215
118
  export default function AdminPage() {
119
+ // DyrectedAdmin handles router, CSS, and "use client" for you
216
120
  return <DyrectedAdmin basename="/admin" />
217
121
  }
218
122
  \`\`\`
219
123
  `,
220
- nuxt: `Install \`@dyrected/nuxt\` and add it to \`nuxt.config.ts\`:
124
+ nuxt: `1. **Nuxt Config** (\`nuxt.config.ts\`):
221
125
  \`\`\`ts
222
126
  export default defineNuxtConfig({
223
127
  modules: ['@dyrected/nuxt'],
224
128
  dyrected: {
225
- baseUrl: '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
226
- apiKey: '${config.apiKey}',
227
- siteId: '${config.siteId}',`}
129
+ baseUrl: process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
130
+ apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
131
+ siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`}
228
132
  },
229
133
  })
230
134
  \`\`\`
231
135
 
232
- MOUNTING THE ADMIN DASHBOARD (\`pages/admin.vue\`):
136
+ 2. **Admin Dashboard** (\`pages/admin.vue\`):
233
137
  \`\`\`vue
234
138
  <script setup lang="ts">
235
139
  // DyrectedAdmin is auto-imported by the module
@@ -310,7 +214,7 @@ onMounted(() => {
310
214
  \`\`\`
311
215
  `
312
216
  };
313
- return baseIntro + credentials + importantNotes + strategy + (frameworks[activeTab] || frameworks.next) + `
217
+ return mission + (frameworks[activeTab] || frameworks.next) + `
314
218
  API Reference: https://docs.dyrected.com`;
315
219
  }
316
220
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/core",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",