@dyrected/sdk 2.0.0 → 2.4.1
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 +835 -11
- package/dist/index.d.cts +21 -4
- package/dist/index.d.ts +21 -4
- package/dist/index.js +832 -10
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -43,6 +43,818 @@ var QueryBuilder = class {
|
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
// src/utils/setup-prompt.ts
|
|
47
|
+
function buildEnvironmentSection(frameworkLabel, isSelfHosted, config) {
|
|
48
|
+
const credentialLines = isSelfHosted ? "" : `- Site ID : ${config.siteId}
|
|
49
|
+
- API Key : ${config.apiKey}`;
|
|
50
|
+
return `
|
|
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
|
|
52
|
+
1. ENVIRONMENT
|
|
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
|
+
- Framework : ${frameworkLabel || "Detect it"}
|
|
55
|
+
- Host Type : ${isSelfHosted ? "Self-Hosted (Local/Private Server)" : "Managed (Dyrected Cloud)"}
|
|
56
|
+
- API Base : ${config.baseUrl || "http://localhost:3000"}
|
|
57
|
+
${credentialLines}`;
|
|
58
|
+
}
|
|
59
|
+
function buildDiagnosticSection() {
|
|
60
|
+
return `
|
|
61
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
62
|
+
2. PHASE 0 \u2014 DISCOVERY
|
|
63
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\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
|
+
Before writing any code, you MUST ask the user these questions.
|
|
65
|
+
Write them exactly as shown below \u2014 in plain language with examples.
|
|
66
|
+
Wait for the user to answer ALL of them before proceeding.
|
|
67
|
+
|
|
68
|
+
\u2500\u2500\u2500 QUESTION 1 \u2500\u2500\u2500
|
|
69
|
+
Ask:
|
|
70
|
+
"Do you already have a website built, or are we starting fresh?
|
|
71
|
+
|
|
72
|
+
\u2192 Already built: share your project or describe what pages you have
|
|
73
|
+
(e.g. Home, About, Services, Contact)
|
|
74
|
+
\u2192 Starting fresh: just say 'new site' and describe what the site is for"
|
|
75
|
+
|
|
76
|
+
\u2500\u2500\u2500 QUESTION 2 \u2500\u2500\u2500
|
|
77
|
+
Ask:
|
|
78
|
+
"What kind of content will your client need to update regularly?
|
|
79
|
+
|
|
80
|
+
Here are some examples to help you think:
|
|
81
|
+
\u2192 Blog posts or news articles
|
|
82
|
+
\u2192 Team member profiles (name, photo, bio)
|
|
83
|
+
\u2192 Services or product descriptions
|
|
84
|
+
\u2192 Testimonials or reviews
|
|
85
|
+
\u2192 Event listings
|
|
86
|
+
\u2192 FAQs
|
|
87
|
+
\u2192 Homepage text (headline, hero image, call-to-action button)
|
|
88
|
+
|
|
89
|
+
Just list the ones that apply. You can say things like:
|
|
90
|
+
'They need to update blog posts and team members'"
|
|
91
|
+
|
|
92
|
+
\u2500\u2500\u2500 QUESTION 3 \u2500\u2500\u2500
|
|
93
|
+
Ask:
|
|
94
|
+
"Are there any pages on the site that should NEVER change \u2014 like a
|
|
95
|
+
custom-coded page you want to leave exactly as it is?
|
|
96
|
+
|
|
97
|
+
Example answer: 'The homepage has a custom animation, leave that alone.
|
|
98
|
+
Everything else can be managed from the CMS.'
|
|
99
|
+
|
|
100
|
+
If everything should be manageable, just say 'all pages'"
|
|
101
|
+
|
|
102
|
+
\u2500\u2500\u2500 QUESTION 4 \u2500\u2500\u2500
|
|
103
|
+
Ask:
|
|
104
|
+
"What is this website for? Pick the closest description:
|
|
105
|
+
|
|
106
|
+
A) A business or agency marketing site (show services, get leads)
|
|
107
|
+
B) A blog or content site (publish articles regularly)
|
|
108
|
+
C) A SaaS or product site (landing page, pricing, features)
|
|
109
|
+
D) A portfolio (show work, case studies)
|
|
110
|
+
E) An e-commerce or product catalogue
|
|
111
|
+
F) Something else \u2014 describe it in one sentence"
|
|
112
|
+
|
|
113
|
+
\u2500\u2500\u2500 IF EXISTING SITE \u2500\u2500\u2500
|
|
114
|
+
If the user confirms they have an existing site, also:
|
|
115
|
+
- Scan the codebase for all hardcoded text that should be CMS-managed
|
|
116
|
+
- Identify repeated data structures that should become collections
|
|
117
|
+
- Propose saving current content to a migration/ folder as .json files
|
|
118
|
+
BEFORE touching any code
|
|
119
|
+
- Report your findings to the user and get confirmation before proceeding
|
|
120
|
+
|
|
121
|
+
Do NOT write any implementation code until all questions are answered
|
|
122
|
+
and the user has confirmed the content plan.`;
|
|
123
|
+
}
|
|
124
|
+
function buildConstraintsSection() {
|
|
125
|
+
return `
|
|
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
|
|
127
|
+
3. ARCHITECTURE & CONSTRAINTS
|
|
128
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
129
|
+
- API ACCESS : Use client.collection(slug) as the only entry point.
|
|
130
|
+
- ZERO-STATE : Always use initialData in all data fetches so the site
|
|
131
|
+
renders correctly on first load and never throws during render.
|
|
132
|
+
- MARKETING FREEDOM : Use a dynamic pages collection with a catch-all route
|
|
133
|
+
so marketing can create and manage pages without a developer.
|
|
134
|
+
- BLOCKS DESIGN : Use blocks for flexible page builders. Store as
|
|
135
|
+
[{ blockType: 'slug', ...fields }] and switch on blockType
|
|
136
|
+
in the frontend renderer.
|
|
137
|
+
- DATA SAFETY : Never overwrite or drop existing content or pages.
|
|
138
|
+
Preserve everything before making changes.
|
|
139
|
+
- RESILIENCE : If Dyrected backend is unreachable, fall back to
|
|
140
|
+
initialData and show stale content \u2014 never an error page.
|
|
141
|
+
All relationship fields must handle null gracefully.
|
|
142
|
+
Every block renderer must have a default fallback case.`;
|
|
143
|
+
}
|
|
144
|
+
function buildSchemaRulesSection() {
|
|
145
|
+
return `
|
|
146
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
147
|
+
4. SCHEMA EVOLUTION RULES
|
|
148
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
149
|
+
- Never drop existing fields from the schema. Mark unused fields as deprecated only.
|
|
150
|
+
- All new fields must have a defaultValue.
|
|
151
|
+
- Never rename a field slug \u2014 add a new field and migrate data separately.
|
|
152
|
+
- For Cloud deployments, run npx @dyrected/cli sync:schema after every config change. Self-hosted deployments sync automatically on startup.`;
|
|
153
|
+
}
|
|
154
|
+
function buildDoNotSection() {
|
|
155
|
+
return `
|
|
156
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
157
|
+
5. DO NOT
|
|
158
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
159
|
+
- Do NOT use client.collections \u2014 always use client.collection(slug).
|
|
160
|
+
- Do NOT add custom auth middleware to the admin route.
|
|
161
|
+
Dyrected handles admin authentication internally. Do not wrap,
|
|
162
|
+
protect, or redirect the admin route yourself.
|
|
163
|
+
- Do NOT use renderAdminUI in a Nuxt.js project. Use the DyrectedAdmin
|
|
164
|
+
component which is auto-imported by @dyrected/nuxt.
|
|
165
|
+
- Do NOT modify or overwrite existing pages without first preserving their data.
|
|
166
|
+
- Do NOT drop, rename, or remove fields from an existing schema.
|
|
167
|
+
- Do NOT integrate blog posts or testimonials unless explicitly requested.
|
|
168
|
+
- Do NOT skip the diagnostic or discovery phase.`;
|
|
169
|
+
}
|
|
170
|
+
function buildTechnicalReferenceSection() {
|
|
171
|
+
return `
|
|
172
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
173
|
+
6. TECHNICAL REFERENCE
|
|
174
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
175
|
+
Use defineCollection, defineGlobal, and defineConfig from '@dyrected/core'.
|
|
176
|
+
|
|
177
|
+
FIELD TYPES:
|
|
178
|
+
- Primitive : text | textarea | richText | number | boolean | date | email | url | json | image
|
|
179
|
+
- Choice : select | multiSelect (requires options: [{ label, value }])
|
|
180
|
+
- Structural : array | object (requires nested fields: [...])
|
|
181
|
+
- Relation : relationship (requires relationTo: '<slug>')
|
|
182
|
+
- Media : relationship to an upload collection (e.g. 'media')
|
|
183
|
+
- Blocks : blocks (requires blocks: [{ slug, labels, fields }])
|
|
184
|
+
|
|
185
|
+
COLLECTION OPTIONS:
|
|
186
|
+
- upload: true \u2014 media library with file upload support
|
|
187
|
+
- auth: true \u2014 adds login/me endpoints; password field is auto-managed
|
|
188
|
+
- audit: true \u2014 enables activity logging
|
|
189
|
+
- admin.useAsTitle \u2014 field used as display title in admin list view
|
|
190
|
+
- admin.group \u2014 groups collection under a sidebar heading
|
|
191
|
+
- admin.hidden \u2014 hides collection from the sidebar (internal/system use)
|
|
192
|
+
|
|
193
|
+
FIELD OPTIONS:
|
|
194
|
+
- label \u2014 user-friendly display name (REQUIRED for all fields)
|
|
195
|
+
- required \u2014 validation
|
|
196
|
+
- unique \u2014 database-level uniqueness constraint
|
|
197
|
+
- hasMany \u2014 allow multiple values (for relationship, select, image)
|
|
198
|
+
- defaultValue \u2014 fallback value (required on all new fields added to existing schemas)
|
|
199
|
+
- admin.condition \u2014 Jexl expression string to conditionally show/hide field
|
|
200
|
+
e.g. "status == \\"published\\""
|
|
201
|
+
- admin.readOnly \u2014 display only, not editable
|
|
202
|
+
- admin.hidden \u2014 hidden from editor UI entirely
|
|
203
|
+
- hooks.beforeChange \u2014 [async (value) => newValue] transform before save
|
|
204
|
+
- hooks.afterRead \u2014 [async (value) => newValue] transform after read
|
|
205
|
+
|
|
206
|
+
GLOBALS:
|
|
207
|
+
Use defineGlobal for single-instance documents like site settings or navigation.
|
|
208
|
+
Access via client.global(slug).get() and client.global(slug).update(data).
|
|
209
|
+
|
|
210
|
+
BLOCKS:
|
|
211
|
+
Blocks are stored as [{ blockType: '<slug>', ...fields }].
|
|
212
|
+
The admin renders a drag-and-drop block editor automatically.
|
|
213
|
+
On the frontend, iterate the array and switch on block.blockType.
|
|
214
|
+
Always include a default case in your switch for unknown block types.
|
|
215
|
+
|
|
216
|
+
COMPLETE SCHEMA EXAMPLE:
|
|
217
|
+
\`\`\`ts
|
|
218
|
+
import { defineCollection, defineGlobal, defineConfig } from '@dyrected/core'
|
|
219
|
+
import { SqliteAdapter } from '@dyrected/db-sqlite'
|
|
220
|
+
|
|
221
|
+
const media = defineCollection({
|
|
222
|
+
slug: 'media',
|
|
223
|
+
upload: true,
|
|
224
|
+
fields: [
|
|
225
|
+
{ name: 'alt', label: 'Alt Text', type: 'text' },
|
|
226
|
+
],
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const pages = defineCollection({
|
|
230
|
+
slug: 'pages',
|
|
231
|
+
admin: { useAsTitle: 'title', group: 'Content' },
|
|
232
|
+
fields: [
|
|
233
|
+
{ name: 'title', label: 'Title', type: 'text', required: true },
|
|
234
|
+
{ name: 'slug', label: 'URL Slug', type: 'text', required: true, unique: true },
|
|
235
|
+
{ name: 'seo', label: 'SEO Metadata', type: 'object', fields: [
|
|
236
|
+
{ name: 'metaTitle', label: 'Meta Title', type: 'text' },
|
|
237
|
+
{ name: 'metaDescription', label: 'Meta Description', type: 'textarea' },
|
|
238
|
+
{ name: 'ogImage', label: 'OG Image', type: 'relationship', relationTo: 'media' },
|
|
239
|
+
]},
|
|
240
|
+
{
|
|
241
|
+
name: 'layout',
|
|
242
|
+
label: 'Page Layout',
|
|
243
|
+
type: 'blocks',
|
|
244
|
+
blocks: [
|
|
245
|
+
{
|
|
246
|
+
slug: 'hero',
|
|
247
|
+
labels: { singular: 'Hero', plural: 'Heroes' },
|
|
248
|
+
fields: [
|
|
249
|
+
{ name: 'heading', label: 'Heading', type: 'text', required: true },
|
|
250
|
+
{ name: 'subheading', label: 'Subheading', type: 'textarea' },
|
|
251
|
+
{ name: 'image', label: 'Hero Image', type: 'relationship', relationTo: 'media' },
|
|
252
|
+
{ name: 'ctaLabel', label: 'Button Label', type: 'text' },
|
|
253
|
+
{ name: 'ctaLink', label: 'Button Link', type: 'url' },
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
slug: 'richContent',
|
|
258
|
+
labels: { singular: 'Rich Content', plural: 'Rich Content Blocks' },
|
|
259
|
+
fields: [
|
|
260
|
+
{ name: 'content', label: 'Content', type: 'richText', required: true },
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
slug: 'callToAction',
|
|
265
|
+
labels: { singular: 'Call to Action', plural: 'Calls to Action' },
|
|
266
|
+
fields: [
|
|
267
|
+
{ name: 'heading', label: 'Heading', type: 'text', required: true },
|
|
268
|
+
{ name: 'description', label: 'Description', type: 'textarea' },
|
|
269
|
+
{ name: 'buttonLabel', label: 'Button Text', type: 'text' },
|
|
270
|
+
{ name: 'buttonLink', label: 'Button Link', type: 'url' },
|
|
271
|
+
{ name: 'theme', label: 'Theme', type: 'select', options: [
|
|
272
|
+
{ label: 'Primary', value: 'primary' },
|
|
273
|
+
{ label: 'Secondary', value: 'secondary' },
|
|
274
|
+
{ label: 'Dark', value: 'dark' },
|
|
275
|
+
]},
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
const settings = defineGlobal({
|
|
284
|
+
slug: 'settings',
|
|
285
|
+
label: 'Site Settings',
|
|
286
|
+
fields: [
|
|
287
|
+
{ name: 'siteName', label: 'Site Name', type: 'text' },
|
|
288
|
+
{ name: 'tagline', label: 'Site Tagline', type: 'text' },
|
|
289
|
+
{ name: 'logo', label: 'Site Logo', type: 'relationship', relationTo: 'media' },
|
|
290
|
+
{ name: 'footerText', label: 'Footer Text', type: 'textarea' },
|
|
291
|
+
],
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
export default defineConfig({
|
|
295
|
+
collections: [media, pages],
|
|
296
|
+
globals: [settings],
|
|
297
|
+
db: new SqliteAdapter({ filename: './dyrected.db' }),
|
|
298
|
+
})
|
|
299
|
+
\`\`\``;
|
|
300
|
+
}
|
|
301
|
+
function buildDeliverablesSection() {
|
|
302
|
+
return `
|
|
303
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
304
|
+
7. REQUIRED DELIVERABLES \u2014 IN THIS ORDER
|
|
305
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
306
|
+
Return your response in exactly this order. Do not combine steps. Do not skip steps.
|
|
307
|
+
|
|
308
|
+
1. Diagnostic findings (what exists, what needs to become CMS-managed)
|
|
309
|
+
2. dyrected.config.ts \u2014 complete file
|
|
310
|
+
3. Admin route file \u2014 complete file
|
|
311
|
+
4. Catch-all frontend page route \u2014 complete file
|
|
312
|
+
5. Block components \u2014 names and fields only
|
|
313
|
+
6. Migration/fallback strategy \u2014 numbered steps
|
|
314
|
+
7. Schema sync command
|
|
315
|
+
|
|
316
|
+
API Reference: https://docs.dyrected.com
|
|
317
|
+
If you are unsure about any syntax or property, refer to the documentation above.`;
|
|
318
|
+
}
|
|
319
|
+
function buildFrameworkSection(activeTab, isSelfHosted, config) {
|
|
320
|
+
const envPrefix = activeTab === "next" ? "NEXT_PUBLIC_" : activeTab === "nuxt" ? "NUXT_PUBLIC_" : "";
|
|
321
|
+
const credentialEnvLines = isSelfHosted ? "" : `
|
|
322
|
+
apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
|
|
323
|
+
siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`;
|
|
324
|
+
const baseUrlLine = `process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}'`;
|
|
325
|
+
const sections = {
|
|
326
|
+
next: `
|
|
327
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
328
|
+
8. IMPLEMENTATION \u2014 Next.js
|
|
329
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
330
|
+
1. SDK Setup (lib/dyrected.ts):
|
|
331
|
+
\`\`\`ts
|
|
332
|
+
import { createClient } from '@dyrected/sdk'
|
|
333
|
+
|
|
334
|
+
export const dyrected = createClient({
|
|
335
|
+
baseUrl: ${baseUrlLine},${credentialEnvLines}
|
|
336
|
+
})
|
|
337
|
+
\`\`\`
|
|
338
|
+
|
|
339
|
+
2. Admin Route (app/cms/page.tsx):
|
|
340
|
+
\`\`\`tsx
|
|
341
|
+
import { DyrectedAdmin } from '@dyrected/next/admin'
|
|
342
|
+
|
|
343
|
+
export default function AdminPage() {
|
|
344
|
+
// DyrectedAdmin handles routing, auth, and CSS automatically.
|
|
345
|
+
// Do NOT wrap this in custom auth middleware.
|
|
346
|
+
return <DyrectedAdmin basename="/cms" />
|
|
347
|
+
}
|
|
348
|
+
\`\`\`
|
|
349
|
+
|
|
350
|
+
3. Catch-all Page Route (app/[...slug]/page.tsx):
|
|
351
|
+
\`\`\`tsx
|
|
352
|
+
import { dyrected } from '@/lib/dyrected'
|
|
353
|
+
|
|
354
|
+
export default async function CmsPage({ params }: { params: { slug: string[] } }) {
|
|
355
|
+
const slug = params.slug?.join('/') || 'home'
|
|
356
|
+
const page = await dyrected.collection('pages').findOne({ where: { slug: { equals: slug } } })
|
|
357
|
+
|
|
358
|
+
if (!page) return <div>Page not found</div>
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
<main>
|
|
362
|
+
{page.layout?.map((block: any, i: number) => (
|
|
363
|
+
<BlockRenderer key={i} block={block} />
|
|
364
|
+
))}
|
|
365
|
+
</main>
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
\`\`\``,
|
|
369
|
+
nuxt: `
|
|
370
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
371
|
+
8. IMPLEMENTATION \u2014 Nuxt.js
|
|
372
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
373
|
+
1. Nuxt.js Config (nuxt.config.ts):
|
|
374
|
+
\`\`\`ts
|
|
375
|
+
export default defineNuxtConfig({
|
|
376
|
+
modules: ['@dyrected/nuxt'],
|
|
377
|
+
dyrected: {
|
|
378
|
+
baseUrl: process.env.${envPrefix}DYRECTED_URL || '${config.baseUrl || "http://localhost:3000"}',${isSelfHosted ? "" : `
|
|
379
|
+
apiKey: process.env.${envPrefix}DYRECTED_API_KEY || '${config.apiKey}',
|
|
380
|
+
siteId: process.env.${envPrefix}SITE_ID || '${config.siteId}',`}
|
|
381
|
+
},
|
|
382
|
+
})
|
|
383
|
+
\`\`\`
|
|
384
|
+
|
|
385
|
+
2. Admin Route (pages/cms/index.vue):
|
|
386
|
+
\`\`\`vue
|
|
387
|
+
<script setup lang="ts">
|
|
388
|
+
// DyrectedAdmin is auto-imported by @dyrected/nuxt.
|
|
389
|
+
// Do NOT manually import it.
|
|
390
|
+
// Do NOT use renderAdminUI here.
|
|
391
|
+
// Do NOT add custom auth middleware \u2014 Dyrected handles authentication.
|
|
392
|
+
definePageMeta({ layout: false })
|
|
393
|
+
</script>
|
|
394
|
+
|
|
395
|
+
<template>
|
|
396
|
+
<ClientOnly>
|
|
397
|
+
<DyrectedAdmin basename="/cms" />
|
|
398
|
+
</ClientOnly>
|
|
399
|
+
</template>
|
|
400
|
+
\`\`\`
|
|
401
|
+
|
|
402
|
+
3. Catch-all Page Route (pages/[...slug].vue):
|
|
403
|
+
\`\`\`vue
|
|
404
|
+
<script setup lang="ts">
|
|
405
|
+
const route = useRoute()
|
|
406
|
+
const slug = computed(() =>
|
|
407
|
+
Array.isArray(route.params.slug)
|
|
408
|
+
? route.params.slug.join('/')
|
|
409
|
+
: route.params.slug || 'home'
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
const { data: page } = await useDyrected('pages').findOne({
|
|
413
|
+
where: { slug: { equals: slug.value } },
|
|
414
|
+
initialData: null,
|
|
415
|
+
})
|
|
416
|
+
</script>
|
|
417
|
+
|
|
418
|
+
<template>
|
|
419
|
+
<main v-if="page">
|
|
420
|
+
<BlockRenderer
|
|
421
|
+
v-for="(block, i) in page.layout"
|
|
422
|
+
:key="i"
|
|
423
|
+
:block="block"
|
|
424
|
+
/>
|
|
425
|
+
</main>
|
|
426
|
+
<div v-else>Page not found</div>
|
|
427
|
+
</template>
|
|
428
|
+
\`\`\``,
|
|
429
|
+
react: `
|
|
430
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
431
|
+
8. IMPLEMENTATION \u2014 React
|
|
432
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
433
|
+
1. SDK Setup (lib/dyrected.ts):
|
|
434
|
+
\`\`\`ts
|
|
435
|
+
import { createClient } from '@dyrected/sdk'
|
|
436
|
+
|
|
437
|
+
export const dyrected = createClient({
|
|
438
|
+
baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
|
|
439
|
+
apiKey: '${config.apiKey}',
|
|
440
|
+
siteId: '${config.siteId}',`}
|
|
441
|
+
})
|
|
442
|
+
\`\`\`
|
|
443
|
+
|
|
444
|
+
2. Admin Route (pages/cms.tsx):
|
|
445
|
+
\`\`\`tsx
|
|
446
|
+
import { AdminUI } from '@dyrected/admin'
|
|
447
|
+
import '@dyrected/admin/styles'
|
|
448
|
+
|
|
449
|
+
export default function AdminPage() {
|
|
450
|
+
return (
|
|
451
|
+
<div style={{ height: '100vh' }}>
|
|
452
|
+
<AdminUI
|
|
453
|
+
baseUrl="${config.baseUrl || "https://api.dyrected.cloud"}"${isSelfHosted ? "" : `
|
|
454
|
+
apiKey="${config.apiKey}"
|
|
455
|
+
siteId="${config.siteId}"`}
|
|
456
|
+
/>
|
|
457
|
+
</div>
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
\`\`\``,
|
|
461
|
+
vue: `
|
|
462
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
463
|
+
8. IMPLEMENTATION \u2014 Vue
|
|
464
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
465
|
+
1. SDK Setup (lib/dyrected.ts):
|
|
466
|
+
\`\`\`ts
|
|
467
|
+
import { createClient } from '@dyrected/sdk'
|
|
468
|
+
|
|
469
|
+
export const dyrected = createClient({
|
|
470
|
+
baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
|
|
471
|
+
apiKey: '${config.apiKey}',
|
|
472
|
+
siteId: '${config.siteId}',`}
|
|
473
|
+
})
|
|
474
|
+
\`\`\`
|
|
475
|
+
|
|
476
|
+
2. Admin Route (pages/cms.vue):
|
|
477
|
+
\`\`\`vue
|
|
478
|
+
<template>
|
|
479
|
+
<div ref="container" style="height: 100vh" />
|
|
480
|
+
</template>
|
|
481
|
+
|
|
482
|
+
<script setup>
|
|
483
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
484
|
+
import { renderAdminUI } from '@dyrected/admin'
|
|
485
|
+
import '@dyrected/admin/styles'
|
|
486
|
+
|
|
487
|
+
const container = ref(null)
|
|
488
|
+
let cleanup: (() => void) | undefined
|
|
489
|
+
|
|
490
|
+
onMounted(() => {
|
|
491
|
+
cleanup = renderAdminUI(container.value, {
|
|
492
|
+
baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
|
|
493
|
+
apiKey: '${config.apiKey}',
|
|
494
|
+
siteId: '${config.siteId}',`}
|
|
495
|
+
})
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
onUnmounted(() => cleanup?.())
|
|
499
|
+
</script>
|
|
500
|
+
\`\`\``
|
|
501
|
+
};
|
|
502
|
+
return sections[activeTab] || sections.next;
|
|
503
|
+
}
|
|
504
|
+
function generateFreshSetupPrompt(activeTab, config) {
|
|
505
|
+
const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt.js" : activeTab ? activeTab.charAt(0).toUpperCase() + activeTab.slice(1) : "the project's detected framework";
|
|
506
|
+
const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
|
|
507
|
+
const frameworkSetup = {
|
|
508
|
+
nuxt: `
|
|
509
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
510
|
+
3. INSTALLATION STEPS \u2014 Run these in order
|
|
511
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
512
|
+
Tell the user to follow these steps exactly. Explain each one in plain
|
|
513
|
+
language before showing the command. Do not skip any step.
|
|
514
|
+
|
|
515
|
+
STEP 1 \u2014 Initialize Dyrected
|
|
516
|
+
Tell the user:
|
|
517
|
+
"Run this command in your terminal inside your Nuxt.js project folder.
|
|
518
|
+
It will set everything up for you automatically."
|
|
519
|
+
|
|
520
|
+
\`\`\`bash
|
|
521
|
+
npx @dyrected/cli init
|
|
522
|
+
\`\`\`
|
|
523
|
+
|
|
524
|
+
When it asks questions, tell the user to:
|
|
525
|
+
- Choose: Nuxt.js 3
|
|
526
|
+
- Choose: SQLite (easiest option, no extra setup needed)
|
|
527
|
+
|
|
528
|
+
The CLI will automatically:
|
|
529
|
+
- Install all required packages
|
|
530
|
+
- Create a dyrected.config.ts file
|
|
531
|
+
- Mount the Admin UI at pages/cms/index.vue
|
|
532
|
+
- Generate a .env.example file
|
|
533
|
+
|
|
534
|
+
STEP 2 \u2014 Register the module
|
|
535
|
+
Tell the user to open nuxt.config.ts and add '@dyrected/nuxt' to modules:
|
|
536
|
+
|
|
537
|
+
\`\`\`ts
|
|
538
|
+
export default defineNuxtConfig({
|
|
539
|
+
modules: ['@dyrected/nuxt'],
|
|
540
|
+
})
|
|
541
|
+
\`\`\`
|
|
542
|
+
|
|
543
|
+
STEP 3 \u2014 Set up environment variables
|
|
544
|
+
Tell the user:
|
|
545
|
+
"Find the file called .env.example in your project.
|
|
546
|
+
Make a copy of it and rename the copy to .env
|
|
547
|
+
Then open .env and fill in the values."
|
|
548
|
+
|
|
549
|
+
STEP 4 \u2014 Start the project
|
|
550
|
+
\`\`\`bash
|
|
551
|
+
pnpm dev
|
|
552
|
+
\`\`\`
|
|
553
|
+
|
|
554
|
+
STEP 5 \u2014 Open the dashboard
|
|
555
|
+
Tell the user to open their browser and go to:
|
|
556
|
+
http://localhost:3000/cms
|
|
557
|
+
|
|
558
|
+
If they see the Dyrected admin dashboard, the installation worked.
|
|
559
|
+
Tell them: "You are ready. Now let's set up your content."`,
|
|
560
|
+
next: `
|
|
561
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
562
|
+
3. INSTALLATION STEPS \u2014 Run these in order
|
|
563
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
564
|
+
Tell the user to follow these steps exactly. Explain each one in plain
|
|
565
|
+
language before showing the command. Do not skip any step.
|
|
566
|
+
|
|
567
|
+
STEP 1 \u2014 Initialize Dyrected
|
|
568
|
+
Tell the user:
|
|
569
|
+
"Run this command in your terminal inside your Next.js project folder."
|
|
570
|
+
|
|
571
|
+
\`\`\`bash
|
|
572
|
+
npx @dyrected/cli init
|
|
573
|
+
\`\`\`
|
|
574
|
+
|
|
575
|
+
When it asks questions, tell the user to:
|
|
576
|
+
- Choose: Next.js
|
|
577
|
+
- Choose: SQLite (easiest option, no extra setup needed)
|
|
578
|
+
|
|
579
|
+
The CLI will automatically:
|
|
580
|
+
- Install all required packages
|
|
581
|
+
- Create a dyrected.config.ts file
|
|
582
|
+
- Mount the Admin UI at app/cms/page.tsx
|
|
583
|
+
- Generate a .env.example file
|
|
584
|
+
|
|
585
|
+
STEP 2 \u2014 Set up environment variables
|
|
586
|
+
Tell the user:
|
|
587
|
+
"Find the file called .env.example in your project.
|
|
588
|
+
Make a copy of it and rename the copy to .env.local
|
|
589
|
+
Then open .env.local and fill in the values."
|
|
590
|
+
|
|
591
|
+
STEP 3 \u2014 Start the project
|
|
592
|
+
\`\`\`bash
|
|
593
|
+
pnpm dev
|
|
594
|
+
\`\`\`
|
|
595
|
+
|
|
596
|
+
STEP 4 \u2014 Open the dashboard
|
|
597
|
+
Tell the user to open their browser and go to:
|
|
598
|
+
http://localhost:3000/cms
|
|
599
|
+
|
|
600
|
+
If they see the Dyrected admin dashboard, the installation worked.
|
|
601
|
+
Tell them: "You are ready. Now let's set up your content."`,
|
|
602
|
+
react: `
|
|
603
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
604
|
+
3. INSTALLATION STEPS \u2014 Run these in order
|
|
605
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
606
|
+
STEP 1 \u2014 Install the SDK and admin packages
|
|
607
|
+
\`\`\`bash
|
|
608
|
+
npm install @dyrected/sdk @dyrected/admin
|
|
609
|
+
\`\`\`
|
|
610
|
+
|
|
611
|
+
STEP 2 \u2014 Set up the client (lib/dyrected.ts):
|
|
612
|
+
\`\`\`ts
|
|
613
|
+
import { createClient } from '@dyrected/sdk'
|
|
614
|
+
|
|
615
|
+
export const dyrected = createClient({
|
|
616
|
+
baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
|
|
617
|
+
apiKey: '${config.apiKey}',
|
|
618
|
+
siteId: '${config.siteId}',`}
|
|
619
|
+
})
|
|
620
|
+
\`\`\`
|
|
621
|
+
|
|
622
|
+
STEP 3 \u2014 Mount the Admin UI (pages/cms.tsx):
|
|
623
|
+
\`\`\`tsx
|
|
624
|
+
import { AdminUI } from '@dyrected/admin'
|
|
625
|
+
import '@dyrected/admin/styles'
|
|
626
|
+
|
|
627
|
+
export default function AdminPage() {
|
|
628
|
+
return (
|
|
629
|
+
<div style={{ height: '100vh' }}>
|
|
630
|
+
<AdminUI
|
|
631
|
+
baseUrl="${config.baseUrl || "https://api.dyrected.cloud"}"${isSelfHosted ? "" : `
|
|
632
|
+
apiKey="${config.apiKey}"
|
|
633
|
+
siteId="${config.siteId}"`}
|
|
634
|
+
/>
|
|
635
|
+
</div>
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
\`\`\``,
|
|
639
|
+
vue: `
|
|
640
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
641
|
+
3. INSTALLATION STEPS \u2014 Run these in order
|
|
642
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
643
|
+
STEP 1 \u2014 Install the SDK and admin packages
|
|
644
|
+
\`\`\`bash
|
|
645
|
+
npm install @dyrected/sdk @dyrected/admin
|
|
646
|
+
\`\`\`
|
|
647
|
+
|
|
648
|
+
STEP 2 \u2014 Set up the client (lib/dyrected.ts):
|
|
649
|
+
\`\`\`ts
|
|
650
|
+
import { createClient } from '@dyrected/sdk'
|
|
651
|
+
|
|
652
|
+
export const dyrected = createClient({
|
|
653
|
+
baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
|
|
654
|
+
apiKey: '${config.apiKey}',
|
|
655
|
+
siteId: '${config.siteId}',`}
|
|
656
|
+
})
|
|
657
|
+
\`\`\`
|
|
658
|
+
|
|
659
|
+
STEP 3 \u2014 Mount the Admin UI (pages/cms.vue):
|
|
660
|
+
\`\`\`vue
|
|
661
|
+
<template>
|
|
662
|
+
<div ref="container" style="height: 100vh" />
|
|
663
|
+
</template>
|
|
664
|
+
|
|
665
|
+
<script setup>
|
|
666
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
667
|
+
import { renderAdminUI } from '@dyrected/admin'
|
|
668
|
+
import '@dyrected/admin/styles'
|
|
669
|
+
|
|
670
|
+
const container = ref(null)
|
|
671
|
+
let cleanup
|
|
672
|
+
|
|
673
|
+
onMounted(() => {
|
|
674
|
+
cleanup = renderAdminUI(container.value, {
|
|
675
|
+
baseUrl: '${config.baseUrl || "https://api.dyrected.cloud"}',${isSelfHosted ? "" : `
|
|
676
|
+
apiKey: '${config.apiKey}',
|
|
677
|
+
siteId: '${config.siteId}',`}
|
|
678
|
+
})
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
onUnmounted(() => cleanup?.())
|
|
682
|
+
</script>
|
|
683
|
+
\`\`\``
|
|
684
|
+
};
|
|
685
|
+
return [
|
|
686
|
+
`You are a friendly technical assistant helping someone set up Dyrected CMS for the very first time in a ${frameworkLabel} project. They have NOT installed anything yet.
|
|
687
|
+
|
|
688
|
+
Your job is to:
|
|
689
|
+
1. Understand who you are talking to before giving any instructions
|
|
690
|
+
2. Ask simple questions about their project and content needs
|
|
691
|
+
3. Walk them through setup in a way that matches their technical level
|
|
692
|
+
4. Confirm each step worked before moving to the next
|
|
693
|
+
5. Help them design their content so their client can manage it
|
|
694
|
+
|
|
695
|
+
Speak in plain language at all times. Never assume technical knowledge.
|
|
696
|
+
Never show more than one step at a time.
|
|
697
|
+
Never mention the terminal, command line, or any commands until you have
|
|
698
|
+
confirmed the user is comfortable running them.`,
|
|
699
|
+
`
|
|
700
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
701
|
+
1. ENVIRONMENT
|
|
702
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
703
|
+
- Framework : ${frameworkLabel}
|
|
704
|
+
- Host Type : ${isSelfHosted ? "Self-Hosted" : "Dyrected Cloud"}
|
|
705
|
+
- API Base : ${config.baseUrl || "http://localhost:3000"}
|
|
706
|
+
${isSelfHosted ? "" : `- Site ID : ${config.siteId}
|
|
707
|
+
- API Key : ${config.apiKey}`}`.trim(),
|
|
708
|
+
`
|
|
709
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
710
|
+
2. PHASE 0 \u2014 UNDERSTAND THE USER FIRST
|
|
711
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
712
|
+
Ask these questions one at a time. Wait for each answer before asking the next.
|
|
713
|
+
Never ask more than one question at a time.
|
|
714
|
+
|
|
715
|
+
\u2500\u2500\u2500 QUESTION 1 \u2014 TECH LEVEL \u2500\u2500\u2500
|
|
716
|
+
This is the most important question. Ask it first.
|
|
717
|
+
|
|
718
|
+
Ask exactly this:
|
|
719
|
+
"Before we dive in \u2014 how would you describe yourself?
|
|
720
|
+
|
|
721
|
+
A) I write code myself (I'm comfortable with the terminal and editing files)
|
|
722
|
+
B) I use tools like Lovable, Bolt, or v0 to build with AI \u2014 I don't write much code myself
|
|
723
|
+
C) I'm a designer or project manager \u2014 someone else usually handles the technical stuff
|
|
724
|
+
D) Something else \u2014 just describe how you work"
|
|
725
|
+
|
|
726
|
+
Use their answer to decide how to guide them for the rest of the setup:
|
|
727
|
+
- If A \u2192 they are TECHNICAL. You may show terminal commands and code directly.
|
|
728
|
+
- If B \u2192 they are SEMI-TECHNICAL. Explain what each step does before showing
|
|
729
|
+
any code or commands. Ask before running anything in the terminal.
|
|
730
|
+
For Lovable users specifically: guide them to use the built-in
|
|
731
|
+
terminal or ask them to paste code into the right file in their editor.
|
|
732
|
+
- If C \u2192 they are NON-TECHNICAL. Do not show terminal commands at all.
|
|
733
|
+
Generate all the code and config for them. Walk them through
|
|
734
|
+
copy-pasting into specific files by name. If a terminal step is
|
|
735
|
+
unavoidable, warn them first and offer to write the exact command
|
|
736
|
+
with a clear explanation of what it does and where to run it.
|
|
737
|
+
- If D \u2192 ask one follow-up question to understand their workflow before
|
|
738
|
+
deciding which path above fits best.
|
|
739
|
+
|
|
740
|
+
\u2500\u2500\u2500 QUESTION 2 \u2014 PROJECT STATUS \u2500\u2500\u2500
|
|
741
|
+
Ask after Q1 is answered:
|
|
742
|
+
|
|
743
|
+
"Do you already have a ${frameworkLabel} project open,
|
|
744
|
+
or are we starting from scratch?
|
|
745
|
+
|
|
746
|
+
\u2192 Already have a project: tell me what the site is about or
|
|
747
|
+
share the folder name
|
|
748
|
+
\u2192 Starting fresh: just say 'new project' and I will help you
|
|
749
|
+
create one first"
|
|
750
|
+
|
|
751
|
+
\u2500\u2500\u2500 QUESTION 3 \u2014 SITE PURPOSE \u2500\u2500\u2500
|
|
752
|
+
Ask after Q2 is answered:
|
|
753
|
+
|
|
754
|
+
"What kind of website is this?
|
|
755
|
+
|
|
756
|
+
A) A business or agency site (show services, get enquiries)
|
|
757
|
+
B) A blog or news site (publish articles regularly)
|
|
758
|
+
C) A SaaS or product landing page (features, pricing, sign up)
|
|
759
|
+
D) A portfolio (show work and past projects)
|
|
760
|
+
E) Something else \u2014 describe it in one sentence"
|
|
761
|
+
|
|
762
|
+
\u2500\u2500\u2500 QUESTION 4 \u2014 CONTENT NEEDS \u2500\u2500\u2500
|
|
763
|
+
Ask after Q3 is answered:
|
|
764
|
+
|
|
765
|
+
"What will your client need to update themselves \u2014 without calling you?
|
|
766
|
+
|
|
767
|
+
Some examples to help you think:
|
|
768
|
+
\u2192 Blog posts or news articles
|
|
769
|
+
\u2192 Team member profiles (name, photo, bio)
|
|
770
|
+
\u2192 Services or product descriptions
|
|
771
|
+
\u2192 Homepage text (headline, buttons, images)
|
|
772
|
+
\u2192 Testimonials or reviews
|
|
773
|
+
\u2192 FAQs
|
|
774
|
+
\u2192 Event listings or announcements
|
|
775
|
+
|
|
776
|
+
Just list the ones that apply. Or say 'not sure yet'
|
|
777
|
+
and we will figure it out together."
|
|
778
|
+
|
|
779
|
+
\u2500\u2500\u2500 AFTER ALL QUESTIONS \u2500\u2500\u2500
|
|
780
|
+
Once all four questions are answered:
|
|
781
|
+
1. Summarise what you understood in plain English
|
|
782
|
+
2. Ask the user to confirm before touching any code
|
|
783
|
+
3. Then move to the installation steps using the correct path
|
|
784
|
+
for their tech level from Question 1`,
|
|
785
|
+
frameworkSetup[activeTab] || frameworkSetup.nuxt,
|
|
786
|
+
`
|
|
787
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
788
|
+
4. AFTER INSTALLATION \u2014 CONTENT SETUP
|
|
789
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
790
|
+
Once the dashboard is confirmed working, help the user design their
|
|
791
|
+
content model in dyrected.config.ts based on what they told you in
|
|
792
|
+
Phase 0.
|
|
793
|
+
|
|
794
|
+
RULES for this phase:
|
|
795
|
+
- Use defineCollection and defineConfig from '@dyrected/core'
|
|
796
|
+
- Use client.collection(slug) only \u2014 never client.collections
|
|
797
|
+
- Always use initialData in all data fetches
|
|
798
|
+
- Use a catch-all pages collection for marketing-managed pages
|
|
799
|
+
- Use blocks for flexible page layouts
|
|
800
|
+
- Use type: 'relationship', relationTo: 'collectionSlug' for relations
|
|
801
|
+
- Never throw during render \u2014 fall back to initialData on errors
|
|
802
|
+
- All relationship fields must handle null gracefully
|
|
803
|
+
|
|
804
|
+
CONTENT SETUP DELIVERABLES \u2014 in this order:
|
|
805
|
+
1. dyrected.config.ts \u2014 complete file based on their answers
|
|
806
|
+
2. Catch-all page route for CMS-managed pages
|
|
807
|
+
3. Block components list (names and fields only)
|
|
808
|
+
4. One example fetch showing how to load content on a page
|
|
809
|
+
|
|
810
|
+
After delivering the config, tell the user to sync their schema.
|
|
811
|
+
For TECHNICAL users show the command directly:
|
|
812
|
+
\`\`\`bash
|
|
813
|
+
npx @dyrected/cli sync:schema
|
|
814
|
+
\`\`\`
|
|
815
|
+
For SEMI-TECHNICAL or NON-TECHNICAL users explain it first:
|
|
816
|
+
"This next step tells Dyrected to read your content setup file
|
|
817
|
+
and prepare the database. Here is the command to run:" then show it.
|
|
818
|
+
|
|
819
|
+
Then ask: "Do you want me to help you connect this content to your
|
|
820
|
+
frontend pages now?"
|
|
821
|
+
|
|
822
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
823
|
+
DO NOT
|
|
824
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
825
|
+
- Do NOT show terminal commands before confirming the user's tech level
|
|
826
|
+
- Do NOT assume the user knows what a terminal, CLI, or package manager is
|
|
827
|
+
- Do NOT show all steps at once \u2014 one step at a time always
|
|
828
|
+
- Do NOT use client.collections \u2014 use client.collection(slug)
|
|
829
|
+
- Do NOT add custom auth middleware to the admin route
|
|
830
|
+
- Do NOT use renderAdminUI in a Nuxt.js project
|
|
831
|
+
- Do NOT skip the confirmation after each installation step
|
|
832
|
+
- Do NOT use jargon without explaining it in plain English first
|
|
833
|
+
- Do NOT assume the installation worked \u2014 ask the user to confirm
|
|
834
|
+
|
|
835
|
+
API Reference: https://docs.dyrected.com
|
|
836
|
+
If you are unsure about any syntax, refer to the official documentation above.`
|
|
837
|
+
].join("\n");
|
|
838
|
+
}
|
|
839
|
+
function generateAIPrompt(activeTab, config) {
|
|
840
|
+
const frameworkLabel = activeTab === "next" ? "Next.js" : activeTab === "nuxt" ? "Nuxt.js" : activeTab ? activeTab.charAt(0).toUpperCase() + activeTab.slice(1) : "the project's detected framework";
|
|
841
|
+
const isSelfHosted = config.isSelfHosted === true || !config.apiKey && !config.siteId;
|
|
842
|
+
const existingSite = config.existingSite ?? false;
|
|
843
|
+
const missionText = existingSite ? `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into an EXISTING ${frameworkLabel} project. Your absolute priority is DATA PRESERVATION and MIGRATION of existing hardcoded content into a flexible, blocks-based schema that empowers marketing teams to move independently.` : `You are a Senior Content Architect. Your mission is to integrate Dyrected CMS into a NEW ${frameworkLabel} project. Your priority is DATA PRESERVATION and creating a CMS that empowers marketing teams to move independently without raising tickets to engineering.`;
|
|
844
|
+
const sections = [
|
|
845
|
+
missionText,
|
|
846
|
+
buildEnvironmentSection(frameworkLabel, isSelfHosted, config),
|
|
847
|
+
buildDiagnosticSection(),
|
|
848
|
+
buildConstraintsSection(),
|
|
849
|
+
buildSchemaRulesSection(),
|
|
850
|
+
buildDoNotSection(),
|
|
851
|
+
buildTechnicalReferenceSection(),
|
|
852
|
+
buildDeliverablesSection(),
|
|
853
|
+
buildFrameworkSection(activeTab, isSelfHosted, config)
|
|
854
|
+
];
|
|
855
|
+
return sections.join("\n");
|
|
856
|
+
}
|
|
857
|
+
|
|
46
858
|
// src/index.ts
|
|
47
859
|
var DyrectedError = class extends Error {
|
|
48
860
|
statusCode;
|
|
@@ -143,6 +955,7 @@ var DyrectedClient = class {
|
|
|
143
955
|
create: (data) => this.create(slug, data),
|
|
144
956
|
update: (id, data) => this.update(slug, id, data),
|
|
145
957
|
delete: (id) => this.delete(slug, id),
|
|
958
|
+
deleteMany: (ids) => this.deleteMany(slug, ids),
|
|
146
959
|
/**
|
|
147
960
|
* Upload a file to this collection. Sends as multipart/form-data.
|
|
148
961
|
* @param file - A File or Blob (browser) or Buffer with filename/mimeType (Node.js)
|
|
@@ -204,7 +1017,9 @@ var DyrectedClient = class {
|
|
|
204
1017
|
this.request(`/api/collections/${collection}/seed`, {
|
|
205
1018
|
method: "POST",
|
|
206
1019
|
body: JSON.stringify({ data: [{ id, ...initialData }] })
|
|
207
|
-
}).catch(
|
|
1020
|
+
}).catch(
|
|
1021
|
+
(err2) => console.error(`[dyrected/sdk] Failed to auto-seed document "${id}" in collection "${collection}":`, err2)
|
|
1022
|
+
);
|
|
208
1023
|
return initialData;
|
|
209
1024
|
}
|
|
210
1025
|
throw err;
|
|
@@ -227,6 +1042,12 @@ var DyrectedClient = class {
|
|
|
227
1042
|
method: "DELETE"
|
|
228
1043
|
});
|
|
229
1044
|
}
|
|
1045
|
+
async deleteMany(collection, ids) {
|
|
1046
|
+
return this.request(`/api/collections/${collection}/delete-many`, {
|
|
1047
|
+
method: "DELETE",
|
|
1048
|
+
body: qs.stringify({ ids })
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
230
1051
|
async getGlobal(slug, args = {}) {
|
|
231
1052
|
const { initialData, ...queryArgs } = args;
|
|
232
1053
|
const query = qs.stringify(queryArgs, { addQueryPrefix: true });
|
|
@@ -303,15 +1124,14 @@ var DyrectedClient = class {
|
|
|
303
1124
|
...init,
|
|
304
1125
|
headers: allHeaders
|
|
305
1126
|
});
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
body.message || `Request failed with status ${res.status}`,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
);
|
|
1127
|
+
if (res && typeof res.ok === "boolean") {
|
|
1128
|
+
if (!res.ok) {
|
|
1129
|
+
const body = await res.json().catch(() => ({ message: "Unknown error" }));
|
|
1130
|
+
throw new DyrectedError(body.message || `Request failed with status ${res.status}`, res.status, body.code);
|
|
1131
|
+
}
|
|
1132
|
+
return res.json();
|
|
313
1133
|
}
|
|
314
|
-
return res
|
|
1134
|
+
return res;
|
|
315
1135
|
}
|
|
316
1136
|
};
|
|
317
1137
|
function createClient(config) {
|
|
@@ -320,5 +1140,7 @@ function createClient(config) {
|
|
|
320
1140
|
export {
|
|
321
1141
|
DyrectedClient,
|
|
322
1142
|
DyrectedError,
|
|
323
|
-
createClient
|
|
1143
|
+
createClient,
|
|
1144
|
+
generateAIPrompt,
|
|
1145
|
+
generateFreshSetupPrompt
|
|
324
1146
|
};
|