decoupled-cli 2.3.2 → 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.
@@ -1,1160 +0,0 @@
1
- # GEMINI.md - End-to-End Content Type Development Guide
2
-
3
- This document provides Gemini with comprehensive instructions for building complete content type implementations from frontend to backend in this Drupal-Next.js project.
4
-
5
- ## Project Overview
6
-
7
- **Architecture**: Headless Drupal backend with Next.js frontend
8
- **Backend**: Drupal 11 with GraphQL Compose and DC Import API
9
- **Frontend**: Next.js 15 with TypeScript, Tailwind CSS, and Apollo GraphQL
10
- **Environment**: DDEV local development
11
-
12
- ## Environment Configuration
13
-
14
- **Environment Variables (`.env.local`):**
15
- - `NEXT_PUBLIC_DRUPAL_BASE_URL` - Drupal backend URL
16
- - `DRUPAL_CLIENT_ID` - OAuth client ID for API authentication
17
- - `DRUPAL_CLIENT_SECRET` - OAuth client secret for API authentication
18
- - `DRUPAL_REVALIDATE_SECRET` - Secret for on-demand revalidation
19
- - `NODE_TLS_REJECT_UNAUTHORIZED=0` - Allow self-signed certificates (development)
20
-
21
- ## DC CLI Setup
22
-
23
- **First-time CLI setup:**
24
- ```bash
25
- # 1. Install decoupled-cli locally (if not already present in package.json)
26
- npm install --save-dev decoupled-cli
27
-
28
- # 2. Configure authentication - you have two options:
29
-
30
- # Option A: Use OAuth with your existing Drupal credentials (recommended for local development)
31
- npx decoupled-cli auth oauth
32
-
33
- # Option B: Use personal access token (for production/remote deployments)
34
- npx decoupled-cli auth login
35
-
36
- # 3. Set your default space for content imports (optional but recommended)
37
- npx decoupled-cli spaces use <space_id>
38
-
39
- # 4. Verify authentication and space setup
40
- npx decoupled-cli spaces current
41
- ```
42
-
43
- ## Space Management
44
-
45
- **Creating a new space:**
46
- ```bash
47
- # Create a space with default type (starter)
48
- npx decoupled-cli spaces create "My New Space"
49
-
50
- # Create a space with specific type
51
- npx decoupled-cli spaces create "Production Site" --type pro
52
- npx decoupled-cli spaces create "Enterprise App" --type premium
53
-
54
- # Available types: starter, pro, premium
55
-
56
- # Create with description
57
- npx decoupled-cli spaces create "E-commerce Platform" \
58
- --type premium \
59
- --description "Main production e-commerce site"
60
- ```
61
-
62
- **Quick Start with AI (One-Shot Workflow) ⚡:**
63
-
64
- The fastest way to get started! Create a space with AI-generated content types and sample data from a single prompt:
65
-
66
- ```bash
67
- # One command to create space + content
68
- npx decoupled-cli spaces quick-start "blog about coffee"
69
-
70
- # Preview AI-generated content first
71
- npx decoupled-cli spaces quick-start "e-commerce store" --preview
72
-
73
- # Custom configuration
74
- npx decoupled-cli spaces quick-start "portfolio site" \
75
- --name "my-portfolio" \
76
- --type premium
77
-
78
- # Add to existing space
79
- npx decoupled-cli spaces quick-start "add products" --space-id 123
80
- ```
81
-
82
- **What it does:**
83
- 1. 🤖 AI generates content type schema + sample data from your prompt
84
- 2. 🚀 Creates space (or uses `--space-id`)
85
- 3. ⏳ Waits for space to be ready
86
- 4. 📥 Imports generated content automatically
87
- 5. 🎉 Displays GraphQL endpoint and next steps
88
-
89
- **Benefits:**
90
- - ✅ No JSON writing - just describe what you want
91
- - ✅ Instant prototypes for demos and POCs
92
- - ✅ Learns from examples/content-import-sample.json patterns
93
- - ✅ Uses centrally-managed AI (no API keys needed)
94
- - ✅ Works with PAT authentication only
95
-
96
- **Example use cases:**
97
- - `"blog with posts about travel"` → Creates post content type with sample travel articles
98
- - `"e-commerce with products"` → Creates product content type with pricing, images, inventory
99
- - `"events calendar"` → Creates event content type with dates, locations, speakers
100
- - `"team directory"` → Creates team_member content type with bios, photos, roles
101
-
102
- **Listing and managing spaces:**
103
- ```bash
104
- # List all spaces
105
- npx decoupled-cli spaces list
106
-
107
- # List with detailed information
108
- npx decoupled-cli spaces list --detailed
109
-
110
- # Get space details by ID or name
111
- npx decoupled-cli spaces get 123
112
- npx decoupled-cli spaces get my-space-name
113
-
114
- # Check space status
115
- npx decoupled-cli spaces status 123
116
-
117
- # Set default space for operations
118
- npx decoupled-cli spaces use 123
119
- npx decoupled-cli spaces current
120
- ```
121
-
122
- **Other space operations:**
123
- ```bash
124
- # Clone an existing space
125
- npx decoupled-cli spaces clone 123 --name "Development Copy"
126
-
127
- # Update space name or type
128
- npx decoupled-cli spaces update 123 --name "Updated Name"
129
- npx decoupled-cli spaces update 123 --type premium
130
-
131
- # Get Drupal admin login link
132
- npx decoupled-cli spaces login 123
133
-
134
- # Archive/unarchive spaces
135
- npx decoupled-cli spaces archive 123
136
- npx decoupled-cli spaces unarchive 123
137
-
138
- # Delete space (with confirmation)
139
- npx decoupled-cli spaces delete 123
140
- ```
141
-
142
- **OAuth Prerequisites (for `decoupled-cli auth oauth`):**
143
- Your `.env.local` must contain:
144
- - `NEXT_PUBLIC_DRUPAL_BASE_URL=https://your-space.decoupled.io`
145
- - `DRUPAL_CLIENT_ID=your_oauth_client_id`
146
- - `DRUPAL_CLIENT_SECRET=your_oauth_client_secret`
147
-
148
- **Authentication Method Differences:**
149
- - **Personal Access Token (PAT)** → Works with Decoupled Drupal platform API (spaces, users, organizations, **content import via platform proxy**)
150
- - **OAuth** → Direct Drupal site API access (alternative method for content import, local development)
151
-
152
- **If CLI is not available locally:**
153
- - For projects with decoupled-cli in package.json: Run `npm install` then use `npx decoupled-cli`
154
- - For development: `cd cli && npm install && npm run build && npm link` then use `decoupled-cli`
155
- - Always prefer using `npx decoupled-cli` for consistency and local package management
156
-
157
- ## End-to-End Development Workflow
158
-
159
- When asked to create a new content type (e.g., "create a product page"), follow these steps:
160
-
161
- **Recommended Workflow (PAT + Platform Proxy - Contentful-like simplicity):**
162
- Use a single PAT token for all operations including content import via the platform proxy.
163
-
164
- ### 1. Content Analysis & Planning
165
-
166
- **Analyze the request and determine:**
167
- - Content type name and machine name
168
- - Required fields and their types
169
- - Frontend components needed
170
- - URL structure and routing
171
- - Display requirements (listing, detail pages, etc.)
172
-
173
- **Create a todo list for tracking progress:**
174
- ```markdown
175
- 1. Verify DC CLI authentication (npx decoupled-cli auth status)
176
- 2. Create DC Import JSON for [content_type]
177
- 3. Import content type and sample content via platform (npx decoupled-cli spaces content-import <space_id> --file import.json)
178
- 4. **CRITICAL**: Run schema generation (`npm run generate-schema`) to update GraphQL schema
179
- 5. Create TypeScript types and GraphQL queries
180
- 6. Build frontend components ([ContentCard], [ContentRenderer])
181
- 7. Create listing and detail pages
182
- 8. Test build process and fix errors
183
- 9. Validate end-to-end functionality
184
- ```
185
-
186
- **Alternative Workflow (OAuth + Direct Drupal API):**
187
- ```markdown
188
- 1. Set up OAuth authentication (npx decoupled-cli auth oauth)
189
- 2. Create DC Import JSON for [content_type]
190
- 3. Import content type and sample content to Drupal directly (npx decoupled-cli content import --file import.json)
191
- 4. **CRITICAL**: Run schema generation (`npm run generate-schema`) to update GraphQL schema
192
- 5. Create TypeScript types and GraphQL queries
193
- 6. Build frontend components ([ContentCard], [ContentRenderer])
194
- 7. Create listing and detail pages
195
- 8. Test build process and fix errors
196
- 9. Validate end-to-end functionality
197
- ```
198
-
199
- ### 0. CLI Authentication Check
200
-
201
- **ALWAYS start by verifying CLI setup:**
202
- ```bash
203
- # Check if CLI is authenticated
204
- npx decoupled-cli auth status
205
-
206
- # If not authenticated, choose authentication method:
207
- # For local development with Drupal OAuth:
208
- npx decoupled-cli auth oauth
209
-
210
- # For production with personal access token:
211
- npx decoupled-cli auth login
212
-
213
- # List available spaces and set default
214
- npx decoupled-cli spaces list
215
- npx decoupled-cli spaces use <space_id>
216
- ```
217
-
218
- **If CLI authentication fails:**
219
- - For OAuth: Verify `.env.local` contains `NEXT_PUBLIC_DRUPAL_BASE_URL`, `DRUPAL_CLIENT_ID`, `DRUPAL_CLIENT_SECRET`
220
- - For personal tokens: Check that DC platform is accessible
221
- - Ensure user has proper permissions for the operations you're trying to perform
222
-
223
- **Important: Command Availability by Authentication Method**
224
- - **OAuth commands**: `content import`, `content status`, `auth status`
225
- - **PAT-only commands**: `spaces list`, `spaces use`, `usage`, `org info`
226
- - **Universal commands**: `auth login`, `auth oauth`, `auth test`
227
-
228
- ### 2. DC Import JSON Creation
229
-
230
- **Get example format with CLI:**
231
- ```bash
232
- npx decoupled-cli spaces content-import --example > my-content-type.json
233
- ```
234
-
235
- **Use this JSON structure:**
236
-
237
- ```json
238
- {
239
- "model": [
240
- {
241
- "bundle": "content_type_name",
242
- "description": "Description of the content type",
243
- "label": "Content Type Label",
244
- "body": true,
245
- "fields": [
246
- {
247
- "id": "field_name",
248
- "label": "Field Label",
249
- "type": "text|string|image|datetime|bool|text[]"
250
- }
251
- ]
252
- }
253
- ],
254
- "content": [
255
- {
256
- "id": "item1",
257
- "type": "node.content_type_name",
258
- "path": "/content-type/item-slug",
259
- "values": {
260
- "title": "Item Title",
261
- "body": "<p>Body content</p>",
262
- "field_name": "field_value"
263
- }
264
- }
265
- ]
266
- }
267
- ```
268
-
269
- **Field Type Reference:**
270
- - `text` - Long text with formatting
271
- - `string` - Short plain text (max 255 chars)
272
- - `image` - Image upload
273
- - `datetime` - Date and time
274
- - `bool` - Boolean true/false
275
- - `text[]` - Multiple text values
276
- - `string[]` - Multiple string values
277
-
278
- **Important Notes:**
279
- - Field IDs become `field_[id]` in Drupal (e.g., `"id": "price"` → `field_price`)
280
- - **CRITICAL**: In content values, use the field ID directly without "field_" prefix (e.g., `"price": "$299.99"` NOT `"field_price": "$299.99"`)
281
- - Use `"body": true` to include standard body field
282
- - Always include sample content for testing
283
- - Path aliases should follow `/content-type/slug` pattern
284
- - **For image fields**: Use full URLs with the Drupal domain from `.env.local`, not relative paths:
285
- ```json
286
- "featured_image": {
287
- "uri": "${DRUPAL_BASE_URL}/modules/custom/dc_import/resources/placeholder.png",
288
- "alt": "Description of the image",
289
- "title": "Image title"
290
- }
291
- ```
292
- Always read the `NEXT_PUBLIC_DRUPAL_BASE_URL` from `.env.local` and use that as the base for image URIs to ensure images load correctly from the Drupal backend.
293
-
294
- ### 3. Import via DC CLI
295
-
296
- **Import Content Type (Recommended - PAT Only):**
297
- ```bash
298
- # Import via platform proxy using PAT token (Contentful-like simplicity)
299
- # No OAuth credentials needed - just your PAT token!
300
- npx decoupled-cli spaces content-import <space_id> --file content-type-import.json
301
-
302
- # Or let the CLI use your default space
303
- npx decoupled-cli spaces content-import --file content-type-import.json
304
- ```
305
-
306
- **Alternative: Direct Drupal Import (OAuth Required):**
307
- ```bash
308
- # Import directly to your Drupal site (requires OAuth authentication)
309
- npx decoupled-cli content import --file content-type-import.json
310
-
311
- # Or preview first to see what will be imported
312
- npx decoupled-cli content import --file content-type-import.json --preview
313
- ```
314
-
315
- **Generate example import file:**
316
- ```bash
317
- # Get example import JSON structure (works with both methods)
318
- npx decoupled-cli content import --example > content-type-import.json
319
- # Then edit the generated file with your content type definition
320
- ```
321
-
322
- **Always check the response for success and note:**
323
- - Content type machine name (may differ from input)
324
- - Field machine names (auto-generated)
325
- - Node IDs created
326
- - GraphQL schema updates
327
- - **Important**: GraphQL field names may differ from DC field IDs (check actual schema)
328
-
329
- **CRITICAL: Immediately Update GraphQL Schema After Import:**
330
- ```bash
331
- # Generate updated GraphQL schema to include new content type
332
- npm run generate-schema
333
- ```
334
-
335
- **Verify the schema includes your content type:**
336
- ```bash
337
- # Check that your content type appears in the generated schema
338
- grep -i [your_content_type] schema/schema.graphql
339
-
340
- # Example for products:
341
- grep -i product schema/schema.graphql
342
- ```
343
-
344
- **Test that content was imported successfully:**
345
- ```bash
346
- # Check that new content type appears in generated schema
347
- npm run generate-schema
348
- grep -i [your_content_type] schema/schema.graphql
349
- ```
350
-
351
- ### 4. Frontend Implementation
352
-
353
- **File Structure:**
354
- ```
355
- app/
356
- ├── components/
357
- │ ├── [ContentType]Card.tsx # List view component
358
- │ ├── [ContentType]Renderer.tsx # Detail view component
359
- │ └── DynamicIcon.tsx # Icon component (if needed)
360
- ├── [content-type]/
361
- │ └── page.tsx # Listing page
362
- ├── [...slug]/
363
- │ └── page.tsx # Dynamic routing (update)
364
- lib/
365
- ├── queries.ts # GraphQL queries (update)
366
- └── types.ts # TypeScript types (update)
367
- ```
368
-
369
- #### GraphQL Queries
370
-
371
- **Add to `lib/queries.ts`:**
372
- ```typescript
373
- export const GET_CONTENT_TYPES = gql`
374
- query GetContentTypes($first: Int = 10) {
375
- node[ContentType]s(first: $first, sortKey: CREATED_AT) {
376
- nodes {
377
- id
378
- title
379
- path
380
- created { timestamp }
381
- changed { timestamp }
382
- ... on Node[ContentType] {
383
- body { processed }
384
- field[FieldName] { processed }
385
- // Add all fields
386
- }
387
- }
388
- }
389
- }
390
- `
391
- ```
392
-
393
- **Update `GET_NODE_BY_PATH` to include new content type:**
394
- ```typescript
395
- ... on Node[ContentType] {
396
- id
397
- title
398
- body { processed }
399
- field[FieldName] { processed }
400
- // Add all fields
401
- }
402
- ```
403
-
404
- #### TypeScript Types
405
-
406
- **Add to `lib/types.ts`:**
407
- ```typescript
408
- export interface Drupal[ContentType] extends DrupalNode {
409
- body?: { processed: string }
410
- field[FieldName]?: { processed: string }
411
- // Add all fields with proper types
412
- }
413
-
414
- export interface [ContentType]Data {
415
- node[ContentType]s: {
416
- nodes: Drupal[ContentType][]
417
- }
418
- }
419
- ```
420
-
421
- #### Component Templates
422
-
423
- **Card Component (`[ContentType]Card.tsx`):**
424
- ```typescript
425
- import Link from 'next/link'
426
- import { Drupal[ContentType] } from '@/lib/types'
427
-
428
- interface [ContentType]CardProps {
429
- [contentType]: Drupal[ContentType]
430
- }
431
-
432
- export default function [ContentType]Card({ [contentType] }: [ContentType]CardProps) {
433
- return (
434
- <div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden hover:shadow-md transition-shadow duration-200">
435
- <div className="p-6">
436
- <h3 className="text-xl font-semibold text-gray-900 mb-2">
437
- {[contentType].title}
438
- </h3>
439
-
440
- {/* Add field displays */}
441
- {/* For text[] fields with HTML content, use dangerouslySetInnerHTML */}
442
- {/* Example: <span dangerouslySetInnerHTML={{ __html: field.processed }} /> */}
443
-
444
- <Link
445
- href={[contentType].path || `/[content-type]/${[contentType].id}`}
446
- className="inline-flex items-center px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 transition-colors duration-200"
447
- >
448
- Learn More
449
- </Link>
450
- </div>
451
- </div>
452
- )
453
- }
454
- ```
455
-
456
- **Renderer Component (`[ContentType]Renderer.tsx`):**
457
- ```typescript
458
- import Header from './Header'
459
- import { Drupal[ContentType] } from '@/lib/types'
460
-
461
- interface [ContentType]RendererProps {
462
- [contentType]: Drupal[ContentType]
463
- }
464
-
465
- export default function [ContentType]Renderer({ [contentType] }: [ContentType]RendererProps) {
466
- return (
467
- <div className="min-h-screen bg-gray-50">
468
- <Header />
469
- <main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
470
- <article className="bg-white rounded-lg shadow-sm overflow-hidden">
471
- <div className="p-8">
472
- <h1 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
473
- {[contentType].title}
474
- </h1>
475
-
476
- {/* Add field displays */}
477
- {/* For text[] fields with HTML content, use dangerouslySetInnerHTML */}
478
- {/* Example: <span dangerouslySetInnerHTML={{ __html: field.processed }} /> */}
479
-
480
- {[contentType].body?.processed && (
481
- <div
482
- className="prose prose-lg max-w-none mt-8"
483
- dangerouslySetInnerHTML={{ __html: [contentType].body.processed }}
484
- />
485
- )}
486
- </div>
487
- </article>
488
- </main>
489
- </div>
490
- )
491
- }
492
- ```
493
-
494
- #### Listing Page
495
-
496
- **Create `app/[content-type]/page.tsx`:**
497
- ```typescript
498
- import Header from '../components/Header'
499
- import [ContentType]Card from '../components/[ContentType]Card'
500
- import { GET_CONTENT_TYPES } from '@/lib/queries'
501
- import { getServerApolloClient } from '@/lib/apollo-client'
502
- import { [ContentType]Data } from '@/lib/types'
503
-
504
- export const revalidate = 300
505
-
506
- export default async function [ContentType]sPage() {
507
- const apollo = getServerApolloClient(await headers())
508
-
509
- try {
510
- const { data } = await apollo.query<[ContentType]Data>({
511
- query: GET_CONTENT_TYPES,
512
- variables: { first: 20 },
513
- fetchPolicy: 'no-cache'
514
- })
515
-
516
- const [contentType]s = data?.node[ContentType]s?.nodes || []
517
-
518
- return (
519
- <div className="min-h-screen bg-gray-50">
520
- <Header />
521
- <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
522
- <div className="text-center mb-12">
523
- <h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
524
- [Content Types]
525
- </h1>
526
- <p className="text-xl text-gray-600 max-w-3xl mx-auto">
527
- Description of content type listing
528
- </p>
529
- </div>
530
-
531
- {[contentType]s.length > 0 ? (
532
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
533
- {[contentType]s.map(([contentType]) => (
534
- <[ContentType]Card key={[contentType].id} [contentType]={[contentType]} />
535
- ))}
536
- </div>
537
- ) : (
538
- <div className="text-center py-16">
539
- <p className="text-gray-600">No [content types] available.</p>
540
- </div>
541
- )}
542
- </main>
543
- </div>
544
- )
545
- } catch (error) {
546
- console.error('Error loading [content types]:', error)
547
- // Return error state
548
- }
549
- }
550
- ```
551
-
552
- #### Update Dynamic Routing
553
-
554
- **Add to `app/[...slug]/page.tsx`:**
555
- ```typescript
556
- // In generateMetadata and page functions, add:
557
- ... on Node[ContentType] {
558
- id
559
- title
560
- // Add fields needed for display
561
- }
562
-
563
- // In the page component return:
564
- if (node.__typename === 'Node[ContentType]') {
565
- return <[ContentType]Renderer [contentType]={node as Drupal[ContentType]} />
566
- }
567
- ```
568
-
569
- #### Update Navigation
570
-
571
- **Add to `app/components/Header.tsx`:**
572
- ```typescript
573
- // Add navigation link
574
- <Link
575
- href="/[content-type]"
576
- className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
577
- pathname === '/[content-type]' ? 'bg-blue-100 text-blue-700' : 'text-gray-700 hover:text-blue-600'
578
- }`}
579
- >
580
- [Content Types]
581
- </Link>
582
- ```
583
-
584
- ### 5. Build and Test Process
585
-
586
- **Always run these commands in sequence:**
587
-
588
- 1. **Build the application:**
589
- ```bash
590
- npm run build
591
- ```
592
-
593
- 2. **Fix any TypeScript/build errors** that appear
594
-
595
- 3. **Start development server:**
596
- ```bash
597
- npm run dev
598
- ```
599
-
600
- 4. **Test endpoints:**
601
- - Listing page: `http://localhost:3001/[content-type]`
602
- - Detail pages: `http://localhost:3001/[content-type]/[slug]`
603
-
604
- ### 6. Testing Checklist
605
-
606
- **Verify each step:**
607
- - [ ] DC import successful with no errors
608
- - [ ] GraphQL schema includes new content type
609
- - [ ] TypeScript types are properly defined
610
- - [ ] Build process completes without errors
611
- - [ ] Listing page displays content
612
- - [ ] Detail pages render correctly
613
- - [ ] Navigation links work
614
- - [ ] Responsive design functions
615
- - [ ] Error states handled gracefully
616
-
617
- ## Common Content Type Patterns
618
-
619
- ### E-commerce Product
620
- ```json
621
- {
622
- "id": "price",
623
- "label": "Price",
624
- "type": "string"
625
- },
626
- {
627
- "id": "product_images",
628
- "label": "Product Images",
629
- "type": "image[]"
630
- },
631
- {
632
- "id": "in_stock",
633
- "label": "In Stock",
634
- "type": "bool"
635
- },
636
- {
637
- "id": "features",
638
- "label": "Key Features",
639
- "type": "string[]"
640
- }
641
- ```
642
-
643
- **Sample content with image URI:**
644
- ```json
645
- "product_images": [
646
- {
647
- "uri": "${DRUPAL_BASE_URL}/modules/custom/dc_import/resources/placeholder.png",
648
- "alt": "Product showcase image",
649
- "title": "Product Image"
650
- }
651
- ]
652
- ```
653
-
654
- ### Event/Conference
655
- ```json
656
- {
657
- "id": "event_date",
658
- "label": "Event Date",
659
- "type": "datetime"
660
- },
661
- {
662
- "id": "location",
663
- "label": "Location",
664
- "type": "string"
665
- },
666
- {
667
- "id": "speakers",
668
- "label": "Speakers",
669
- "type": "string[]"
670
- }
671
- ```
672
-
673
- ### Team Member
674
- ```json
675
- {
676
- "id": "position",
677
- "label": "Position",
678
- "type": "string"
679
- },
680
- {
681
- "id": "profile_image",
682
- "label": "Profile Image",
683
- "type": "image"
684
- },
685
- {
686
- "id": "bio",
687
- "label": "Biography",
688
- "type": "text"
689
- }
690
- ```
691
-
692
- **Sample content with image URI:**
693
- ```json
694
- "profile_image": {
695
- "uri": "${DRUPAL_BASE_URL}/modules/custom/dc_import/resources/placeholder.png",
696
- "alt": "Team member headshot",
697
- "title": "Profile Photo"
698
- }
699
- ```
700
-
701
- ### Portfolio/Case Study
702
- ```json
703
- {
704
- "id": "project_url",
705
- "label": "Project URL",
706
- "type": "string"
707
- },
708
- {
709
- "id": "technologies",
710
- "label": "Technologies Used",
711
- "type": "string[]"
712
- },
713
- {
714
- "id": "project_images",
715
- "label": "Project Images",
716
- "type": "image[]"
717
- }
718
- ```
719
-
720
- **Sample content with image URIs:**
721
- ```json
722
- "project_images": [
723
- {
724
- "uri": "${DRUPAL_BASE_URL}/modules/custom/dc_import/resources/placeholder.png",
725
- "alt": "Project screenshot showing main interface",
726
- "title": "Project Interface"
727
- }
728
- ]
729
- ```
730
-
731
- ## Troubleshooting
732
-
733
- ### Common Issues
734
-
735
- **1. DC Import Fails**
736
- - Check OAuth token expiration
737
- - Verify JSON structure matches `schema/sample.json` format
738
- - Ensure field IDs don't start with `field_`
739
- - **Critical**: In content values, use field ID without "field_" prefix (e.g., `"price": "$299.99"` not `"field_price": "$299.99"`)
740
-
741
- **2. GraphQL Errors**
742
- - Check if content type was created successfully in Drupal
743
- - Verify GraphQL Compose configuration
744
- - Clear GraphQL cache in Drupal admin
745
-
746
- **3. Build Errors**
747
- - Check TypeScript type definitions
748
- - Ensure all imports are correct
749
- - Verify GraphQL query syntax
750
-
751
- **4. Content Not Displaying**
752
- - Check GraphQL query field names match Drupal fields (may not match DC field IDs)
753
- - Verify content was created and published
754
- - Check query variables and pagination
755
- - For HTML content showing raw tags, use `dangerouslySetInnerHTML={{ __html: field.processed }}`
756
-
757
- **5. GraphQL Schema Not Updated After DC Import**
758
- - **Critical Issue**: DC imports create content types successfully but GraphQL schema may not update immediately
759
- - Content exists in Drupal but `nodeProducts` query returns "field not found" errors
760
- - This is the most common issue with DC imports
761
-
762
- **Solution Process**:
763
- 1. Clear Drupal caches (if you have admin access)
764
- 2. **Always run schema generation script**: `npm run generate-schema`
765
- 3. This regenerates the GraphQL schema files and validates the schema is updated
766
- 4. Test queries again after schema regeneration
767
-
768
- ### Essential Schema Generation Workflow
769
-
770
- **CRITICAL**: Always run schema generation after DC imports to ensure GraphQL integration works properly.
771
-
772
- ```bash
773
- # Generate updated GraphQL schema after content type imports
774
- npm run generate-schema
775
- ```
776
-
777
- This command:
778
- - Authenticates with Drupal using OAuth
779
- - Performs GraphQL introspection to get the current schema
780
- - Generates updated schema files in `/schema/` directory
781
- - Validates that new content types are available in GraphQL
782
-
783
- **Add this to your workflow**:
784
- 1. Import content type via DC API
785
- 2. **Immediately run**: `npm run generate-schema`
786
- 3. Check generated schema includes your new content type
787
- 4. Test GraphQL queries
788
- 5. Proceed with frontend implementation
789
-
790
- ### Debug Commands
791
-
792
- ```bash
793
- # Check CLI authentication status
794
- npx decoupled-cli auth status
795
-
796
- # OAUTH/DRUPAL COMMANDS:
797
- # Check content import API status
798
- npx decoupled-cli content status
799
-
800
- # Preview content import without applying changes
801
- npx decoupled-cli content import --file your-import.json --preview
802
-
803
- # Import content to Drupal
804
- npx decoupled-cli content import --file your-import.json
805
-
806
- # Generate fresh schema (most important after any import)
807
- npm run generate-schema
808
-
809
- # Check schema includes your new content type
810
- npm run generate-schema
811
- grep -i [your_content_type] schema/schema.graphql
812
-
813
- # PAT/PLATFORM COMMANDS (require npx decoupled-cli auth login):
814
- # List available spaces
815
- npx decoupled-cli spaces list
816
-
817
- # Check current default space
818
- npx decoupled-cli spaces current
819
-
820
- # Get health status of DC platform
821
- npx decoupled-cli health check
822
-
823
- # Check organization info
824
- npx decoupled-cli org info
825
-
826
- # Legacy platform content import
827
- npx decoupled-cli spaces content-import --file your-import.json --preview
828
- npx decoupled-cli spaces content-import --file your-import.json --json
829
- ```
830
-
831
- ## Content Import Command Reference
832
-
833
- ### OAuth + Drupal API Commands
834
-
835
- **Check content import API status:**
836
- ```bash
837
- npx decoupled-cli content status
838
- ```
839
-
840
- **Generate example import JSON:**
841
- ```bash
842
- npx decoupled-cli content import --example > my-content-type.json
843
- ```
844
-
845
- **Preview import (dry run):**
846
- ```bash
847
- npx decoupled-cli content import --file my-content-type.json --preview
848
- ```
849
-
850
- **Import content to Drupal:**
851
- ```bash
852
- npx decoupled-cli content import --file my-content-type.json
853
- ```
854
-
855
- ### Authentication Setup for Content Import
856
-
857
- **Quick OAuth Setup (recommended for local development):**
858
- ```bash
859
- # 1. Ensure .env.local has OAuth credentials
860
- # 2. Set up OAuth profile
861
- npx decoupled-cli auth oauth
862
-
863
- # 3. Verify it's working
864
- npx decoupled-cli content status
865
-
866
- # 4. Import content
867
- npx decoupled-cli content import --file your-content.json
868
- ```
869
-
870
- **When to use PAT vs OAuth:**
871
- - **PAT (Recommended)**: All operations including content import, space management, production deployments - **just like Contentful's CMA token**
872
- - **OAuth**: Alternative method for direct Drupal API access, local development with existing Drupal credentials
873
-
874
- ## Best Practices
875
-
876
- 1. **Always create sample content** during import for immediate testing
877
- 2. **Use descriptive field names** that are clear and consistent
878
- 3. **Follow existing naming conventions** in the codebase
879
- 4. **Test responsive design** on different screen sizes
880
- 5. **Handle empty/missing data gracefully** in components
881
- 6. **Use semantic HTML** for accessibility
882
- 7. **Include proper TypeScript types** for all data structures
883
- 8. **Test the full user journey** from listing to detail pages
884
- 9. **Use `dangerouslySetInnerHTML`** for processed HTML content from Drupal to avoid raw tag display
885
- 10. **Verify GraphQL field names** match actual schema, not DC field IDs
886
-
887
- ## Success Criteria
888
-
889
- A successful end-to-end implementation should:
890
- - [ ] Build without errors
891
- - [ ] Display content correctly on listing and detail pages
892
- - [ ] Handle navigation between pages
893
- - [ ] Render responsively on all device sizes
894
- - [ ] Show appropriate fallbacks for missing data
895
- - [ ] Follow the established design patterns
896
- - [ ] Include proper metadata and SEO elements
897
- - [ ] Work with the existing authentication and routing system
898
-
899
- ## Key Learnings and Common Mistakes
900
-
901
- Based on successful product catalog implementation, here are critical learnings to avoid common pitfalls:
902
-
903
- ### DC Import Format Issues
904
-
905
- **Problem**: Field values incorrectly formatted with "field_" prefix
906
- ```json
907
- // WRONG - This causes import failures
908
- "values": {
909
- "field_price": "$299.99",
910
- "field_in_stock": true
911
- }
912
-
913
- // CORRECT - Use field ID directly
914
- "values": {
915
- "price": "$299.99",
916
- "in_stock": true
917
- }
918
- ```
919
-
920
- **Solution**: Always use the field `id` value directly in content values, never add "field_" prefix.
921
-
922
- ### GraphQL Field Name Mapping
923
-
924
- **Problem**: Assuming GraphQL field names match DC field IDs
925
- ```typescript
926
- // WRONG - Field names may be transformed
927
- price: string // DC field ID
928
- fieldPrice: string // What you might expect in GraphQL
929
-
930
- // CORRECT - Check actual schema
931
- price: string // Actual GraphQL field name (can match DC ID)
932
- inStock: boolean // camelCase transformation
933
- productImages: object // snake_case to camelCase
934
- ```
935
-
936
- **Solution**: Always verify GraphQL field names by checking the actual schema or API response before writing queries.
937
-
938
- ### GraphQL Field Value Structure Discovery
939
-
940
- **Critical Learning**: GraphQL field values from DC imports return as simple types, not objects:
941
-
942
- ```typescript
943
- // WRONG - Assuming field values are objects with .processed
944
- fieldPrice?: {
945
- processed: string
946
- }
947
- fieldFeatures?: Array<{
948
- processed: string
949
- }>
950
-
951
- // CORRECT - DC imports create simple value fields
952
- price?: string
953
- features?: string[]
954
- ```
955
-
956
- **Discovery Process**: Check GraphQL schema to understand actual field structure:
957
- ```bash
958
- # Generate fresh schema and check field structure
959
- npm run generate-schema
960
- grep -i nodeProducts schema/schema.graphql
961
- # Check the actual schema files for field names and types
962
- ```
963
-
964
- **Solution**: Always test a simple GraphQL query first to understand the actual field structure before writing TypeScript types and components.
965
-
966
- ### HTML Content Rendering
967
-
968
- **Problem**: Drupal processed HTML showing as raw tags in frontend
969
- ```jsx
970
- // WRONG - Shows: "Key Features <p>Fast Wireless Charging</p> <p>Qi-Compatible</p>"
971
- <span>{feature.processed}</span>
972
-
973
- // CORRECT - Renders HTML properly
974
- <span dangerouslySetInnerHTML={{ __html: feature.processed }} />
975
- ```
976
-
977
- **Better Solution**: Use `string[]` instead of `text[]` for simple lists to avoid HTML altogether:
978
- ```jsx
979
- // BEST - Clean, simple, no HTML rendering needed
980
- <span>{feature}</span> // Just plain text: "Fast Wireless Charging"
981
- ```
982
-
983
- **Solution**: Use `dangerouslySetInnerHTML` only when necessary for rich `text[]` fields. Prefer `string[]` for simple bullet lists.
984
-
985
- ### Field Type Selection
986
-
987
- **Best Practice**: Choose field types based on content structure:
988
- - `string[]` for bullet points, features, specifications, tags, categories (clean plain text, no HTML rendering issues)
989
- - `text[]` only when you need rich formatting within each item (rare - avoid unless necessary)
990
- - `text` for rich content descriptions that need HTML formatting
991
- - `string` for simple values like price, SKU, names (plain text, max 255 chars)
992
-
993
- **Recommendation**: For repeated items like product features, use `string[]` instead of `text[]` to avoid HTML rendering complexity and security concerns.
994
-
995
- ### Navigation Menu Integration Pattern
996
-
997
- **Essential Step**: Always update the main navigation when creating new content types:
998
-
999
- ```typescript
1000
- // Update navigationItems array in app/components/Header.tsx
1001
- const navigationItems = [
1002
- { name: 'Home', href: '/' },
1003
- { name: 'Products', href: '/products' }, // Add new content type
1004
- { name: 'Articles', href: '/articles' },
1005
- { name: 'About', href: '/about' }
1006
- ]
1007
-
1008
- // Update active tab detection to include content type routes
1009
- const getActiveTab = () => {
1010
- if (pathname === '/') return 'Home'
1011
- if (pathname === '/products' || pathname.startsWith('/products/')) return 'Products'
1012
- if (pathname === '/articles' || pathname.startsWith('/articles/')) return 'Articles'
1013
- if (pathname === '/about') return 'About'
1014
- return null
1015
- }
1016
- ```
1017
-
1018
- **Pattern**: Use `.startsWith()` for active state detection to highlight navigation for both listing and detail pages.
1019
-
1020
- ### Component Architecture Best Practices
1021
-
1022
- **Proven Pattern**: Create two components per content type:
1023
- 1. **`[ContentType]Card.tsx`** - For listing views with preview information
1024
- 2. **`[ContentType]Renderer.tsx`** - For detail pages with complete information
1025
-
1026
- **Component Features**:
1027
- - **Cards**: Preview data, stock/status indicators, truncated feature lists, call-to-action links
1028
- - **Renderers**: Full data display, sticky sidebars, responsive grids, structured information hierarchy
1029
-
1030
- **File Organization**:
1031
- ```
1032
- app/
1033
- ├── components/
1034
- │ ├── ProductCard.tsx # Reusable card for listings
1035
- │ ├── ProductRenderer.tsx # Full page renderer for details
1036
- │ └── Header.tsx # Updated with new navigation
1037
- ├── products/
1038
- │ └── page.tsx # Listing page using ProductCard
1039
- └── [...slug]/
1040
- └── page.tsx # Updated to handle new content type routing
1041
- ```
1042
-
1043
- ### Build Process Validation
1044
-
1045
- **Critical**: Always run build process after major changes to catch TypeScript and import errors:
1046
-
1047
- ```bash
1048
- npm run build # Must complete without errors
1049
- npm run dev # Test in development mode
1050
- ```
1051
-
1052
- **Testing Checklist**:
1053
- - [ ] Build completes successfully
1054
- - [ ] Listing page loads and displays content
1055
- - [ ] Detail pages render via dynamic routing
1056
- - [ ] Navigation highlights correctly
1057
- - [ ] Mobile responsive design works
1058
- - [ ] Error states display appropriately
1059
-
1060
- ### GraphQL Schema Generation Workflow (CRITICAL LEARNING)
1061
-
1062
- The most important learning from the product catalog implementation is the **mandatory schema generation step**:
1063
-
1064
- **Problem**: DC imports successfully create content types and content, but GraphQL schema doesn't update automatically.
1065
- **Symptoms**:
1066
- - `nodeProducts` query returns "field not found" errors
1067
- - Content exists in Drupal but not accessible via GraphQL
1068
- - Route queries work but content type queries fail
1069
-
1070
- **Solution**: **ALWAYS run schema generation immediately after DC imports**:
1071
-
1072
- ```bash
1073
- # After any DC import, immediately run:
1074
- npm run generate-schema
1075
-
1076
- # This step is MANDATORY for GraphQL integration to work
1077
- ```
1078
-
1079
- **Why this is critical**:
1080
- - DC API creates Drupal content types but doesn't trigger GraphQL schema rebuilds
1081
- - The `generate-schema` script performs fresh introspection and updates local schema files
1082
- - Without this step, frontend development will fail with "type not found" errors
1083
- - This step bridges the gap between Drupal content type creation and Next.js GraphQL integration
1084
-
1085
- **Workflow Integration**:
1086
- 1. Import via DC API ✅
1087
- 2. **Run `npm run generate-schema`** ✅ ← CRITICAL STEP
1088
- 3. Verify schema includes new content type ✅
1089
- 4. Proceed with frontend development ✅
1090
-
1091
- This learning transforms the development workflow from "sometimes works" to "reliably works every time."
1092
-
1093
- ### Additional Key Learnings
1094
-
1095
- #### GraphQL Field Name Discovery (CRITICAL)
1096
- **Always verify actual GraphQL field names after DC import** - they may differ from field IDs used in import JSON.
1097
-
1098
- **Field Name Transformations**:
1099
- - `in_stock` becomes `inStock` (camelCase)
1100
- - `product_images` becomes `productImages` (camelCase)
1101
- - Simple field types work reliably: `string`, `boolean`, `string[]`
1102
-
1103
- **Discovery Method**: Check generated schema to understand actual field structure:
1104
- ```bash
1105
- # Check generated schema to understand actual field structure
1106
- npm run generate-schema
1107
- grep -A 10 -B 5 nodeProducts schema/schema.graphql
1108
- # This shows the actual field names and types available
1109
- ```
1110
-
1111
- #### Component Architecture Best Practices
1112
- **Proven Two-Component Pattern**:
1113
- 1. **`[ContentType]Card.tsx`** - Listing view with preview info, truncated content, CTAs
1114
- 2. **`[ContentType]Renderer.tsx`** - Detail page with complete data, image galleries, full specs
1115
-
1116
- **Navigation Integration Pattern**:
1117
- ```typescript
1118
- // Update navigationItems array
1119
- const navigationItems = [
1120
- { name: 'Home', href: '/' },
1121
- { name: 'Products', href: '/products' }, // Add new content type
1122
- // ...
1123
- ]
1124
-
1125
- // Use .startsWith() for active state detection
1126
- const getActiveTab = () => {
1127
- if (pathname === '/products' || pathname.startsWith('/products/')) return 'Products'
1128
- // ...
1129
- }
1130
- ```
1131
-
1132
- #### Field Type Best Practices (Revised)
1133
- **Reliable Field Types**:
1134
- - `string` - Simple text values (price, SKU, category)
1135
- - `string[]` - Multiple simple values (features, specifications) - **recommended for lists**
1136
- - `bool` - Boolean flags (in_stock, featured)
1137
- - `text` - Rich HTML content (body, descriptions)
1138
- - `image` - Single image upload
1139
- - `image[]` - Multiple image uploads
1140
-
1141
- **Recommendation**: Use `string[]` for lists (features, specifications) instead of `text[]` to avoid HTML rendering complexity.
1142
-
1143
- #### Build Process Integration
1144
- **Essential Commands Sequence**:
1145
- ```bash
1146
- # After DC import, always run:
1147
- npm run generate-schema # Updates GraphQL schema
1148
- npm run build # Validates TypeScript and builds
1149
- npm run dev # Test in development
1150
- ```
1151
-
1152
- **Success Criteria**:
1153
- - ✅ Build completes without TypeScript errors
1154
- - ✅ Listing page loads with HTTP 200
1155
- - ✅ Detail pages load with proper titles
1156
- - ✅ Navigation highlighting works correctly
1157
-
1158
- ## Summary
1159
-
1160
- This comprehensive guide enables "one-shot" prompts like "create a product catalog" to result in complete, working implementations from backend to frontend, with field name discovery and component architecture patterns proven in production.