@dyrected/core 1.0.7 → 1.0.9
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/dist/index.cjs +325 -92
- package/dist/index.js +325 -92
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -29,125 +29,306 @@ __export(index_exports, {
|
|
|
29
29
|
module.exports = __toCommonJS(index_exports);
|
|
30
30
|
|
|
31
31
|
// src/utils/setup-prompt.ts
|
|
32
|
-
function
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
32
|
+
function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
|
|
33
|
+
const credentialLines = isSelfHosted ? "" : `- Site ID : ${config.siteId}
|
|
34
|
+
- API Key : ${config.apiKey}`;
|
|
35
|
+
return `
|
|
38
36
|
\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
|
|
39
37
|
1. ENVIRONMENT
|
|
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
|
-
- Framework: ${frameworkLabel}
|
|
42
|
-
- Host Type: ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
|
|
43
|
-
- API Base
|
|
44
|
-
${
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
- Framework : ${frameworkLabel}
|
|
40
|
+
- Host Type : ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
|
|
41
|
+
- API Base : ${config.baseUrl || "http://localhost:3000"}
|
|
42
|
+
${credentialLines}`;
|
|
43
|
+
}
|
|
44
|
+
function buildDiagnosticSection(existingSite) {
|
|
45
|
+
if (existingSite) {
|
|
46
|
+
return `
|
|
47
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
|
|
48
|
-
2.
|
|
48
|
+
2. PHASE 0 \u2014 DIAGNOSTIC (EXISTING SITE)
|
|
49
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
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
|
|
50
|
+
Before writing any code, scan the existing codebase and report:
|
|
51
|
+
- All hardcoded text strings that should be CMS-managed
|
|
52
|
+
- All repeated data structures that should become collections
|
|
53
|
+
- All static pages that marketing will want to edit independently
|
|
54
|
+
- Any existing fetch or API calls that overlap with Dyrected
|
|
55
|
+
|
|
56
|
+
Then propose a backup plan: extract all current content into a
|
|
57
|
+
migration/ folder as structured .md or .json files BEFORE
|
|
58
|
+
modifying any code.
|
|
56
59
|
|
|
60
|
+
Do NOT write any implementation code until you have reported
|
|
61
|
+
your findings and the user has confirmed the content plan.`;
|
|
62
|
+
}
|
|
63
|
+
return `
|
|
57
64
|
\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
|
-
|
|
65
|
+
2. PHASE 0 \u2014 DISCOVERY (NEW SITE)
|
|
59
66
|
\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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
5. A migration/fallback strategy for current static pages.
|
|
66
|
-
6. A safe schema sync step using \`npx @dyrected/cli sync:schema\`.
|
|
67
|
+
This is a new project. Before designing the content model, ask:
|
|
68
|
+
- "What are your core content types? (e.g. Services, Team, Blog, Projects)"
|
|
69
|
+
- "Which pages should marketing manage independently with blocks?"
|
|
70
|
+
- "Are there any pages that must remain static and never become CMS-managed?"
|
|
71
|
+
- "What is the primary goal of the site \u2014 marketing, e-commerce, portfolio, SaaS?"
|
|
67
72
|
|
|
73
|
+
Do NOT write any code until the user has answered these questions.`;
|
|
74
|
+
}
|
|
75
|
+
function buildConstraintsSection() {
|
|
76
|
+
return `
|
|
68
77
|
\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
|
|
69
|
-
|
|
78
|
+
3. ARCHITECTURE & CONSTRAINTS
|
|
70
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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
- API ACCESS : Use client.collection(slug) as the only entry point.
|
|
81
|
+
- ZERO-STATE : Always use initialData in all data fetches so the site
|
|
82
|
+
renders correctly on first load and never throws during render.
|
|
83
|
+
- MARKETING FREEDOM : Use a dynamic pages collection with a catch-all route
|
|
84
|
+
so marketing can create and manage pages without a developer.
|
|
85
|
+
- BLOCKS DESIGN : Use blocks for flexible page builders. Store as
|
|
86
|
+
[{ blockType: 'slug', ...fields }] and switch on blockType
|
|
87
|
+
in the frontend renderer.
|
|
88
|
+
- DATA SAFETY : Never overwrite or drop existing content or pages.
|
|
89
|
+
Preserve everything before making changes.
|
|
90
|
+
- RESILIENCE : If Dyrected backend is unreachable, fall back to
|
|
91
|
+
initialData and show stale content \u2014 never an error page.
|
|
92
|
+
All relationship fields must handle null gracefully.
|
|
93
|
+
Every block renderer must have a default fallback case.`;
|
|
94
|
+
}
|
|
95
|
+
function buildSchemaRulesSection() {
|
|
96
|
+
return `
|
|
97
|
+
\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
|
|
98
|
+
4. SCHEMA EVOLUTION RULES
|
|
99
|
+
\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
|
|
100
|
+
- Never drop existing fields from the schema. Mark unused fields as deprecated only.
|
|
101
|
+
- All new fields must have a defaultValue.
|
|
102
|
+
- Never rename a field slug \u2014 add a new field and migrate data separately.
|
|
103
|
+
- Run npx @dyrected/cli sync:schema after every config change.`;
|
|
104
|
+
}
|
|
105
|
+
function buildDoNotSection() {
|
|
106
|
+
return `
|
|
107
|
+
\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
|
|
108
|
+
5. DO NOT
|
|
109
|
+
\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
|
|
110
|
+
- Do NOT use client.collections \u2014 always use client.collection(slug).
|
|
111
|
+
- Do NOT add custom auth middleware to the admin route.
|
|
112
|
+
Dyrected handles admin authentication internally. Do not wrap,
|
|
113
|
+
protect, or redirect the admin route yourself.
|
|
114
|
+
- Do NOT use renderAdminUI in a Nuxt project. Use the DyrectedAdmin
|
|
115
|
+
component which is auto-imported by @dyrected/nuxt.
|
|
116
|
+
- Do NOT modify or overwrite existing pages without first preserving their data.
|
|
117
|
+
- Do NOT drop, rename, or remove fields from an existing schema.
|
|
118
|
+
- Do NOT integrate blog posts or testimonials unless explicitly requested.
|
|
119
|
+
- Do NOT skip the diagnostic or discovery phase.`;
|
|
120
|
+
}
|
|
121
|
+
function buildTechnicalReferenceSection() {
|
|
122
|
+
return `
|
|
79
123
|
\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
|
-
|
|
124
|
+
6. TECHNICAL REFERENCE
|
|
81
125
|
\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
|
|
82
|
-
Use
|
|
126
|
+
Use defineCollection, defineGlobal, and defineConfig from '@dyrected/core'.
|
|
83
127
|
|
|
84
128
|
FIELD TYPES:
|
|
85
129
|
- Primitive : text | textarea | richText | number | boolean | date | email | url | json
|
|
86
|
-
- Choice : select | multiSelect
|
|
87
|
-
- Structural : array | object
|
|
88
|
-
- Relation : relationship
|
|
130
|
+
- Choice : select | multiSelect (requires options: [{ label, value }])
|
|
131
|
+
- Structural : array | object (requires nested fields: [...])
|
|
132
|
+
- Relation : relationship (requires collection: '<slug>')
|
|
89
133
|
- Media : relationship to an upload collection (e.g. 'media')
|
|
90
|
-
- Blocks : blocks
|
|
134
|
+
- Blocks : blocks (requires blocks: [{ slug, labels, fields }])
|
|
91
135
|
|
|
92
136
|
COLLECTION OPTIONS:
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
137
|
+
- upload: true \u2014 media library with file upload support
|
|
138
|
+
- auth: true \u2014 adds login/me endpoints; password field is auto-managed
|
|
139
|
+
- admin.useAsTitle \u2014 field used as display title in admin list view
|
|
140
|
+
- admin.group \u2014 groups collection under a sidebar heading
|
|
141
|
+
- admin.hidden \u2014 hides collection from the sidebar (internal/system use)
|
|
96
142
|
|
|
97
|
-
|
|
98
|
-
|
|
143
|
+
FIELD OPTIONS:
|
|
144
|
+
- required \u2014 validation
|
|
145
|
+
- unique \u2014 database-level uniqueness constraint
|
|
146
|
+
- defaultValue \u2014 fallback value (required on all new fields added to existing schemas)
|
|
147
|
+
- admin.condition \u2014 Jexl expression string to conditionally show/hide field
|
|
148
|
+
e.g. "status == \\"published\\""
|
|
149
|
+
- admin.readOnly \u2014 display only, not editable
|
|
150
|
+
- admin.hidden \u2014 hidden from editor UI entirely
|
|
151
|
+
- hooks.beforeChange \u2014 [async (value) => newValue] transform before save
|
|
152
|
+
- hooks.afterRead \u2014 [async (value) => newValue] transform after read
|
|
99
153
|
|
|
100
|
-
|
|
154
|
+
GLOBALS:
|
|
155
|
+
Use defineGlobal for single-instance documents like site settings or navigation.
|
|
156
|
+
Access via client.global(slug).get() and client.global(slug).update(data).
|
|
157
|
+
|
|
158
|
+
BLOCKS:
|
|
159
|
+
Blocks are stored as [{ blockType: '<slug>', ...fields }].
|
|
160
|
+
The admin renders a drag-and-drop block editor automatically.
|
|
161
|
+
On the frontend, iterate the array and switch on block.blockType.
|
|
162
|
+
Always include a default case in your switch for unknown block types.
|
|
163
|
+
|
|
164
|
+
COMPLETE SCHEMA EXAMPLE:
|
|
101
165
|
\`\`\`ts
|
|
102
|
-
import { defineCollection, defineConfig } from '@dyrected/core'
|
|
166
|
+
import { defineCollection, defineGlobal, defineConfig } from '@dyrected/core'
|
|
167
|
+
import { MongoAdapter } from '@dyrected/db-mongodb'
|
|
168
|
+
import { S3StorageAdapter } from '@dyrected/storage-s3'
|
|
103
169
|
|
|
104
170
|
const media = defineCollection({
|
|
105
171
|
slug: 'media',
|
|
106
172
|
upload: true,
|
|
107
|
-
fields: [
|
|
173
|
+
fields: [
|
|
174
|
+
{ name: 'alt', type: 'text', label: 'Alt Text' },
|
|
175
|
+
],
|
|
108
176
|
})
|
|
109
177
|
|
|
110
178
|
const pages = defineCollection({
|
|
111
179
|
slug: 'pages',
|
|
180
|
+
admin: { useAsTitle: 'title', group: 'Content' },
|
|
112
181
|
fields: [
|
|
113
182
|
{ name: 'title', type: 'text', required: true },
|
|
114
|
-
{ name: 'slug',
|
|
115
|
-
{ name: '
|
|
116
|
-
{
|
|
117
|
-
|
|
118
|
-
|
|
183
|
+
{ name: 'slug', type: 'text', required: true, unique: true },
|
|
184
|
+
{ name: 'seo', type: 'object', fields: [
|
|
185
|
+
{ name: 'metaTitle', type: 'text' },
|
|
186
|
+
{ name: 'metaDescription', type: 'textarea' },
|
|
187
|
+
{ name: 'ogImage', type: 'relationship', collection: 'media' },
|
|
188
|
+
]},
|
|
189
|
+
{
|
|
190
|
+
name: 'layout',
|
|
191
|
+
type: 'blocks',
|
|
192
|
+
blocks: [
|
|
193
|
+
{
|
|
194
|
+
slug: 'hero',
|
|
195
|
+
labels: { singular: 'Hero', plural: 'Heroes' },
|
|
196
|
+
fields: [
|
|
197
|
+
{ name: 'heading', type: 'text', required: true },
|
|
198
|
+
{ name: 'subheading', type: 'textarea' },
|
|
199
|
+
{ name: 'image', type: 'relationship', collection: 'media' },
|
|
200
|
+
{ name: 'ctaLabel', type: 'text' },
|
|
201
|
+
{ name: 'ctaLink', type: 'url' },
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
slug: 'richContent',
|
|
206
|
+
labels: { singular: 'Rich Content', plural: 'Rich Content Blocks' },
|
|
207
|
+
fields: [
|
|
208
|
+
{ name: 'content', type: 'richText', required: true },
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
slug: 'callToAction',
|
|
213
|
+
labels: { singular: 'Call to Action', plural: 'Calls to Action' },
|
|
214
|
+
fields: [
|
|
215
|
+
{ name: 'heading', type: 'text', required: true },
|
|
216
|
+
{ name: 'description', type: 'textarea' },
|
|
217
|
+
{ name: 'buttonLabel', type: 'text' },
|
|
218
|
+
{ name: 'buttonLink', type: 'url' },
|
|
219
|
+
{ name: 'theme', type: 'select', options: [
|
|
220
|
+
{ label: 'Primary', value: 'primary' },
|
|
221
|
+
{ label: 'Secondary', value: 'secondary' },
|
|
222
|
+
{ label: 'Dark', value: 'dark' },
|
|
223
|
+
]},
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
],
|
|
119
229
|
})
|
|
120
230
|
|
|
121
|
-
|
|
122
|
-
|
|
231
|
+
const settings = defineGlobal({
|
|
232
|
+
slug: 'settings',
|
|
233
|
+
label: 'Site Settings',
|
|
234
|
+
fields: [
|
|
235
|
+
{ name: 'siteName', type: 'text' },
|
|
236
|
+
{ name: 'tagline', type: 'text' },
|
|
237
|
+
{ name: 'logo', type: 'relationship', collection: 'media' },
|
|
238
|
+
{ name: 'footerText', type: 'textarea' },
|
|
239
|
+
],
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
export default defineConfig({
|
|
243
|
+
collections: [media, pages],
|
|
244
|
+
globals: [settings],
|
|
245
|
+
db: new MongoAdapter({
|
|
246
|
+
url: process.env.DATABASE_URL!,
|
|
247
|
+
dbName: 'dyrected_cms',
|
|
248
|
+
}),
|
|
249
|
+
storage: new S3StorageAdapter({
|
|
250
|
+
bucket: process.env.S3_BUCKET!,
|
|
251
|
+
region: process.env.S3_REGION!,
|
|
252
|
+
credentials: {
|
|
253
|
+
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
|
|
254
|
+
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
|
|
255
|
+
},
|
|
256
|
+
}),
|
|
257
|
+
})
|
|
258
|
+
\`\`\``;
|
|
259
|
+
}
|
|
260
|
+
function buildDeliverablesSection() {
|
|
261
|
+
return `
|
|
262
|
+
\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
|
|
263
|
+
7. REQUIRED DELIVERABLES \u2014 IN THIS ORDER
|
|
264
|
+
\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
|
|
265
|
+
Return your response in exactly this order. Do not combine steps. Do not skip steps.
|
|
123
266
|
|
|
267
|
+
1. Diagnostic findings (what exists, what needs to become CMS-managed)
|
|
268
|
+
2. dyrected.config.ts \u2014 complete file
|
|
269
|
+
3. Admin route file \u2014 complete file
|
|
270
|
+
4. Catch-all frontend page route \u2014 complete file
|
|
271
|
+
5. Block components \u2014 names and fields only
|
|
272
|
+
6. Migration/fallback strategy \u2014 numbered steps
|
|
273
|
+
7. Schema sync command
|
|
274
|
+
|
|
275
|
+
API Reference: https://docs.dyrected.com`;
|
|
276
|
+
}
|
|
277
|
+
function buildFrameworkSection(activeTab, isSelfHosted, config) {
|
|
278
|
+
const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
|
|
279
|
+
const credentialEnvLines = isSelfHosted ? "" : `
|
|
280
|
+
apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
|
|
281
|
+
siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`;
|
|
282
|
+
const baseUrlLine = `process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}'`;
|
|
283
|
+
const sections = {
|
|
284
|
+
next: `
|
|
124
285
|
\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
|
|
125
|
-
|
|
286
|
+
8. IMPLEMENTATION \u2014 Next.js
|
|
126
287
|
\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
|
|
127
|
-
|
|
128
|
-
const frameworks = {
|
|
129
|
-
next: `1. **SDK Setup** (\`lib/dyrected.ts\`):
|
|
288
|
+
1. SDK Setup (lib/dyrected.ts):
|
|
130
289
|
\`\`\`ts
|
|
131
290
|
import { createClient } from '@dyrected/sdk'
|
|
132
291
|
|
|
133
292
|
export const dyrected = createClient({
|
|
134
|
-
baseUrl:
|
|
135
|
-
apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
|
|
136
|
-
siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`}
|
|
293
|
+
baseUrl: ${baseUrlLine},${credentialEnvLines}
|
|
137
294
|
})
|
|
138
295
|
\`\`\`
|
|
139
296
|
|
|
140
|
-
2.
|
|
297
|
+
2. Admin Route (app/admin/[[...slug]]/page.tsx):
|
|
141
298
|
\`\`\`tsx
|
|
142
299
|
import { DyrectedAdmin } from '@dyrected/next/admin'
|
|
143
300
|
|
|
144
301
|
export default function AdminPage() {
|
|
145
|
-
// DyrectedAdmin handles
|
|
302
|
+
// DyrectedAdmin handles routing, auth, and CSS automatically.
|
|
303
|
+
// Do NOT wrap this in custom auth middleware.
|
|
146
304
|
return <DyrectedAdmin basename="/admin" />
|
|
147
305
|
}
|
|
148
306
|
\`\`\`
|
|
149
|
-
|
|
150
|
-
|
|
307
|
+
|
|
308
|
+
3. Catch-all Page Route (app/[...slug]/page.tsx):
|
|
309
|
+
\`\`\`tsx
|
|
310
|
+
import { dyrected } from '@/lib/dyrected'
|
|
311
|
+
|
|
312
|
+
export default async function CmsPage({ params }: { params: { slug: string[] } }) {
|
|
313
|
+
const slug = params.slug?.join('/') || 'home'
|
|
314
|
+
const page = await dyrected.collection('pages').findOne({ where: { slug: { equals: slug } } })
|
|
315
|
+
|
|
316
|
+
if (!page) return <div>Page not found</div>
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<main>
|
|
320
|
+
{page.layout?.map((block: any, i: number) => (
|
|
321
|
+
<BlockRenderer key={i} block={block} />
|
|
322
|
+
))}
|
|
323
|
+
</main>
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
\`\`\``,
|
|
327
|
+
nuxt: `
|
|
328
|
+
\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
|
|
329
|
+
8. IMPLEMENTATION \u2014 Nuxt
|
|
330
|
+
\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
|
|
331
|
+
1. Nuxt Config (nuxt.config.ts):
|
|
151
332
|
\`\`\`ts
|
|
152
333
|
export default defineNuxtConfig({
|
|
153
334
|
modules: ['@dyrected/nuxt'],
|
|
@@ -159,10 +340,13 @@ export default defineNuxtConfig({
|
|
|
159
340
|
})
|
|
160
341
|
\`\`\`
|
|
161
342
|
|
|
162
|
-
2.
|
|
343
|
+
2. Admin Route (pages/admin.vue):
|
|
163
344
|
\`\`\`vue
|
|
164
345
|
<script setup lang="ts">
|
|
165
|
-
// DyrectedAdmin is auto-imported by
|
|
346
|
+
// DyrectedAdmin is auto-imported by @dyrected/nuxt.
|
|
347
|
+
// Do NOT manually import it.
|
|
348
|
+
// Do NOT use renderAdminUI here.
|
|
349
|
+
// Do NOT add custom auth middleware \u2014 Dyrected handles authentication.
|
|
166
350
|
definePageMeta({ layout: false })
|
|
167
351
|
</script>
|
|
168
352
|
|
|
@@ -172,10 +356,39 @@ definePageMeta({ layout: false })
|
|
|
172
356
|
</ClientOnly>
|
|
173
357
|
</template>
|
|
174
358
|
\`\`\`
|
|
175
|
-
`,
|
|
176
|
-
react: `Install \`@dyrected/sdk\`:
|
|
177
359
|
|
|
178
|
-
|
|
360
|
+
3. Catch-all Page Route (pages/[...slug].vue):
|
|
361
|
+
\`\`\`vue
|
|
362
|
+
<script setup lang="ts">
|
|
363
|
+
const route = useRoute()
|
|
364
|
+
const slug = computed(() =>
|
|
365
|
+
Array.isArray(route.params.slug)
|
|
366
|
+
? route.params.slug.join('/')
|
|
367
|
+
: route.params.slug || 'home'
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
const { data: page } = await useDyrected('pages').findOne({
|
|
371
|
+
where: { slug: { equals: slug.value } },
|
|
372
|
+
initialData: null,
|
|
373
|
+
})
|
|
374
|
+
</script>
|
|
375
|
+
|
|
376
|
+
<template>
|
|
377
|
+
<main v-if="page">
|
|
378
|
+
<BlockRenderer
|
|
379
|
+
v-for="(block, i) in page.layout"
|
|
380
|
+
:key="i"
|
|
381
|
+
:block="block"
|
|
382
|
+
/>
|
|
383
|
+
</main>
|
|
384
|
+
<div v-else>Page not found</div>
|
|
385
|
+
</template>
|
|
386
|
+
\`\`\``,
|
|
387
|
+
react: `
|
|
388
|
+
\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
|
|
389
|
+
8. IMPLEMENTATION \u2014 React
|
|
390
|
+
\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
|
|
391
|
+
1. SDK Setup (lib/dyrected.ts):
|
|
179
392
|
\`\`\`ts
|
|
180
393
|
import { createClient } from '@dyrected/sdk'
|
|
181
394
|
|
|
@@ -186,7 +399,7 @@ export const dyrected = createClient({
|
|
|
186
399
|
})
|
|
187
400
|
\`\`\`
|
|
188
401
|
|
|
189
|
-
|
|
402
|
+
2. Admin Route (pages/admin.tsx):
|
|
190
403
|
\`\`\`tsx
|
|
191
404
|
import { AdminUI } from '@dyrected/admin'
|
|
192
405
|
import '@dyrected/admin/styles'
|
|
@@ -194,19 +407,20 @@ import '@dyrected/admin/styles'
|
|
|
194
407
|
export default function AdminPage() {
|
|
195
408
|
return (
|
|
196
409
|
<div style={{ height: '100vh' }}>
|
|
197
|
-
<AdminUI
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
410
|
+
<AdminUI
|
|
411
|
+
baseUrl="${config.baseUrl || "https://api.dyrected.cloud"}"${isSelfHosted ? "" : `
|
|
412
|
+
apiKey="${config.apiKey}"
|
|
413
|
+
siteId="${config.siteId}"`}
|
|
201
414
|
/>
|
|
202
415
|
</div>
|
|
203
416
|
)
|
|
204
417
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
418
|
+
\`\`\``,
|
|
419
|
+
vue: `
|
|
420
|
+
\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
|
|
421
|
+
8. IMPLEMENTATION \u2014 Vue
|
|
422
|
+
\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
|
|
423
|
+
1. SDK Setup (lib/dyrected.ts):
|
|
210
424
|
\`\`\`ts
|
|
211
425
|
import { createClient } from '@dyrected/sdk'
|
|
212
426
|
|
|
@@ -217,31 +431,50 @@ export const dyrected = createClient({
|
|
|
217
431
|
})
|
|
218
432
|
\`\`\`
|
|
219
433
|
|
|
220
|
-
|
|
434
|
+
2. Admin Route (pages/admin.vue):
|
|
221
435
|
\`\`\`vue
|
|
222
436
|
<template>
|
|
223
437
|
<div ref="container" style="height: 100vh" />
|
|
224
438
|
</template>
|
|
225
439
|
|
|
226
440
|
<script setup>
|
|
227
|
-
import { ref, onMounted } from 'vue'
|
|
441
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
228
442
|
import { renderAdminUI } from '@dyrected/admin'
|
|
229
443
|
import '@dyrected/admin/styles'
|
|
230
444
|
|
|
231
445
|
const container = ref(null)
|
|
446
|
+
let cleanup: (() => void) | undefined
|
|
447
|
+
|
|
232
448
|
onMounted(() => {
|
|
233
|
-
renderAdminUI(container.value, {
|
|
449
|
+
cleanup = renderAdminUI(container.value, {
|
|
450
|
+
baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
|
|
234
451
|
apiKey: '${config.apiKey}',
|
|
235
|
-
siteId: '${config.siteId}'
|
|
236
|
-
baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}'
|
|
452
|
+
siteId: '${config.siteId}',`}
|
|
237
453
|
})
|
|
238
454
|
})
|
|
455
|
+
|
|
456
|
+
onUnmounted(() => cleanup?.())
|
|
239
457
|
</script>
|
|
240
|
-
|
|
241
|
-
`
|
|
458
|
+
\`\`\``
|
|
242
459
|
};
|
|
243
|
-
return
|
|
244
|
-
|
|
460
|
+
return sections[activeTab] || sections.next;
|
|
461
|
+
}
|
|
462
|
+
function generateAIPrompt(activeTab, config) {
|
|
463
|
+
const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt" : activeTab.charAt(0).toUpperCase() + activeTab.slice(1);
|
|
464
|
+
const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
|
|
465
|
+
const existingSite = config.existingSite ?? false;
|
|
466
|
+
const sections = [
|
|
467
|
+
`You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a ${frameworkLabel} project. Your priority is DATA PRESERVATION and creating a CMS that empowers marketing teams to move independently without raising tickets to engineering.`,
|
|
468
|
+
buildEnvironmentSection(frameworkLabel, isSelfHosted, config),
|
|
469
|
+
buildDiagnosticSection(existingSite),
|
|
470
|
+
buildConstraintsSection(),
|
|
471
|
+
buildSchemaRulesSection(),
|
|
472
|
+
buildDoNotSection(),
|
|
473
|
+
buildTechnicalReferenceSection(),
|
|
474
|
+
buildDeliverablesSection(),
|
|
475
|
+
buildFrameworkSection(activeTab, isSelfHosted, config)
|
|
476
|
+
];
|
|
477
|
+
return sections.join("\n");
|
|
245
478
|
}
|
|
246
479
|
|
|
247
480
|
// src/utils/config.ts
|
package/dist/index.js
CHANGED
|
@@ -3,125 +3,306 @@ import {
|
|
|
3
3
|
} from "./chunk-GM4WW6IE.js";
|
|
4
4
|
|
|
5
5
|
// src/utils/setup-prompt.ts
|
|
6
|
-
function
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
6
|
+
function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
|
|
7
|
+
const credentialLines = isSelfHosted ? "" : `- Site ID : ${config.siteId}
|
|
8
|
+
- API Key : ${config.apiKey}`;
|
|
9
|
+
return `
|
|
12
10
|
\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
|
|
13
11
|
1. ENVIRONMENT
|
|
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
|
-
- Framework: ${frameworkLabel}
|
|
16
|
-
- Host Type: ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
|
|
17
|
-
- API Base
|
|
18
|
-
${
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
- Framework : ${frameworkLabel}
|
|
14
|
+
- Host Type : ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
|
|
15
|
+
- API Base : ${config.baseUrl || "http://localhost:3000"}
|
|
16
|
+
${credentialLines}`;
|
|
17
|
+
}
|
|
18
|
+
function buildDiagnosticSection(existingSite) {
|
|
19
|
+
if (existingSite) {
|
|
20
|
+
return `
|
|
21
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
|
|
22
|
-
2.
|
|
22
|
+
2. PHASE 0 \u2014 DIAGNOSTIC (EXISTING SITE)
|
|
23
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
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
- NO DEPRECATIONS: Use the framework-specific \`DyrectedAdmin\` components (Next/Nuxt) which handle routing and CSS automatically.
|
|
24
|
+
Before writing any code, scan the existing codebase and report:
|
|
25
|
+
- All hardcoded text strings that should be CMS-managed
|
|
26
|
+
- All repeated data structures that should become collections
|
|
27
|
+
- All static pages that marketing will want to edit independently
|
|
28
|
+
- Any existing fetch or API calls that overlap with Dyrected
|
|
30
29
|
|
|
30
|
+
Then propose a backup plan: extract all current content into a
|
|
31
|
+
migration/ folder as structured .md or .json files BEFORE
|
|
32
|
+
modifying any code.
|
|
33
|
+
|
|
34
|
+
Do NOT write any implementation code until you have reported
|
|
35
|
+
your findings and the user has confirmed the content plan.`;
|
|
36
|
+
}
|
|
37
|
+
return `
|
|
31
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
|
|
32
|
-
|
|
39
|
+
2. PHASE 0 \u2014 DISCOVERY (NEW SITE)
|
|
33
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
5. A migration/fallback strategy for current static pages.
|
|
40
|
-
6. A safe schema sync step using \`npx @dyrected/cli sync:schema\`.
|
|
41
|
+
This is a new project. Before designing the content model, ask:
|
|
42
|
+
- "What are your core content types? (e.g. Services, Team, Blog, Projects)"
|
|
43
|
+
- "Which pages should marketing manage independently with blocks?"
|
|
44
|
+
- "Are there any pages that must remain static and never become CMS-managed?"
|
|
45
|
+
- "What is the primary goal of the site \u2014 marketing, e-commerce, portfolio, SaaS?"
|
|
41
46
|
|
|
47
|
+
Do NOT write any code until the user has answered these questions.`;
|
|
48
|
+
}
|
|
49
|
+
function buildConstraintsSection() {
|
|
50
|
+
return `
|
|
42
51
|
\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
|
-
|
|
52
|
+
3. ARCHITECTURE & CONSTRAINTS
|
|
44
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
- API ACCESS : Use client.collection(slug) as the only entry point.
|
|
55
|
+
- ZERO-STATE : Always use initialData in all data fetches so the site
|
|
56
|
+
renders correctly on first load and never throws during render.
|
|
57
|
+
- MARKETING FREEDOM : Use a dynamic pages collection with a catch-all route
|
|
58
|
+
so marketing can create and manage pages without a developer.
|
|
59
|
+
- BLOCKS DESIGN : Use blocks for flexible page builders. Store as
|
|
60
|
+
[{ blockType: 'slug', ...fields }] and switch on blockType
|
|
61
|
+
in the frontend renderer.
|
|
62
|
+
- DATA SAFETY : Never overwrite or drop existing content or pages.
|
|
63
|
+
Preserve everything before making changes.
|
|
64
|
+
- RESILIENCE : If Dyrected backend is unreachable, fall back to
|
|
65
|
+
initialData and show stale content \u2014 never an error page.
|
|
66
|
+
All relationship fields must handle null gracefully.
|
|
67
|
+
Every block renderer must have a default fallback case.`;
|
|
68
|
+
}
|
|
69
|
+
function buildSchemaRulesSection() {
|
|
70
|
+
return `
|
|
71
|
+
\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
|
|
72
|
+
4. SCHEMA EVOLUTION RULES
|
|
73
|
+
\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
|
+
- Never drop existing fields from the schema. Mark unused fields as deprecated only.
|
|
75
|
+
- All new fields must have a defaultValue.
|
|
76
|
+
- Never rename a field slug \u2014 add a new field and migrate data separately.
|
|
77
|
+
- Run npx @dyrected/cli sync:schema after every config change.`;
|
|
78
|
+
}
|
|
79
|
+
function buildDoNotSection() {
|
|
80
|
+
return `
|
|
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
|
|
82
|
+
5. DO NOT
|
|
83
|
+
\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
|
|
84
|
+
- Do NOT use client.collections \u2014 always use client.collection(slug).
|
|
85
|
+
- Do NOT add custom auth middleware to the admin route.
|
|
86
|
+
Dyrected handles admin authentication internally. Do not wrap,
|
|
87
|
+
protect, or redirect the admin route yourself.
|
|
88
|
+
- Do NOT use renderAdminUI in a Nuxt project. Use the DyrectedAdmin
|
|
89
|
+
component which is auto-imported by @dyrected/nuxt.
|
|
90
|
+
- Do NOT modify or overwrite existing pages without first preserving their data.
|
|
91
|
+
- Do NOT drop, rename, or remove fields from an existing schema.
|
|
92
|
+
- Do NOT integrate blog posts or testimonials unless explicitly requested.
|
|
93
|
+
- Do NOT skip the diagnostic or discovery phase.`;
|
|
94
|
+
}
|
|
95
|
+
function buildTechnicalReferenceSection() {
|
|
96
|
+
return `
|
|
53
97
|
\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
|
-
|
|
98
|
+
6. TECHNICAL REFERENCE
|
|
55
99
|
\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
|
|
56
|
-
Use
|
|
100
|
+
Use defineCollection, defineGlobal, and defineConfig from '@dyrected/core'.
|
|
57
101
|
|
|
58
102
|
FIELD TYPES:
|
|
59
103
|
- Primitive : text | textarea | richText | number | boolean | date | email | url | json
|
|
60
|
-
- Choice : select | multiSelect
|
|
61
|
-
- Structural : array | object
|
|
62
|
-
- Relation : relationship
|
|
104
|
+
- Choice : select | multiSelect (requires options: [{ label, value }])
|
|
105
|
+
- Structural : array | object (requires nested fields: [...])
|
|
106
|
+
- Relation : relationship (requires collection: '<slug>')
|
|
63
107
|
- Media : relationship to an upload collection (e.g. 'media')
|
|
64
|
-
- Blocks : blocks
|
|
108
|
+
- Blocks : blocks (requires blocks: [{ slug, labels, fields }])
|
|
65
109
|
|
|
66
110
|
COLLECTION OPTIONS:
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
111
|
+
- upload: true \u2014 media library with file upload support
|
|
112
|
+
- auth: true \u2014 adds login/me endpoints; password field is auto-managed
|
|
113
|
+
- admin.useAsTitle \u2014 field used as display title in admin list view
|
|
114
|
+
- admin.group \u2014 groups collection under a sidebar heading
|
|
115
|
+
- admin.hidden \u2014 hides collection from the sidebar (internal/system use)
|
|
70
116
|
|
|
71
|
-
|
|
72
|
-
|
|
117
|
+
FIELD OPTIONS:
|
|
118
|
+
- required \u2014 validation
|
|
119
|
+
- unique \u2014 database-level uniqueness constraint
|
|
120
|
+
- defaultValue \u2014 fallback value (required on all new fields added to existing schemas)
|
|
121
|
+
- admin.condition \u2014 Jexl expression string to conditionally show/hide field
|
|
122
|
+
e.g. "status == \\"published\\""
|
|
123
|
+
- admin.readOnly \u2014 display only, not editable
|
|
124
|
+
- admin.hidden \u2014 hidden from editor UI entirely
|
|
125
|
+
- hooks.beforeChange \u2014 [async (value) => newValue] transform before save
|
|
126
|
+
- hooks.afterRead \u2014 [async (value) => newValue] transform after read
|
|
73
127
|
|
|
74
|
-
|
|
128
|
+
GLOBALS:
|
|
129
|
+
Use defineGlobal for single-instance documents like site settings or navigation.
|
|
130
|
+
Access via client.global(slug).get() and client.global(slug).update(data).
|
|
131
|
+
|
|
132
|
+
BLOCKS:
|
|
133
|
+
Blocks are stored as [{ blockType: '<slug>', ...fields }].
|
|
134
|
+
The admin renders a drag-and-drop block editor automatically.
|
|
135
|
+
On the frontend, iterate the array and switch on block.blockType.
|
|
136
|
+
Always include a default case in your switch for unknown block types.
|
|
137
|
+
|
|
138
|
+
COMPLETE SCHEMA EXAMPLE:
|
|
75
139
|
\`\`\`ts
|
|
76
|
-
import { defineCollection, defineConfig } from '@dyrected/core'
|
|
140
|
+
import { defineCollection, defineGlobal, defineConfig } from '@dyrected/core'
|
|
141
|
+
import { MongoAdapter } from '@dyrected/db-mongodb'
|
|
142
|
+
import { S3StorageAdapter } from '@dyrected/storage-s3'
|
|
77
143
|
|
|
78
144
|
const media = defineCollection({
|
|
79
145
|
slug: 'media',
|
|
80
146
|
upload: true,
|
|
81
|
-
fields: [
|
|
147
|
+
fields: [
|
|
148
|
+
{ name: 'alt', type: 'text', label: 'Alt Text' },
|
|
149
|
+
],
|
|
82
150
|
})
|
|
83
151
|
|
|
84
152
|
const pages = defineCollection({
|
|
85
153
|
slug: 'pages',
|
|
154
|
+
admin: { useAsTitle: 'title', group: 'Content' },
|
|
86
155
|
fields: [
|
|
87
156
|
{ name: 'title', type: 'text', required: true },
|
|
88
|
-
{ name: 'slug',
|
|
89
|
-
{ name: '
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
|
|
157
|
+
{ name: 'slug', type: 'text', required: true, unique: true },
|
|
158
|
+
{ name: 'seo', type: 'object', fields: [
|
|
159
|
+
{ name: 'metaTitle', type: 'text' },
|
|
160
|
+
{ name: 'metaDescription', type: 'textarea' },
|
|
161
|
+
{ name: 'ogImage', type: 'relationship', collection: 'media' },
|
|
162
|
+
]},
|
|
163
|
+
{
|
|
164
|
+
name: 'layout',
|
|
165
|
+
type: 'blocks',
|
|
166
|
+
blocks: [
|
|
167
|
+
{
|
|
168
|
+
slug: 'hero',
|
|
169
|
+
labels: { singular: 'Hero', plural: 'Heroes' },
|
|
170
|
+
fields: [
|
|
171
|
+
{ name: 'heading', type: 'text', required: true },
|
|
172
|
+
{ name: 'subheading', type: 'textarea' },
|
|
173
|
+
{ name: 'image', type: 'relationship', collection: 'media' },
|
|
174
|
+
{ name: 'ctaLabel', type: 'text' },
|
|
175
|
+
{ name: 'ctaLink', type: 'url' },
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
slug: 'richContent',
|
|
180
|
+
labels: { singular: 'Rich Content', plural: 'Rich Content Blocks' },
|
|
181
|
+
fields: [
|
|
182
|
+
{ name: 'content', type: 'richText', required: true },
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
slug: 'callToAction',
|
|
187
|
+
labels: { singular: 'Call to Action', plural: 'Calls to Action' },
|
|
188
|
+
fields: [
|
|
189
|
+
{ name: 'heading', type: 'text', required: true },
|
|
190
|
+
{ name: 'description', type: 'textarea' },
|
|
191
|
+
{ name: 'buttonLabel', type: 'text' },
|
|
192
|
+
{ name: 'buttonLink', type: 'url' },
|
|
193
|
+
{ name: 'theme', type: 'select', options: [
|
|
194
|
+
{ label: 'Primary', value: 'primary' },
|
|
195
|
+
{ label: 'Secondary', value: 'secondary' },
|
|
196
|
+
{ label: 'Dark', value: 'dark' },
|
|
197
|
+
]},
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
],
|
|
93
203
|
})
|
|
94
204
|
|
|
95
|
-
|
|
96
|
-
|
|
205
|
+
const settings = defineGlobal({
|
|
206
|
+
slug: 'settings',
|
|
207
|
+
label: 'Site Settings',
|
|
208
|
+
fields: [
|
|
209
|
+
{ name: 'siteName', type: 'text' },
|
|
210
|
+
{ name: 'tagline', type: 'text' },
|
|
211
|
+
{ name: 'logo', type: 'relationship', collection: 'media' },
|
|
212
|
+
{ name: 'footerText', type: 'textarea' },
|
|
213
|
+
],
|
|
214
|
+
})
|
|
97
215
|
|
|
216
|
+
export default defineConfig({
|
|
217
|
+
collections: [media, pages],
|
|
218
|
+
globals: [settings],
|
|
219
|
+
db: new MongoAdapter({
|
|
220
|
+
url: process.env.DATABASE_URL!,
|
|
221
|
+
dbName: 'dyrected_cms',
|
|
222
|
+
}),
|
|
223
|
+
storage: new S3StorageAdapter({
|
|
224
|
+
bucket: process.env.S3_BUCKET!,
|
|
225
|
+
region: process.env.S3_REGION!,
|
|
226
|
+
credentials: {
|
|
227
|
+
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
|
|
228
|
+
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
|
|
229
|
+
},
|
|
230
|
+
}),
|
|
231
|
+
})
|
|
232
|
+
\`\`\``;
|
|
233
|
+
}
|
|
234
|
+
function buildDeliverablesSection() {
|
|
235
|
+
return `
|
|
236
|
+
\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
|
|
237
|
+
7. REQUIRED DELIVERABLES \u2014 IN THIS ORDER
|
|
238
|
+
\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
|
|
239
|
+
Return your response in exactly this order. Do not combine steps. Do not skip steps.
|
|
240
|
+
|
|
241
|
+
1. Diagnostic findings (what exists, what needs to become CMS-managed)
|
|
242
|
+
2. dyrected.config.ts \u2014 complete file
|
|
243
|
+
3. Admin route file \u2014 complete file
|
|
244
|
+
4. Catch-all frontend page route \u2014 complete file
|
|
245
|
+
5. Block components \u2014 names and fields only
|
|
246
|
+
6. Migration/fallback strategy \u2014 numbered steps
|
|
247
|
+
7. Schema sync command
|
|
248
|
+
|
|
249
|
+
API Reference: https://docs.dyrected.com`;
|
|
250
|
+
}
|
|
251
|
+
function buildFrameworkSection(activeTab, isSelfHosted, config) {
|
|
252
|
+
const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
|
|
253
|
+
const credentialEnvLines = isSelfHosted ? "" : `
|
|
254
|
+
apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
|
|
255
|
+
siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`;
|
|
256
|
+
const baseUrlLine = `process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}'`;
|
|
257
|
+
const sections = {
|
|
258
|
+
next: `
|
|
98
259
|
\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
|
|
99
|
-
|
|
260
|
+
8. IMPLEMENTATION \u2014 Next.js
|
|
100
261
|
\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
|
|
101
|
-
|
|
102
|
-
const frameworks = {
|
|
103
|
-
next: `1. **SDK Setup** (\`lib/dyrected.ts\`):
|
|
262
|
+
1. SDK Setup (lib/dyrected.ts):
|
|
104
263
|
\`\`\`ts
|
|
105
264
|
import { createClient } from '@dyrected/sdk'
|
|
106
265
|
|
|
107
266
|
export const dyrected = createClient({
|
|
108
|
-
baseUrl:
|
|
109
|
-
apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
|
|
110
|
-
siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`}
|
|
267
|
+
baseUrl: ${baseUrlLine},${credentialEnvLines}
|
|
111
268
|
})
|
|
112
269
|
\`\`\`
|
|
113
270
|
|
|
114
|
-
2.
|
|
271
|
+
2. Admin Route (app/admin/[[...slug]]/page.tsx):
|
|
115
272
|
\`\`\`tsx
|
|
116
273
|
import { DyrectedAdmin } from '@dyrected/next/admin'
|
|
117
274
|
|
|
118
275
|
export default function AdminPage() {
|
|
119
|
-
// DyrectedAdmin handles
|
|
276
|
+
// DyrectedAdmin handles routing, auth, and CSS automatically.
|
|
277
|
+
// Do NOT wrap this in custom auth middleware.
|
|
120
278
|
return <DyrectedAdmin basename="/admin" />
|
|
121
279
|
}
|
|
122
280
|
\`\`\`
|
|
123
|
-
|
|
124
|
-
|
|
281
|
+
|
|
282
|
+
3. Catch-all Page Route (app/[...slug]/page.tsx):
|
|
283
|
+
\`\`\`tsx
|
|
284
|
+
import { dyrected } from '@/lib/dyrected'
|
|
285
|
+
|
|
286
|
+
export default async function CmsPage({ params }: { params: { slug: string[] } }) {
|
|
287
|
+
const slug = params.slug?.join('/') || 'home'
|
|
288
|
+
const page = await dyrected.collection('pages').findOne({ where: { slug: { equals: slug } } })
|
|
289
|
+
|
|
290
|
+
if (!page) return <div>Page not found</div>
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<main>
|
|
294
|
+
{page.layout?.map((block: any, i: number) => (
|
|
295
|
+
<BlockRenderer key={i} block={block} />
|
|
296
|
+
))}
|
|
297
|
+
</main>
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
\`\`\``,
|
|
301
|
+
nuxt: `
|
|
302
|
+
\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
|
|
303
|
+
8. IMPLEMENTATION \u2014 Nuxt
|
|
304
|
+
\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
|
|
305
|
+
1. Nuxt Config (nuxt.config.ts):
|
|
125
306
|
\`\`\`ts
|
|
126
307
|
export default defineNuxtConfig({
|
|
127
308
|
modules: ['@dyrected/nuxt'],
|
|
@@ -133,10 +314,13 @@ export default defineNuxtConfig({
|
|
|
133
314
|
})
|
|
134
315
|
\`\`\`
|
|
135
316
|
|
|
136
|
-
2.
|
|
317
|
+
2. Admin Route (pages/admin.vue):
|
|
137
318
|
\`\`\`vue
|
|
138
319
|
<script setup lang="ts">
|
|
139
|
-
// DyrectedAdmin is auto-imported by
|
|
320
|
+
// DyrectedAdmin is auto-imported by @dyrected/nuxt.
|
|
321
|
+
// Do NOT manually import it.
|
|
322
|
+
// Do NOT use renderAdminUI here.
|
|
323
|
+
// Do NOT add custom auth middleware \u2014 Dyrected handles authentication.
|
|
140
324
|
definePageMeta({ layout: false })
|
|
141
325
|
</script>
|
|
142
326
|
|
|
@@ -146,10 +330,39 @@ definePageMeta({ layout: false })
|
|
|
146
330
|
</ClientOnly>
|
|
147
331
|
</template>
|
|
148
332
|
\`\`\`
|
|
149
|
-
`,
|
|
150
|
-
react: `Install \`@dyrected/sdk\`:
|
|
151
333
|
|
|
152
|
-
|
|
334
|
+
3. Catch-all Page Route (pages/[...slug].vue):
|
|
335
|
+
\`\`\`vue
|
|
336
|
+
<script setup lang="ts">
|
|
337
|
+
const route = useRoute()
|
|
338
|
+
const slug = computed(() =>
|
|
339
|
+
Array.isArray(route.params.slug)
|
|
340
|
+
? route.params.slug.join('/')
|
|
341
|
+
: route.params.slug || 'home'
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
const { data: page } = await useDyrected('pages').findOne({
|
|
345
|
+
where: { slug: { equals: slug.value } },
|
|
346
|
+
initialData: null,
|
|
347
|
+
})
|
|
348
|
+
</script>
|
|
349
|
+
|
|
350
|
+
<template>
|
|
351
|
+
<main v-if="page">
|
|
352
|
+
<BlockRenderer
|
|
353
|
+
v-for="(block, i) in page.layout"
|
|
354
|
+
:key="i"
|
|
355
|
+
:block="block"
|
|
356
|
+
/>
|
|
357
|
+
</main>
|
|
358
|
+
<div v-else>Page not found</div>
|
|
359
|
+
</template>
|
|
360
|
+
\`\`\``,
|
|
361
|
+
react: `
|
|
362
|
+
\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
|
|
363
|
+
8. IMPLEMENTATION \u2014 React
|
|
364
|
+
\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
|
|
365
|
+
1. SDK Setup (lib/dyrected.ts):
|
|
153
366
|
\`\`\`ts
|
|
154
367
|
import { createClient } from '@dyrected/sdk'
|
|
155
368
|
|
|
@@ -160,7 +373,7 @@ export const dyrected = createClient({
|
|
|
160
373
|
})
|
|
161
374
|
\`\`\`
|
|
162
375
|
|
|
163
|
-
|
|
376
|
+
2. Admin Route (pages/admin.tsx):
|
|
164
377
|
\`\`\`tsx
|
|
165
378
|
import { AdminUI } from '@dyrected/admin'
|
|
166
379
|
import '@dyrected/admin/styles'
|
|
@@ -168,19 +381,20 @@ import '@dyrected/admin/styles'
|
|
|
168
381
|
export default function AdminPage() {
|
|
169
382
|
return (
|
|
170
383
|
<div style={{ height: '100vh' }}>
|
|
171
|
-
<AdminUI
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
384
|
+
<AdminUI
|
|
385
|
+
baseUrl="${config.baseUrl || "https://api.dyrected.cloud"}"${isSelfHosted ? "" : `
|
|
386
|
+
apiKey="${config.apiKey}"
|
|
387
|
+
siteId="${config.siteId}"`}
|
|
175
388
|
/>
|
|
176
389
|
</div>
|
|
177
390
|
)
|
|
178
391
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
392
|
+
\`\`\``,
|
|
393
|
+
vue: `
|
|
394
|
+
\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
|
|
395
|
+
8. IMPLEMENTATION \u2014 Vue
|
|
396
|
+
\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
|
|
397
|
+
1. SDK Setup (lib/dyrected.ts):
|
|
184
398
|
\`\`\`ts
|
|
185
399
|
import { createClient } from '@dyrected/sdk'
|
|
186
400
|
|
|
@@ -191,31 +405,50 @@ export const dyrected = createClient({
|
|
|
191
405
|
})
|
|
192
406
|
\`\`\`
|
|
193
407
|
|
|
194
|
-
|
|
408
|
+
2. Admin Route (pages/admin.vue):
|
|
195
409
|
\`\`\`vue
|
|
196
410
|
<template>
|
|
197
411
|
<div ref="container" style="height: 100vh" />
|
|
198
412
|
</template>
|
|
199
413
|
|
|
200
414
|
<script setup>
|
|
201
|
-
import { ref, onMounted } from 'vue'
|
|
415
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
202
416
|
import { renderAdminUI } from '@dyrected/admin'
|
|
203
417
|
import '@dyrected/admin/styles'
|
|
204
418
|
|
|
205
419
|
const container = ref(null)
|
|
420
|
+
let cleanup: (() => void) | undefined
|
|
421
|
+
|
|
206
422
|
onMounted(() => {
|
|
207
|
-
renderAdminUI(container.value, {
|
|
423
|
+
cleanup = renderAdminUI(container.value, {
|
|
424
|
+
baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
|
|
208
425
|
apiKey: '${config.apiKey}',
|
|
209
|
-
siteId: '${config.siteId}'
|
|
210
|
-
baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}'
|
|
426
|
+
siteId: '${config.siteId}',`}
|
|
211
427
|
})
|
|
212
428
|
})
|
|
429
|
+
|
|
430
|
+
onUnmounted(() => cleanup?.())
|
|
213
431
|
</script>
|
|
214
|
-
|
|
215
|
-
`
|
|
432
|
+
\`\`\``
|
|
216
433
|
};
|
|
217
|
-
return
|
|
218
|
-
|
|
434
|
+
return sections[activeTab] || sections.next;
|
|
435
|
+
}
|
|
436
|
+
function generateAIPrompt(activeTab, config) {
|
|
437
|
+
const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt" : activeTab.charAt(0).toUpperCase() + activeTab.slice(1);
|
|
438
|
+
const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
|
|
439
|
+
const existingSite = config.existingSite ?? false;
|
|
440
|
+
const sections = [
|
|
441
|
+
`You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a ${frameworkLabel} project. Your priority is DATA PRESERVATION and creating a CMS that empowers marketing teams to move independently without raising tickets to engineering.`,
|
|
442
|
+
buildEnvironmentSection(frameworkLabel, isSelfHosted, config),
|
|
443
|
+
buildDiagnosticSection(existingSite),
|
|
444
|
+
buildConstraintsSection(),
|
|
445
|
+
buildSchemaRulesSection(),
|
|
446
|
+
buildDoNotSection(),
|
|
447
|
+
buildTechnicalReferenceSection(),
|
|
448
|
+
buildDeliverablesSection(),
|
|
449
|
+
buildFrameworkSection(activeTab, isSelfHosted, config)
|
|
450
|
+
];
|
|
451
|
+
return sections.join("\n");
|
|
219
452
|
}
|
|
220
453
|
|
|
221
454
|
// src/index.ts
|