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.
- package/README.md +416 -0
- package/bin/decoupled-cli +3 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +386 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +84 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/content.d.ts +3 -0
- package/dist/commands/content.d.ts.map +1 -0
- package/dist/commands/content.js +199 -0
- package/dist/commands/content.js.map +1 -0
- package/dist/commands/download.d.ts +4 -0
- package/dist/commands/download.d.ts.map +1 -0
- package/dist/commands/download.js +127 -0
- package/dist/commands/download.js.map +1 -0
- package/dist/commands/health.d.ts +3 -0
- package/dist/commands/health.d.ts.map +1 -0
- package/dist/commands/health.js +28 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/org.d.ts +3 -0
- package/dist/commands/org.d.ts.map +1 -0
- package/dist/commands/org.js +73 -0
- package/dist/commands/org.js.map +1 -0
- package/dist/commands/spaces.d.ts +3 -0
- package/dist/commands/spaces.d.ts.map +1 -0
- package/dist/commands/spaces.js +613 -0
- package/dist/commands/spaces.js.map +1 -0
- package/dist/commands/tokens.d.ts +3 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +90 -0
- package/dist/commands/tokens.js.map +1 -0
- package/dist/commands/usage.d.ts +3 -0
- package/dist/commands/usage.d.ts.map +1 -0
- package/dist/commands/usage.js +104 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +40 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +162 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/config.d.ts +63 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +317 -0
- package/dist/lib/config.js.map +1 -0
- package/examples/.cursorrules +189 -0
- package/examples/CLAUDE.md +1080 -0
- package/examples/GEMINI.md +1056 -0
- package/examples/content-import-sample.json +114 -0
- 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.
|