decoupled-cli 2.0.0

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.
Files changed (55) hide show
  1. package/README.md +416 -0
  2. package/bin/decoupled-cli +3 -0
  3. package/dist/commands/auth.d.ts +3 -0
  4. package/dist/commands/auth.d.ts.map +1 -0
  5. package/dist/commands/auth.js +386 -0
  6. package/dist/commands/auth.js.map +1 -0
  7. package/dist/commands/config.d.ts +3 -0
  8. package/dist/commands/config.d.ts.map +1 -0
  9. package/dist/commands/config.js +84 -0
  10. package/dist/commands/config.js.map +1 -0
  11. package/dist/commands/content.d.ts +3 -0
  12. package/dist/commands/content.d.ts.map +1 -0
  13. package/dist/commands/content.js +199 -0
  14. package/dist/commands/content.js.map +1 -0
  15. package/dist/commands/download.d.ts +4 -0
  16. package/dist/commands/download.d.ts.map +1 -0
  17. package/dist/commands/download.js +127 -0
  18. package/dist/commands/download.js.map +1 -0
  19. package/dist/commands/health.d.ts +3 -0
  20. package/dist/commands/health.d.ts.map +1 -0
  21. package/dist/commands/health.js +28 -0
  22. package/dist/commands/health.js.map +1 -0
  23. package/dist/commands/org.d.ts +3 -0
  24. package/dist/commands/org.d.ts.map +1 -0
  25. package/dist/commands/org.js +73 -0
  26. package/dist/commands/org.js.map +1 -0
  27. package/dist/commands/spaces.d.ts +3 -0
  28. package/dist/commands/spaces.d.ts.map +1 -0
  29. package/dist/commands/spaces.js +613 -0
  30. package/dist/commands/spaces.js.map +1 -0
  31. package/dist/commands/tokens.d.ts +3 -0
  32. package/dist/commands/tokens.d.ts.map +1 -0
  33. package/dist/commands/tokens.js +90 -0
  34. package/dist/commands/tokens.js.map +1 -0
  35. package/dist/commands/usage.d.ts +3 -0
  36. package/dist/commands/usage.d.ts.map +1 -0
  37. package/dist/commands/usage.js +104 -0
  38. package/dist/commands/usage.js.map +1 -0
  39. package/dist/index.d.ts +3 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +49 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/lib/api.d.ts +40 -0
  44. package/dist/lib/api.d.ts.map +1 -0
  45. package/dist/lib/api.js +162 -0
  46. package/dist/lib/api.js.map +1 -0
  47. package/dist/lib/config.d.ts +63 -0
  48. package/dist/lib/config.d.ts.map +1 -0
  49. package/dist/lib/config.js +317 -0
  50. package/dist/lib/config.js.map +1 -0
  51. package/examples/.cursorrules +189 -0
  52. package/examples/CLAUDE.md +1080 -0
  53. package/examples/GEMINI.md +1056 -0
  54. package/examples/content-import-sample.json +114 -0
  55. package/package.json +55 -0
@@ -0,0 +1,317 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.configManager = exports.ConfigManager = void 0;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const os = __importStar(require("os"));
43
+ const chalk_1 = __importDefault(require("chalk"));
44
+ const dotenv_1 = require("dotenv");
45
+ const node_fetch_1 = __importDefault(require("node-fetch"));
46
+ // Conditionally import keytar to avoid native module errors in Docker environments.
47
+ let keytar = null;
48
+ let keytarAvailable = false;
49
+ try {
50
+ keytar = require('keytar');
51
+ keytarAvailable = true;
52
+ }
53
+ catch (error) {
54
+ console.warn(chalk_1.default.yellow('Warning: Secure credential storage not available (keytar). Falling back to environment variables.'));
55
+ keytarAvailable = false;
56
+ }
57
+ const CONFIG_DIR = path.join(os.homedir(), '.decoupled-cli');
58
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
59
+ const SERVICE_NAME = 'decoupled-cli';
60
+ const DEFAULT_CONFIG = {
61
+ currentProfile: 'default',
62
+ profiles: {},
63
+ settings: {
64
+ output: 'table',
65
+ timeout: 30,
66
+ color: true
67
+ }
68
+ };
69
+ class ConfigManager {
70
+ constructor() {
71
+ this.config = this.loadConfig();
72
+ this.loadEnvFile();
73
+ }
74
+ loadConfig() {
75
+ try {
76
+ if (!fs.existsSync(CONFIG_FILE)) {
77
+ this.ensureConfigDir();
78
+ this.saveConfig(DEFAULT_CONFIG);
79
+ return DEFAULT_CONFIG;
80
+ }
81
+ const configData = fs.readFileSync(CONFIG_FILE, 'utf-8');
82
+ return { ...DEFAULT_CONFIG, ...JSON.parse(configData) };
83
+ }
84
+ catch (error) {
85
+ console.warn(chalk_1.default.yellow('Warning: Could not load config, using defaults'));
86
+ return DEFAULT_CONFIG;
87
+ }
88
+ }
89
+ ensureConfigDir() {
90
+ if (!fs.existsSync(CONFIG_DIR)) {
91
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
92
+ }
93
+ }
94
+ saveConfig(config) {
95
+ this.ensureConfigDir();
96
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
97
+ }
98
+ getConfig() {
99
+ return this.config;
100
+ }
101
+ getCurrentProfile() {
102
+ return this.config.profiles[this.config.currentProfile] || null;
103
+ }
104
+ setProfile(name, profile) {
105
+ this.config.profiles[name] = profile;
106
+ this.saveConfig(this.config);
107
+ }
108
+ setCurrentProfile(name) {
109
+ if (!this.config.profiles[name]) {
110
+ throw new Error(`Profile '${name}' does not exist`);
111
+ }
112
+ this.config.currentProfile = name;
113
+ this.saveConfig(this.config);
114
+ }
115
+ deleteProfile(name) {
116
+ if (name === this.config.currentProfile) {
117
+ throw new Error('Cannot delete the current profile');
118
+ }
119
+ delete this.config.profiles[name];
120
+ this.saveConfig(this.config);
121
+ }
122
+ setSetting(key, value) {
123
+ this.config.settings[key] = value;
124
+ this.saveConfig(this.config);
125
+ }
126
+ async storeToken(profileName, token) {
127
+ if (keytarAvailable) {
128
+ try {
129
+ await keytar.setPassword(SERVICE_NAME, profileName, token);
130
+ return;
131
+ }
132
+ catch (error) {
133
+ console.warn(chalk_1.default.yellow(`Warning: Failed to store token in keychain: ${error}`));
134
+ }
135
+ }
136
+ // Fallback: Store token hash in config file (less secure but works in Docker).
137
+ const tokenHash = Buffer.from(token).toString('base64');
138
+ this.config.profiles[profileName] = {
139
+ ...this.config.profiles[profileName],
140
+ tokenHash
141
+ };
142
+ this.saveConfig(this.config);
143
+ console.warn(chalk_1.default.yellow('Token stored in config file (less secure). Consider using environment variables in production.'));
144
+ }
145
+ async getToken(profileName) {
146
+ const profile = profileName || this.config.currentProfile;
147
+ const profileConfig = this.config.profiles[profile];
148
+ // Check if profile is configured for OAuth
149
+ if (profileConfig?.useOAuth) {
150
+ // For .ddev.site domains, skip OAuth token generation to avoid OAuth module conflicts
151
+ if (profileConfig.baseUrl && profileConfig.baseUrl.includes('.ddev.site')) {
152
+ console.log(chalk_1.default.yellow('Skipping OAuth token for DDEV site - using development bypass'));
153
+ return 'dev-bypass-token'; // Return a dummy token
154
+ }
155
+ const oauthToken = await this.getOAuthTokenFromEnv();
156
+ if (oauthToken) {
157
+ return oauthToken;
158
+ }
159
+ }
160
+ // Try keytar first if available
161
+ if (keytarAvailable) {
162
+ try {
163
+ const token = await keytar.getPassword(SERVICE_NAME, profile);
164
+ if (token) {
165
+ return token;
166
+ }
167
+ }
168
+ catch (error) {
169
+ console.warn(chalk_1.default.yellow(`Warning: Failed to retrieve token from keychain: ${error}`));
170
+ }
171
+ }
172
+ // Fallback to config file stored token hash
173
+ if (profileConfig?.tokenHash) {
174
+ try {
175
+ return Buffer.from(profileConfig.tokenHash, 'base64').toString();
176
+ }
177
+ catch (error) {
178
+ console.warn(chalk_1.default.yellow(`Warning: Failed to decode token from config: ${error}`));
179
+ }
180
+ }
181
+ return null;
182
+ }
183
+ async deleteToken(profileName) {
184
+ // Delete from keytar if available
185
+ if (keytarAvailable) {
186
+ try {
187
+ await keytar.deletePassword(SERVICE_NAME, profileName);
188
+ }
189
+ catch (error) {
190
+ // Ignore if token doesn't exist
191
+ }
192
+ }
193
+ // Also remove token hash from config file
194
+ if (this.config.profiles[profileName]?.tokenHash) {
195
+ delete this.config.profiles[profileName].tokenHash;
196
+ this.saveConfig(this.config);
197
+ }
198
+ }
199
+ listProfiles() {
200
+ return Object.keys(this.config.profiles);
201
+ }
202
+ getConfigPath() {
203
+ return CONFIG_FILE;
204
+ }
205
+ setDefaultSpace(spaceId) {
206
+ this.config.defaultSpace = spaceId;
207
+ this.saveConfig(this.config);
208
+ }
209
+ getDefaultSpace() {
210
+ return this.config.defaultSpace;
211
+ }
212
+ clearDefaultSpace() {
213
+ delete this.config.defaultSpace;
214
+ this.saveConfig(this.config);
215
+ }
216
+ async resetConfig() {
217
+ try {
218
+ // Get list of all profiles to clear their tokens
219
+ const profiles = Object.keys(this.config.profiles);
220
+ // Clear all stored tokens
221
+ for (const profile of profiles) {
222
+ await this.deleteToken(profile);
223
+ }
224
+ // Reset configuration to defaults
225
+ this.config = { ...DEFAULT_CONFIG };
226
+ // Remove config file and recreate with defaults
227
+ if (fs.existsSync(CONFIG_FILE)) {
228
+ fs.unlinkSync(CONFIG_FILE);
229
+ }
230
+ // Save default configuration
231
+ this.saveConfig(this.config);
232
+ }
233
+ catch (error) {
234
+ throw new Error(`Failed to reset configuration: ${error}`);
235
+ }
236
+ }
237
+ /**
238
+ * Load .env.local file if it exists.
239
+ */
240
+ loadEnvFile() {
241
+ const envPath = path.join(process.cwd(), '.env.local');
242
+ if (fs.existsSync(envPath)) {
243
+ (0, dotenv_1.config)({ path: envPath });
244
+ }
245
+ }
246
+ /**
247
+ * Get OAuth token by performing client credentials flow.
248
+ */
249
+ async getOAuthTokenFromEnv() {
250
+ const clientId = process.env.DRUPAL_CLIENT_ID;
251
+ const clientSecret = process.env.DRUPAL_CLIENT_SECRET;
252
+ const drupalBaseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL;
253
+ if (!clientId || !clientSecret || !drupalBaseUrl) {
254
+ return null;
255
+ }
256
+ try {
257
+ // Perform OAuth2 client credentials flow
258
+ const tokenUrl = `${drupalBaseUrl}/oauth/token`;
259
+ const response = await (0, node_fetch_1.default)(tokenUrl, {
260
+ method: 'POST',
261
+ headers: {
262
+ 'Content-Type': 'application/x-www-form-urlencoded',
263
+ 'Accept': 'application/json',
264
+ },
265
+ body: new URLSearchParams({
266
+ grant_type: 'client_credentials',
267
+ client_id: clientId,
268
+ client_secret: clientSecret,
269
+ }),
270
+ });
271
+ if (!response.ok) {
272
+ console.warn(chalk_1.default.yellow(`OAuth token request failed: ${response.status} ${response.statusText}`));
273
+ return null;
274
+ }
275
+ const tokenData = await response.json();
276
+ return tokenData.access_token || null;
277
+ }
278
+ catch (error) {
279
+ console.warn(chalk_1.default.yellow(`OAuth token request error: ${error instanceof Error ? error.message : String(error)}`));
280
+ return null;
281
+ }
282
+ }
283
+ /**
284
+ * Check if OAuth credentials are available in environment.
285
+ */
286
+ hasOAuthCredentials() {
287
+ return !!(process.env.DRUPAL_CLIENT_ID &&
288
+ process.env.DRUPAL_CLIENT_SECRET &&
289
+ process.env.NEXT_PUBLIC_DRUPAL_BASE_URL);
290
+ }
291
+ /**
292
+ * Get Drupal base URL from environment.
293
+ */
294
+ getDrupalBaseUrl() {
295
+ return process.env.NEXT_PUBLIC_DRUPAL_BASE_URL || null;
296
+ }
297
+ /**
298
+ * Set profile to use OAuth authentication.
299
+ */
300
+ setProfileOAuth(profileName, useOAuth) {
301
+ if (!this.config.profiles[profileName]) {
302
+ throw new Error(`Profile '${profileName}' does not exist`);
303
+ }
304
+ this.config.profiles[profileName].useOAuth = useOAuth;
305
+ this.saveConfig(this.config);
306
+ }
307
+ /**
308
+ * Check if current profile uses OAuth authentication.
309
+ */
310
+ isCurrentProfileOAuth() {
311
+ const currentProfile = this.getCurrentProfile();
312
+ return currentProfile?.useOAuth || false;
313
+ }
314
+ }
315
+ exports.ConfigManager = ConfigManager;
316
+ exports.configManager = new ConfigManager();
317
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,189 @@
1
+ You are the Cursor AI working in a Next.js + Drupal monorepo. Follow these rules to deliver complete, working features end-to-end.
2
+
3
+ # Project Overview
4
+ - Architecture: Headless Drupal backend with Next.js frontend
5
+ - Backend: Drupal 11 with GraphQL Compose and DCloud Import API
6
+ - Frontend: Next.js 15 with TypeScript, Tailwind CSS, Apollo GraphQL
7
+ - Environment: DDEV local development
8
+
9
+ # Environment Configuration
10
+ - Environment variables in `.env.local`:
11
+ - `NEXT_PUBLIC_DRUPAL_BASE_URL` – Drupal base URL
12
+ - `DRUPAL_CLIENT_ID` / `DRUPAL_CLIENT_SECRET` – OAuth credentials
13
+ - `DRUPAL_REVALIDATE_SECRET` – On-demand revalidation secret
14
+ - `NODE_TLS_REJECT_UNAUTHORIZED=0` – Allow self-signed certs (dev)
15
+
16
+ # DCloud CLI Usage
17
+ **CRITICAL**: Use DCloud CLI instead of direct API calls.
18
+
19
+ First-time setup:
20
+ ```bash
21
+ dcloud auth login # Authenticate with platform
22
+ dcloud spaces use <id> # Set default space (optional)
23
+ dcloud spaces current # Verify setup
24
+ ```
25
+
26
+ # End-to-End Workflow
27
+ When asked to implement a new content type (e.g., “create a product page”), complete all steps:
28
+
29
+ 1) Verify CLI authentication
30
+ ```bash
31
+ dcloud auth status # Check if authenticated
32
+ dcloud spaces list # List available spaces
33
+ dcloud spaces use <id> # Set default space if needed
34
+ ```
35
+
36
+ 2) Plan content type
37
+ - Define name + machine name
38
+ - List fields and types
39
+ - Determine components, routes, display needs
40
+
41
+ 3) Create DCloud Import JSON
42
+ - Get example format: `dcloud spaces content-import --example`
43
+ - Include model definition and sample content
44
+ - Important: In `values`, use field ID directly, never with `field_` prefix
45
+ - For image fields: Use full URLs with Drupal domain from `.env.local`, not relative paths
46
+
47
+ Template:
48
+ ```json
49
+ {
50
+ "model": [
51
+ {
52
+ "bundle": "content_type_name",
53
+ "description": "Description",
54
+ "label": "Content Type Label",
55
+ "body": true,
56
+ "fields": [
57
+ { "id": "field_name", "label": "Field Label", "type": "text|string|image|datetime|bool|text[]" }
58
+ ]
59
+ }
60
+ ],
61
+ "content": [
62
+ {
63
+ "id": "item1",
64
+ "type": "node.content_type_name",
65
+ "path": "/content-type/item-slug",
66
+ "values": {
67
+ "title": "Item Title",
68
+ "body": "<p>Body content</p>",
69
+ "field_name": "field_value",
70
+ "image_field": {
71
+ "uri": "${DRUPAL_BASE_URL}/modules/custom/dcloud_import/resources/placeholder.png",
72
+ "alt": "Image description",
73
+ "title": "Image title"
74
+ }
75
+ }
76
+ }
77
+ ]
78
+ }
79
+ ```
80
+
81
+ 4) Import via DCloud CLI
82
+ ```bash
83
+ # Import content type and sample content
84
+ dcloud spaces content-import --file content-type-import.json --preview # Preview first
85
+ dcloud spaces content-import --file content-type-import.json # Apply changes
86
+
87
+ # Generate example if needed
88
+ dcloud spaces content-import --example > content-type-import.json
89
+ ```
90
+ - **CRITICAL**: Immediately run schema generation:
91
+ ```bash
92
+ npm run generate-schema
93
+ ```
94
+ - Verify: machine names, created nodes, and GraphQL schema updates
95
+
96
+ 4) Implement frontend
97
+ - Files:
98
+ ```
99
+ app/
100
+ components/
101
+ [ContentType]Card.tsx
102
+ [ContentType]Renderer.tsx
103
+ DynamicIcon.tsx (optional)
104
+ [content-type]/page.tsx
105
+ [...slug]/page.tsx (update)
106
+ lib/
107
+ queries.ts (update)
108
+ types.ts (update)
109
+ ```
110
+ - GraphQL: add list query and update `GET_NODE_BY_PATH` cases
111
+ - Types: add `Drupal[ContentType]` and `[...]Data` interfaces
112
+ - Components: card + renderer; use `dangerouslySetInnerHTML` for processed HTML
113
+ - Routing: handle in dynamic route switch by `__typename`
114
+ - Navigation: add link in `app/components/Header.tsx`; update `navigationItems` and active detection with `pathname.startsWith('/route/')`
115
+ - DCloud GraphQL fields return simple types (`string`, `string[]`, `bool`, etc.), not `{ processed }` objects — probe schema with a quick query before typing components
116
+
117
+ 5) Build and test
118
+ - `npm run build` → fix TypeScript/build errors
119
+ - `npm run dev` → verify listing and detail views
120
+ - Check URLs: `/[content-type]`, `/[content-type]/[slug]`
121
+
122
+ Checklist:
123
+ - [ ] DCloud import succeeds
124
+ - [ ] **Schema generation runs (`npm run generate-schema`)**
125
+ - [ ] GraphQL schema exposes fields
126
+ - [ ] Types compile
127
+ - [ ] Build passes
128
+ - [ ] Listing and detail pages render
129
+ - [ ] Navigation works, responsive design verified
130
+
131
+ # Component Architecture
132
+ - Per type, create `[ContentType]Card.tsx` (listing) and `[ContentType]Renderer.tsx` (detail)
133
+ - Cards: preview data + CTA; Renderers: full display, responsive layout
134
+ - Place components in `app/components/`; listing in `app/[content-type]/page.tsx`; update `app/[...slug]/page.tsx` to route by `__typename`
135
+
136
+ # Field Types Reference
137
+ - `text` – Rich HTML field
138
+ - `string` – Short text (≤255 chars)
139
+ - `image` – Single image upload
140
+ - `image[]` – Multiple image uploads
141
+ - `datetime` – Date/time
142
+ - `bool` – Boolean
143
+ - `text[]` – Rich HTML list
144
+ - `string[]` – Plain text list (recommended for features/specs)
145
+
146
+ # Common Patterns
147
+ - Product: `price (string)`, `product_images (image[])`, `in_stock (bool)`, `features (string[])`
148
+ - Image URI: `{"uri": "${DRUPAL_BASE_URL}/modules/custom/dcloud_import/resources/placeholder.png", "alt": "Product image", "title": "Product"}`
149
+ - Event: `event_date (datetime)`, `location (string)`, `speakers (string[])`
150
+ - Team: `position (string)`, `profile_image (image)`, `bio (text)`
151
+ - Image URI: `{"uri": "${DRUPAL_BASE_URL}/modules/custom/dcloud_import/resources/placeholder.png", "alt": "Team member photo", "title": "Profile"}`
152
+ - Case Study: `project_url (string)`, `technologies (string[])`, `project_images (image[])`
153
+ - Image URI: `{"uri": "${DRUPAL_BASE_URL}/modules/custom/dcloud_import/resources/placeholder.png", "alt": "Project screenshot", "title": "Interface"}`
154
+
155
+ # Troubleshooting
156
+ - DCloud import fails: check OAuth, JSON structure, no `field_` in values
157
+ - **Schema not updated**: ALWAYS run `npm run generate-schema` after DCloud imports
158
+ - GraphQL errors: confirm content type exists, clear GraphQL cache, regenerate schema
159
+ - Build errors: verify types, imports, query syntax
160
+ - Content not showing: confirm field names match GraphQL schema; for HTML use `dangerouslySetInnerHTML={{ __html: field.processed }}`
161
+ - **Field name mismatches**: DCloud field IDs may transform (e.g., `in_stock` → `inStock` in GraphQL)
162
+
163
+ Debug commands:
164
+ ```bash
165
+ dcloud auth status # Check authentication
166
+ dcloud spaces list # List available spaces
167
+ dcloud spaces current # Show default space
168
+ dcloud health check # Platform status
169
+ dcloud spaces content-import --file test.json --preview # Preview import
170
+ # MOST IMPORTANT: Generate fresh schema after any import
171
+ npm run generate-schema
172
+ # Check schema includes your content type
173
+ grep -i [content_type] schema/schema.graphql
174
+ ```
175
+ ```
176
+
177
+ # Best Practices
178
+ 1. **ALWAYS run `npm run generate-schema` immediately after DCloud imports**
179
+ 2. Create sample content for immediate testing
180
+ 3. Prefer `string[]` for simple lists; use `text[]` only when necessary
181
+ 4. Verify GraphQL field names against the actual schema (may differ from DCloud field IDs)
182
+ 5. Handle empty/missing data gracefully
183
+ 6. Keep TypeScript types accurate and complete
184
+ 7. Use semantic HTML and ensure responsive design
185
+ 8. **Navigation integration**: Update `navigationItems` array and use `.startsWith()` for active detection
186
+ 9. **Component architecture**: Create Card + Renderer components per content type
187
+
188
+ # Success Criteria
189
+ - Builds without errors; routes render correctly; navigation works; responsive; proper fallbacks; follows design patterns; integrates with existing auth and routing.