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,1184 +0,0 @@
1
- # CLAUDE.md - End-to-End Content Type Development Guide
2
-
3
- This document provides Claude Code 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
- # PAT/PLATFORM COMMANDS (require npx decoupled-cli auth login):
810
- # List available spaces
811
- npx decoupled-cli spaces list
812
-
813
- # Check current default space
814
- npx decoupled-cli spaces current
815
-
816
- # Get health status of DC platform
817
- npx decoupled-cli health check
818
-
819
- # Check organization info
820
- npx decoupled-cli org info
821
-
822
- # Legacy platform content import
823
- npx decoupled-cli spaces content-import --file your-import.json --preview
824
- npx decoupled-cli spaces content-import --file your-import.json --json
825
- ```
826
-
827
- ## Content Import Command Reference
828
-
829
- ### OAuth + Drupal API Commands
830
-
831
- **Check content import API status:**
832
- ```bash
833
- npx decoupled-cli content status
834
- ```
835
-
836
- **Generate example import JSON:**
837
- ```bash
838
- npx decoupled-cli content import --example > my-content-type.json
839
- ```
840
-
841
- **Preview import (dry run):**
842
- ```bash
843
- npx decoupled-cli content import --file my-content-type.json --preview
844
- ```
845
-
846
- **Import content to Drupal:**
847
- ```bash
848
- npx decoupled-cli content import --file my-content-type.json
849
- ```
850
-
851
- ### Authentication Setup for Content Import
852
-
853
- **Quick OAuth Setup (recommended for local development):**
854
- ```bash
855
- # 1. Ensure .env.local has OAuth credentials
856
- # 2. Set up OAuth profile
857
- npx decoupled-cli auth oauth
858
-
859
- # 3. Verify it's working
860
- npx decoupled-cli content status
861
-
862
- # 4. Import content
863
- npx decoupled-cli content import --file your-content.json
864
- ```
865
-
866
- **When to use PAT vs OAuth:**
867
- - **PAT (Recommended)**: All operations including content import, space management, production deployments - **just like Contentful's CMA token**
868
- - **OAuth**: Alternative method for direct Drupal API access, local development with existing Drupal credentials
869
-
870
- ## Best Practices
871
-
872
- 1. **Always create sample content** during import for immediate testing
873
- 2. **Use descriptive field names** that are clear and consistent
874
- 3. **Follow existing naming conventions** in the codebase
875
- 4. **Test responsive design** on different screen sizes
876
- 5. **Handle empty/missing data gracefully** in components
877
- 6. **Use semantic HTML** for accessibility
878
- 7. **Include proper TypeScript types** for all data structures
879
- 8. **Test the full user journey** from listing to detail pages
880
- 9. **Use `dangerouslySetInnerHTML`** for processed HTML content from Drupal to avoid raw tag display
881
- 10. **Verify GraphQL field names** match actual schema, not DC field IDs
882
-
883
- ## Success Criteria
884
-
885
- A successful end-to-end implementation should:
886
- - [ ] Build without errors
887
- - [ ] Display content correctly on listing and detail pages
888
- - [ ] Handle navigation between pages
889
- - [ ] Render responsively on all device sizes
890
- - [ ] Show appropriate fallbacks for missing data
891
- - [ ] Follow the established design patterns
892
- - [ ] Include proper metadata and SEO elements
893
- - [ ] Work with the existing authentication and routing system
894
-
895
- ## Key Learnings and Common Mistakes
896
-
897
- Based on successful product catalog implementation, here are critical learnings to avoid common pitfalls:
898
-
899
- ### DC Import Format Issues
900
-
901
- **Problem**: Field values incorrectly formatted with "field_" prefix
902
- ```json
903
- // WRONG - This causes import failures
904
- "values": {
905
- "field_price": "$299.99",
906
- "field_in_stock": true
907
- }
908
-
909
- // CORRECT - Use field ID directly
910
- "values": {
911
- "price": "$299.99",
912
- "in_stock": true
913
- }
914
- ```
915
-
916
- **Solution**: Always use the field `id` value directly in content values, never add "field_" prefix.
917
-
918
- ### GraphQL Field Name Mapping
919
-
920
- **Problem**: Assuming GraphQL field names match DC field IDs
921
- ```typescript
922
- // WRONG - Field names may be transformed
923
- price: string // DC field ID
924
- fieldPrice: string // What you might expect in GraphQL
925
-
926
- // CORRECT - Check actual schema
927
- price: string // Actual GraphQL field name (can match DC ID)
928
- inStock: boolean // camelCase transformation
929
- productImages: object // snake_case to camelCase
930
- ```
931
-
932
- **Solution**: Always verify GraphQL field names by checking the actual schema or API response before writing queries.
933
-
934
- ### GraphQL Field Value Structure Discovery
935
-
936
- **Critical Learning**: GraphQL field values from DC imports return as simple types, not objects:
937
-
938
- ```typescript
939
- // WRONG - Assuming field values are objects with .processed
940
- fieldPrice?: {
941
- processed: string
942
- }
943
- fieldFeatures?: Array<{
944
- processed: string
945
- }>
946
-
947
- // CORRECT - DC imports create simple value fields
948
- price?: string
949
- features?: string[]
950
- ```
951
-
952
- **Discovery Process**: Check GraphQL schema to understand actual field structure:
953
- ```bash
954
- # Generate fresh schema and check field structure
955
- npm run generate-schema
956
- grep -i nodeProducts schema/schema.graphql
957
- # Check the actual schema files for field names and types
958
- ```
959
-
960
- **Solution**: Always check the generated GraphQL schema first to understand the actual field structure before writing TypeScript types and components.
961
-
962
- ### HTML Content Rendering
963
-
964
- **Problem**: Drupal processed HTML showing as raw tags in frontend
965
- ```jsx
966
- // WRONG - Shows: "Key Features <p>Fast Wireless Charging</p> <p>Qi-Compatible</p>"
967
- <span>{feature.processed}</span>
968
-
969
- // CORRECT - Renders HTML properly
970
- <span dangerouslySetInnerHTML={{ __html: feature.processed }} />
971
- ```
972
-
973
- **Better Solution**: Use `string[]` instead of `text[]` for simple lists to avoid HTML altogether:
974
- ```jsx
975
- // BEST - Clean, simple, no HTML rendering needed
976
- <span>{feature}</span> // Just plain text: "Fast Wireless Charging"
977
- ```
978
-
979
- **Solution**: Use `dangerouslySetInnerHTML` only when necessary for rich `text[]` fields. Prefer `string[]` for simple bullet lists.
980
-
981
- ### Field Type Selection
982
-
983
- **Best Practice**: Choose field types based on content structure:
984
- - `string[]` for bullet points, features, specifications, tags, categories (clean plain text, no HTML rendering issues)
985
- - `text[]` only when you need rich formatting within each item (rare - avoid unless necessary)
986
- - `text` for rich content descriptions that need HTML formatting
987
- - `string` for simple values like price, SKU, names (plain text, max 255 chars)
988
-
989
- **Recommendation**: For repeated items like product features, use `string[]` instead of `text[]` to avoid HTML rendering complexity and security concerns.
990
-
991
- ### Navigation Menu Integration Pattern
992
-
993
- **Essential Step**: Always update the main navigation when creating new content types:
994
-
995
- ```typescript
996
- // Update navigationItems array in app/components/Header.tsx
997
- const navigationItems = [
998
- { name: 'Home', href: '/' },
999
- { name: 'Products', href: '/products' }, // Add new content type
1000
- { name: 'Articles', href: '/articles' },
1001
- { name: 'About', href: '/about' }
1002
- ]
1003
-
1004
- // Update active tab detection to include content type routes
1005
- const getActiveTab = () => {
1006
- if (pathname === '/') return 'Home'
1007
- if (pathname === '/products' || pathname.startsWith('/products/')) return 'Products'
1008
- if (pathname === '/articles' || pathname.startsWith('/articles/')) return 'Articles'
1009
- if (pathname === '/about') return 'About'
1010
- return null
1011
- }
1012
- ```
1013
-
1014
- **Pattern**: Use `.startsWith()` for active state detection to highlight navigation for both listing and detail pages.
1015
-
1016
- ### Component Architecture Best Practices
1017
-
1018
- **Proven Pattern**: Create two components per content type:
1019
- 1. **`[ContentType]Card.tsx`** - For listing views with preview information
1020
- 2. **`[ContentType]Renderer.tsx`** - For detail pages with complete information
1021
-
1022
- **Component Features**:
1023
- - **Cards**: Preview data, stock/status indicators, truncated feature lists, call-to-action links
1024
- - **Renderers**: Full data display, sticky sidebars, responsive grids, structured information hierarchy
1025
-
1026
- **File Organization**:
1027
- ```
1028
- app/
1029
- ├── components/
1030
- │ ├── ProductCard.tsx # Reusable card for listings
1031
- │ ├── ProductRenderer.tsx # Full page renderer for details
1032
- │ └── Header.tsx # Updated with new navigation
1033
- ├── products/
1034
- │ └── page.tsx # Listing page using ProductCard
1035
- └── [...slug]/
1036
- └── page.tsx # Updated to handle new content type routing
1037
- ```
1038
-
1039
- ### Build Process Validation
1040
-
1041
- **Critical**: Always run build process after major changes to catch TypeScript and import errors:
1042
-
1043
- ```bash
1044
- npm run build # Must complete without errors
1045
- npm run dev # Test in development mode
1046
- ```
1047
-
1048
- **Testing Checklist**:
1049
- - [ ] Build completes successfully
1050
- - [ ] Listing page loads and displays content
1051
- - [ ] Detail pages render via dynamic routing
1052
- - [ ] Navigation highlights correctly
1053
- - [ ] Mobile responsive design works
1054
- - [ ] Error states display appropriately
1055
-
1056
- ### GraphQL Schema Generation Workflow (CRITICAL LEARNING)
1057
-
1058
- The most important learning from the product catalog implementation is the **mandatory schema generation step**:
1059
-
1060
- **Problem**: DC imports successfully create content types and content, but GraphQL schema doesn't update automatically.
1061
- **Symptoms**:
1062
- - `nodeProducts` query returns "field not found" errors
1063
- - Content exists in Drupal but not accessible via GraphQL
1064
- - Route queries work but content type queries fail
1065
-
1066
- **Solution**: **ALWAYS run schema generation immediately after DC imports**:
1067
-
1068
- ```bash
1069
- # After any DC import, immediately run:
1070
- npm run generate-schema
1071
-
1072
- # This step is MANDATORY for GraphQL integration to work
1073
- ```
1074
-
1075
- **Why this is critical**:
1076
- - DC API creates Drupal content types but doesn't trigger GraphQL schema rebuilds
1077
- - The `generate-schema` script performs fresh introspection and updates local schema files
1078
- - Without this step, frontend development will fail with "type not found" errors
1079
- - This step bridges the gap between Drupal content type creation and Next.js GraphQL integration
1080
-
1081
- **Workflow Integration**:
1082
- 1. Import via DC API ✅
1083
- 2. **Run `npm run generate-schema`** ✅ ← CRITICAL STEP
1084
- 3. Verify schema includes new content type ✅
1085
- 4. Proceed with frontend development ✅
1086
-
1087
- This learning transforms the development workflow from "sometimes works" to "reliably works every time."
1088
-
1089
- ### GraphQL Schema Field Name Discovery
1090
-
1091
- **Critical Process**: Always verify actual GraphQL field names after DC import - they may differ from field IDs used in import JSON.
1092
-
1093
- **Discovery Method**:
1094
- ```bash
1095
- # Check generated schema to understand actual field structure
1096
- npm run generate-schema
1097
- grep -A 10 -B 5 nodeProducts schema/schema.graphql
1098
- # This shows the actual field names and types available
1099
- ```
1100
-
1101
- **Key Learning**: DC field IDs may transform in GraphQL schema:
1102
- - `in_stock` becomes `inStock` (camelCase)
1103
- - `product_images` becomes `productImages` (camelCase)
1104
- - Simple field types work reliably (string, boolean, string[])
1105
-
1106
- ### Component Architecture Patterns
1107
-
1108
- **Proven Two-Component Pattern**:
1109
- 1. **`[ContentType]Card.tsx`** - Listing view component
1110
- - Preview information only
1111
- - Truncated content (features.slice(0, 3))
1112
- - Call-to-action buttons
1113
- - Stock/status indicators
1114
-
1115
- 2. **`[ContentType]Renderer.tsx`** - Detail page component
1116
- - Complete data display
1117
- - Full feature/specification lists
1118
- - Image galleries
1119
- - Action buttons and forms
1120
-
1121
- **Navigation Integration Pattern**:
1122
- ```typescript
1123
- // Always update navigationItems array
1124
- const navigationItems = [
1125
- { name: 'Home', href: '/' },
1126
- { name: 'Products', href: '/products' }, // Add new content type
1127
- // ...
1128
- ]
1129
-
1130
- // Use .startsWith() for active state detection
1131
- const getActiveTab = () => {
1132
- if (pathname === '/products' || pathname.startsWith('/products/')) return 'Products'
1133
- // ...
1134
- }
1135
- ```
1136
-
1137
- ### Field Type Best Practices (Revised)
1138
-
1139
- Based on successful product catalog implementation:
1140
-
1141
- **Reliable Field Types**:
1142
- - `string` - Simple text values (price, SKU, category)
1143
- - `string[]` - Multiple simple values (features, specifications)
1144
- - `bool` - Boolean flags (in_stock, featured)
1145
- - `text` - Rich HTML content (body, descriptions)
1146
- - `image[]` - Multiple image uploads
1147
- - `image` - Single image upload
1148
-
1149
- **Recommendation**: Use `string[]` for lists (features, specifications) instead of `text[]` to avoid HTML rendering complexity.
1150
-
1151
- ### Build Process Integration
1152
-
1153
- **Essential Commands Sequence**:
1154
- ```bash
1155
- # After DC import, always run:
1156
- npm run generate-schema # Updates GraphQL schema
1157
- npm run build # Validates TypeScript and builds
1158
- npm run dev # Test in development
1159
- ```
1160
-
1161
- **Success Criteria**:
1162
- - ✅ Build completes without TypeScript errors
1163
- - ✅ Listing page loads with HTTP 200
1164
- - ✅ Detail pages load with proper titles
1165
- - ✅ Navigation highlighting works correctly
1166
-
1167
- ### Deployment Field Mapping Reference
1168
-
1169
- **DC Import → GraphQL Schema Mapping**:
1170
- ```json
1171
- // DC import format:
1172
- {
1173
- "in_stock": true, // → GraphQL: inStock
1174
- "product_images": [...], // → GraphQL: productImages
1175
- "features": ["A", "B"], // → GraphQL: features
1176
- "specifications": ["X"] // → GraphQL: specifications
1177
- }
1178
- ```
1179
-
1180
- **Always verify field names in generated schema before writing TypeScript types.**
1181
-
1182
- ## Summary
1183
-
1184
- This comprehensive guide enables "one-shot" prompts like "create a product catalog" to result in complete, working implementations from backend to frontend, with known limitations documented for efficient troubleshooting.