gatsby-attainlabs-cms 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/gatsby-browser.js CHANGED
@@ -1,7 +1,7 @@
1
1
  exports.onClientEntry = (_, pluginOptions) => {
2
2
  const { brand } = pluginOptions;
3
3
 
4
- if (brand === "LendDirect") {
4
+ if (brand === "LendDirect" || brand === "Cash Money") {
5
5
  import("@fontsource/open-sans/300.css");
6
6
  import("@fontsource/open-sans/400.css");
7
7
  import("@fontsource/open-sans/500.css");
package/gatsby-node.js CHANGED
@@ -4,7 +4,7 @@ const path = require("path");
4
4
  const prettier = require("prettier");
5
5
  const fse = require("fs-extra");
6
6
  const crypto = require("crypto");
7
- const { graphql } = require("gatsby");
7
+ const { graphql } = require("gatsby"); // kept (even if unused here) to match your existing file
8
8
 
9
9
  const brands = {
10
10
  LendDirect: "lenddirect",
@@ -15,7 +15,227 @@ const brands = {
15
15
 
16
16
  const generateSliceWrapper = () => {};
17
17
 
18
- const generatePage = async (blocks, layout, isNested) => {
18
+ /**
19
+ * ----------------------------
20
+ * Promise-based download helpers
21
+ * ----------------------------
22
+ */
23
+
24
+ function httpsGet(url, options) {
25
+ return new Promise((resolve, reject) => {
26
+ https
27
+ .get(url, options, (res) => {
28
+ let data = "";
29
+ res.on("data", (chunk) => (data += chunk));
30
+ res.on("end", () => resolve({ res, data }));
31
+ })
32
+ .on("error", reject);
33
+ });
34
+ }
35
+
36
+ async function listItems({ org, project, repo, repoPath, branch, options }) {
37
+ const listUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
38
+ repo
39
+ )}/items?scopePath=${encodeURIComponent(
40
+ repoPath
41
+ )}&recursionLevel=full&includeContentMetadata=true&versionDescriptor.version=${encodeURIComponent(
42
+ branch
43
+ )}&versionDescriptor.versionType=branch&api-version=7.0`;
44
+
45
+ const { res, data } = await httpsGet(listUrl, options);
46
+
47
+ if (res.statusCode !== 200) {
48
+ throw new Error(
49
+ `Failed to list items for ${repoPath}: ${res.statusCode} ${res.statusMessage}\n${data}`
50
+ );
51
+ }
52
+
53
+ const result = JSON.parse(data);
54
+ const posix = path.posix;
55
+
56
+ // Exclude files named config.tsx and preview.tsx (same as your original logic)
57
+ return (result.value || []).filter(
58
+ (i) =>
59
+ !i.isFolder &&
60
+ i.gitObjectType === "blob" &&
61
+ posix.basename(i.path) !== "config.tsx" &&
62
+ posix.basename(i.path) !== "preview.tsx"
63
+ );
64
+ }
65
+
66
+ function downloadFileAsync({
67
+ org,
68
+ project,
69
+ repo,
70
+ repoPath,
71
+ filePath,
72
+ localBasePath,
73
+ branch,
74
+ options,
75
+ }) {
76
+ return new Promise((resolve, reject) => {
77
+ const fileUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
78
+ repo
79
+ )}/items?path=${encodeURIComponent(
80
+ filePath
81
+ )}&versionDescriptor.version=${encodeURIComponent(
82
+ branch
83
+ )}&versionDescriptor.versionType=branch&api-version=7.0&download=true`;
84
+
85
+ const posix = path.posix;
86
+ const relativePath = posix.relative(repoPath, filePath);
87
+ const destFile = path.join(localBasePath, ...relativePath.split("/"));
88
+ const destDir = path.dirname(destFile);
89
+
90
+ if (!fs.existsSync(destDir)) {
91
+ fs.mkdirSync(destDir, { recursive: true });
92
+ }
93
+
94
+ const doRequest = (url) => {
95
+ https
96
+ .get(url, options, (res) => {
97
+ // Follow redirects
98
+ if (
99
+ res.statusCode &&
100
+ res.statusCode >= 300 &&
101
+ res.statusCode < 400 &&
102
+ res.headers.location
103
+ ) {
104
+ res.resume();
105
+ return doRequest(res.headers.location);
106
+ }
107
+
108
+ if (res.statusCode !== 200) {
109
+ res.resume();
110
+ return reject(
111
+ new Error(
112
+ `Failed to download ${filePath}: ${res.statusCode} ${res.statusMessage}`
113
+ )
114
+ );
115
+ }
116
+
117
+ const uniqueId = Math.random().toString(36).substring(7);
118
+ const tmpFile = destFile + "." + uniqueId + ".part";
119
+ const fileStream = fs.createWriteStream(tmpFile);
120
+
121
+ res.pipe(fileStream);
122
+
123
+ fileStream.on("error", reject);
124
+ res.on("error", reject);
125
+
126
+ fileStream.on("finish", () => {
127
+ fileStream.close(() => {
128
+ try {
129
+ const ext = path.extname(destFile).toLowerCase();
130
+ let generatedComment = "";
131
+ if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
132
+ generatedComment =
133
+ "// GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.\n";
134
+ }
135
+
136
+ const fileContent = fs.readFileSync(tmpFile, "utf8");
137
+ fs.writeFileSync(destFile, generatedComment + fileContent);
138
+ fs.unlinkSync(tmpFile);
139
+
140
+ resolve(destFile);
141
+ } catch (e) {
142
+ reject(e);
143
+ }
144
+ });
145
+ });
146
+ })
147
+ .on("error", reject);
148
+ };
149
+
150
+ doRequest(fileUrl);
151
+ });
152
+ }
153
+
154
+ async function downloadTarget({
155
+ org,
156
+ project,
157
+ repo,
158
+ repoPath,
159
+ localBasePath,
160
+ branch,
161
+ options,
162
+ }) {
163
+ const items = await listItems({
164
+ org,
165
+ project,
166
+ repo,
167
+ repoPath,
168
+ branch,
169
+ options,
170
+ });
171
+
172
+ console.log(`Found ${items.length} files in ${repoPath}`);
173
+
174
+ const results = await Promise.all(
175
+ items.map((item) =>
176
+ downloadFileAsync({
177
+ org,
178
+ project,
179
+ repo,
180
+ repoPath,
181
+ filePath: item.path,
182
+ localBasePath,
183
+ branch,
184
+ options,
185
+ })
186
+ )
187
+ );
188
+
189
+ console.log(`✅ Completed downloading all files for target: ${repoPath}`);
190
+ return results;
191
+ }
192
+
193
+ function rewriteSliceWrapper(fetchConfig) {
194
+ const sliceWrapperPath = path.resolve(
195
+ "./src/cms/components/sliceWrapper.tsx"
196
+ );
197
+
198
+ if (!fs.existsSync(sliceWrapperPath)) {
199
+ console.warn(
200
+ `⚠️ [gatsby-attainlabs-cms] sliceWrapper.tsx not found at ${sliceWrapperPath}. Skipping rewrite.`
201
+ );
202
+ return;
203
+ }
204
+
205
+ let content = fs.readFileSync(sliceWrapperPath, "utf8");
206
+
207
+ if (!fetchConfig || !fetchConfig.includes("blogs")) {
208
+ content = content.replace(
209
+ /# CHECK_BLOGS_START[\s\S]*?# CHECK_BLOGS_END/g,
210
+ ""
211
+ );
212
+ content = content.replace(
213
+ /\/\* CHECK_BLOGS_START \*\/[\s\S]*?\/\* CHECK_BLOGS_END \*\//g,
214
+ ""
215
+ );
216
+ }
217
+
218
+ if (!fetchConfig || !fetchConfig.includes("disclaimers")) {
219
+ content = content.replace(
220
+ /# CHECK_DISCLAIMERS_START[\s\S]*?# CHECK_DISCLAIMERS_END/g,
221
+ ""
222
+ );
223
+ content = content.replace(
224
+ /\/\* CHECK_DISCLAIMERS_START \*\/[\s\S]*?\/\* CHECK_DISCLAIMERS_END \*\//g,
225
+ ""
226
+ );
227
+ }
228
+
229
+ fs.writeFileSync(sliceWrapperPath, content);
230
+ }
231
+
232
+ /**
233
+ * ----------------------------
234
+ * Existing code (mostly unchanged)
235
+ * ----------------------------
236
+ */
237
+
238
+ const generatePage = async (blocks, layout, isNested, environment, page) => {
19
239
  // Validate input parameters
20
240
  if (!Array.isArray(blocks)) {
21
241
  throw new Error("Invalid parameters passed to createPage.");
@@ -33,8 +253,8 @@ const generatePage = async (blocks, layout, isNested) => {
33
253
  import SEO from "../${isNested && "../"}../cms/components/SEO";
34
254
 
35
255
  export const Head = ({ pageContext }: any) => {
36
- const blocks = pageContext.blocks;
37
- const meta = blocks.root.props.meta;
256
+ const blocks = ${environment === "staging" ? (page.draft ? "pageContext.draft.blocks" : "pageContext.blocks") : "pageContext.blocks"}
257
+ const meta = blocks.root.props.meta
38
258
  return (
39
259
  <SEO
40
260
  title={meta ? meta.title : ""}
@@ -90,6 +310,7 @@ const getAllBlogs = (data) => {
90
310
  };
91
311
 
92
312
  require("dotenv").config();
313
+
93
314
  // Load PAT from env instead of hardcoding (fallback to old const for now but warn)
94
315
  exports.onPreInit = async (_, pluginOptions) => {
95
316
  const {
@@ -135,7 +356,7 @@ exports.onPreInit = async (_, pluginOptions) => {
135
356
 
136
357
  // Helper to fetch latest PR branch
137
358
  const getLatestPullRequestBranch = (org, project, repo, pat) => {
138
- return new Promise((resolve, reject) => {
359
+ return new Promise((resolve) => {
139
360
  const options = {
140
361
  headers: {
141
362
  Authorization: `Basic ${Buffer.from(`:${pat}`).toString("base64")}`,
@@ -143,8 +364,7 @@ exports.onPreInit = async (_, pluginOptions) => {
143
364
  },
144
365
  };
145
366
 
146
- // Fetch top 10 active PRs to ensure we find the latest one
147
- // Note: Azure DevOps usually sorts by ID desc (creation time), but fetching a batch is safer.
367
+ // Fetch top 10 active PRs
148
368
  const url = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
149
369
  repo
150
370
  )}/pullrequests?searchCriteria.status=active&top=10&api-version=7.0`;
@@ -164,8 +384,6 @@ exports.onPreInit = async (_, pluginOptions) => {
164
384
  try {
165
385
  const result = JSON.parse(data);
166
386
  if (result.value && result.value.length > 0) {
167
- // Sort by creationDate descending to get the latest created PR
168
- // (Since we can't easily get 'last updated/pushed' without extra calls, creation is the standard proxy)
169
387
  const latestPr = result.value.sort(
170
388
  (a, b) =>
171
389
  new Date(b.creationDate).getTime() -
@@ -230,18 +448,6 @@ exports.onPreInit = async (_, pluginOptions) => {
230
448
  }
231
449
  }
232
450
 
233
- const localTargets = [
234
- {
235
- localPath: path.resolve(__dirname, "src/sliceWrapper.tsx"),
236
- targetPath: path.resolve("./src/cms/components/"),
237
- },
238
- {
239
- localPath: path.resolve(__dirname, "src/map.ts"),
240
- targetPath: path.resolve("./src/cms/components/"),
241
- },
242
- ];
243
-
244
- // List of folders to download from
245
451
  const targets = [
246
452
  {
247
453
  repoPath: `/apps/cms/src/cms/editors/visual-block-editor/brands/${brands[brand]}/`,
@@ -264,179 +470,30 @@ exports.onPreInit = async (_, pluginOptions) => {
264
470
  },
265
471
  };
266
472
 
267
- // Copy local files first
268
- localTargets.forEach(({ localPath, targetPath }) => {
269
- if (!fs.existsSync(targetPath)) {
270
- fs.mkdirSync(targetPath, { recursive: true });
271
- // console.log(`📂 Created directory: ${targetPath}`);
272
- }
273
- const fileName = path.basename(localPath);
274
- const destFile = path.join(targetPath, fileName);
275
-
276
- if (fileName === "sliceWrapper.tsx") {
277
- let content = fs.readFileSync(localPath, "utf8");
278
-
279
- if (!fetchConfig || !fetchConfig.includes("blogs")) {
280
- content = content.replace(
281
- /# CHECK_BLOGS_START[\s\S]*?# CHECK_BLOGS_END/g,
282
- ""
283
- );
284
- content = content.replace(
285
- /\/\* CHECK_BLOGS_START \*\/[\s\S]*?\/\* CHECK_BLOGS_END \*\//g,
286
- ""
287
- );
288
- }
289
-
290
- if (!fetchConfig || !fetchConfig.includes("disclaimers")) {
291
- content = content.replace(
292
- /# CHECK_DISCLAIMERS_START[\s\S]*?# CHECK_DISCLAIMERS_END/g,
293
- ""
294
- );
295
- content = content.replace(
296
- /\/\* CHECK_DISCLAIMERS_START \*\/[\s\S]*?\/\* CHECK_DISCLAIMERS_END \*\//g,
297
- ""
298
- );
299
- }
300
-
301
- fs.writeFileSync(localPath, content);
302
- fs.writeFileSync(destFile, content);
303
- console.log(`✅ Copied and transformed ${destFile}`);
304
- } else {
305
- fs.copyFileSync(localPath, destFile);
306
- console.log(`✅ Copied ${destFile}`);
307
- }
308
- });
309
-
310
- // Loop through targets
311
- targets.forEach(({ repoPath, localBasePath }) => {
312
- const listUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
313
- repo
314
- )}/items?scopePath=${encodeURIComponent(
315
- repoPath
316
- )}&recursionLevel=full&includeContentMetadata=true&versionDescriptor.version=${encodeURIComponent(
317
- branch
318
- )}&versionDescriptor.versionType=branch&api-version=7.0`;
319
-
320
- https
321
- .get(listUrl, options, (res) => {
322
- let data = "";
323
- res.on("data", (chunk) => (data += chunk));
324
- res.on("end", () => {
325
- if (res.statusCode !== 200) {
326
- console.error(
327
- `Failed to list items for ${repoPath}: ${res.statusCode} ${res.statusMessage}`
328
- );
329
- console.error(data);
330
- return;
331
- }
332
-
333
- const result = JSON.parse(data);
334
- const posix = path.posix;
335
-
336
- // Exclude files named config.tsx
337
- const items = (result.value || []).filter(
338
- (i) =>
339
- !i.isFolder &&
340
- i.gitObjectType === "blob" &&
341
- posix.basename(i.path) !== "config.tsx" &&
342
- posix.basename(i.path) !== "preview.tsx"
343
- );
344
-
345
- console.log(`Found ${items.length} files in ${repoPath}`);
346
- let completedDownloads = 0;
347
-
348
- items.forEach((item) => {
349
- downloadFile(item.path, repoPath, localBasePath, () => {
350
- completedDownloads++;
351
- if (completedDownloads === items.length) {
352
- console.log(
353
- `✅ Completed downloading all files for target: ${repoPath}`
354
- );
355
- }
356
- });
357
- });
358
- });
473
+ /**
474
+ * Awaitable download pipeline:
475
+ * - list items
476
+ * - download all files
477
+ * - only after all targets complete, rewrite sliceWrapper.tsx
478
+ */
479
+ await Promise.all(
480
+ targets.map((t) =>
481
+ downloadTarget({
482
+ org,
483
+ project,
484
+ repo,
485
+ repoPath: t.repoPath,
486
+ localBasePath: t.localBasePath,
487
+ branch,
488
+ options,
359
489
  })
360
- .on("error", (err) => {
361
- console.error("Request error:", err.message);
362
- });
363
- });
364
-
365
- // Download a single file preserving folder structure under localBasePath
366
- function downloadFile(filePath, repoPath, localBasePath, callback) {
367
- const fileUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
368
- repo
369
- )}/items?path=${encodeURIComponent(
370
- filePath
371
- )}&versionDescriptor.version=${encodeURIComponent(
372
- branch
373
- )}&versionDescriptor.versionType=branch&api-version=7.0&download=true`;
374
-
375
- const posix = path.posix;
376
- const relativePath = posix.relative(repoPath, filePath); // structure under repoPath
377
- const destFile = path.join(localBasePath, ...relativePath.split("/"));
378
- const destDir = path.dirname(destFile);
379
-
380
- if (!fs.existsSync(destDir)) {
381
- fs.mkdirSync(destDir, { recursive: true });
382
- // console.log(`📂 Created directory: ${destDir}`);
383
- }
384
-
385
- const doRequest = (url) => {
386
- https
387
- .get(url, options, (res) => {
388
- if (
389
- res.statusCode &&
390
- res.statusCode >= 300 &&
391
- res.statusCode < 400 &&
392
- res.headers.location
393
- ) {
394
- return doRequest(res.headers.location);
395
- }
396
-
397
- if (res.statusCode !== 200) {
398
- console.error(
399
- `Failed to download ${filePath}: ${res.statusCode} ${res.statusMessage}`
400
- );
401
- res.resume();
402
- return;
403
- }
404
-
405
- const uniqueId = Math.random().toString(36).substring(7);
406
- const tmpFile = destFile + "." + uniqueId + ".part";
407
- const fileStream = fs.createWriteStream(tmpFile);
408
-
409
- res.pipe(fileStream);
410
- fileStream.on("finish", () => {
411
- fileStream.close(() => {
412
- const ext = path.extname(destFile).toLowerCase();
413
- let generatedComment = "";
414
- if (
415
- ext === ".ts" ||
416
- ext === ".tsx" ||
417
- ext === ".js" ||
418
- ext === ".jsx"
419
- ) {
420
- generatedComment =
421
- "// GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.\n";
422
- }
423
- const fileContent = fs.readFileSync(tmpFile, "utf8");
424
- fs.writeFileSync(destFile, generatedComment + fileContent);
425
-
426
- // Remove the temporary file
427
- fs.unlinkSync(tmpFile);
428
- if (callback) callback();
429
- });
430
- });
431
- })
432
- .on("error", (err) => {
433
- console.error("Request error:", err.message);
434
- });
435
- };
490
+ )
491
+ );
436
492
 
437
- doRequest(fileUrl);
438
- }
493
+ // ✅ Now safe: no later download will overwrite your changes.
494
+ rewriteSliceWrapper(fetchConfig);
439
495
  };
496
+
440
497
  exports.sourceNodes = async (
441
498
  { actions, createNodeId, createContentDigest },
442
499
  pluginOptions
@@ -460,13 +517,15 @@ exports.sourceNodes = async (
460
517
  console.log(
461
518
  `ℹ️ [gatsby-attainlabs-cms] Fetching Trustpilot data for brand: ${brand}`
462
519
  );
463
- // // Map brand names to Trustpilot business unit IDs
520
+
464
521
  const businessIds = {
465
522
  LendDirect: "599affea0000ff0005a95acd",
466
523
  "Cash Money": "599afd420000ff0005a95a9d",
467
524
  "Heights Finance": "5e72238d600d1a0001be01eb",
468
525
  };
526
+
469
527
  const businessUnitId = businessIds[brand];
528
+
470
529
  if (!apiKey) {
471
530
  console.warn(
472
531
  "⚠️ [gatsby-attainlabs-cms] No TRUSTPILOT_API_KEY found. " +
@@ -474,7 +533,7 @@ exports.sourceNodes = async (
474
533
  "Example .env:\n" +
475
534
  "TRUSTPILOT_API_KEY=xxxxxxx\n"
476
535
  );
477
- return; // stop execution early
536
+ return;
478
537
  }
479
538
 
480
539
  if (!brand || !businessUnitId) {
@@ -483,13 +542,9 @@ exports.sourceNodes = async (
483
542
  `Current value: '${brand}'. Supported brands: ${Object.keys(businessIds).join(", ")}. ` +
484
543
  "Trustpilot data will not be fetched."
485
544
  );
486
- return; // stop execution early
545
+ return;
487
546
  }
488
547
 
489
- console.log(
490
- `ℹ️ [gatsby-attainlabs-cms] Fetching Firebase Blogs Data brand: ${brand}`
491
- );
492
-
493
548
  const fetchTrustpilotData = async (endpoint) => {
494
549
  try {
495
550
  const response = await fetch(
@@ -554,7 +609,12 @@ exports.sourceNodes = async (
554
609
 
555
610
  if (fetchConfig) {
556
611
  const firebaseData = await fetchData();
612
+
557
613
  if (fetchConfig.includes("blogs")) {
614
+ console.log(
615
+ `ℹ️ [gatsby-attainlabs-cms] Fetching Firebase Blogs Data brand: ${brand}`
616
+ );
617
+
558
618
  const allBlogs = getAllBlogs(firebaseData.pages);
559
619
 
560
620
  for (const blog of allBlogs) {
@@ -562,6 +622,7 @@ exports.sourceNodes = async (
562
622
  blog.blocks.root.props.datePublished = new Date(
563
623
  blog.blocks.root.props.datePublished
564
624
  );
625
+
565
626
  const node = {
566
627
  ...blog,
567
628
  id,
@@ -573,12 +634,17 @@ exports.sourceNodes = async (
573
634
  contentDigest: createContentDigest(blog),
574
635
  },
575
636
  };
637
+
576
638
  createNode(node);
577
639
  }
578
640
  }
579
641
 
580
642
  // Disclaimers Source Node
581
643
  if (fetchConfig.includes("disclaimers")) {
644
+ console.log(
645
+ `ℹ️ [gatsby-attainlabs-cms] Fetching Firebase Disclaimers Data brand: ${brand}`
646
+ );
647
+
582
648
  const allDisclaimers = Object.values(firebaseData.disclaimers);
583
649
  for (const disclaimer of allDisclaimers) {
584
650
  const id = createNodeId(`attain-cms-disclaimer-${disclaimer.id}`);
@@ -598,16 +664,19 @@ exports.sourceNodes = async (
598
664
  }
599
665
  }
600
666
  };
667
+
601
668
  exports.createPages = async ({ actions, store }, pluginOptions) => {
602
669
  const { brand, environment, debug } = pluginOptions;
603
670
  const { createSlice, createPage } = actions;
604
671
  const siteRoot = store.getState().program.directory;
672
+
605
673
  if (environment === "dev") {
606
674
  console.log(
607
675
  "ℹ️ [gatsby-attainlabs-cms] Running in 'dev' environment mode. Skipping page creation."
608
676
  );
609
677
  return;
610
678
  }
679
+
611
680
  // 🚨 Validate brand option
612
681
  if (!brand || !brands[brand]) {
613
682
  throw new Error(
@@ -620,6 +689,7 @@ exports.createPages = async ({ actions, store }, pluginOptions) => {
620
689
  `}`
621
690
  );
622
691
  }
692
+
623
693
  const firebaseData = await fetch(
624
694
  `https://attain-finance-cms-default-rtdb.firebaseio.com/cms/brands/${brands[brand]}/pages.json`
625
695
  );
@@ -634,44 +704,50 @@ exports.createPages = async ({ actions, store }, pluginOptions) => {
634
704
  });
635
705
 
636
706
  async function processPage(page, parentPath = "") {
637
- // Check for folderUrl first — handle nested folders immediately
707
+ // Handle folders
638
708
  if (
639
709
  page.folderUrl &&
640
710
  page.children &&
641
711
  Object.keys(page.children).length > 0
642
712
  ) {
643
- // use folderUrl instead of pageUrl for folder structure
644
713
  const folderPath = parentPath
645
714
  ? path.join(parentPath, page.folderUrl)
646
715
  : page.folderUrl;
647
- const folderFullPath = path.join(siteRoot, "src/cms/pages", folderPath);
648
716
 
717
+ const folderFullPath = path.join(siteRoot, "src/cms/pages", folderPath);
649
718
  await fse.ensureDir(folderFullPath);
650
719
 
651
720
  for (const child of Object.values(page.children)) {
652
721
  await processPage(child, folderPath);
653
722
  }
654
723
 
655
- return; // Stop here for folders — no page created for folder node
724
+ return;
656
725
  }
657
726
 
658
- // 🧩 Step 2: Process actual pages (no folderUrl)
659
- if (page.template === "visual-editor" && page.published) {
727
+ // Pages
728
+ if (
729
+ (page.published && environment === "production") ||
730
+ (environment === "staging" && page.hasOwnProperty("type"))
731
+ ) {
732
+ let blocks =
733
+ environment === "staging"
734
+ ? page.draft
735
+ ? page.draft.blocks
736
+ : page.blocks
737
+ : page.blocks;
738
+
660
739
  const {
661
- blocks: {
662
- content,
663
- root: {
664
- props: { pageUrl, layout },
665
- },
740
+ content,
741
+ root: {
742
+ props: { pageUrl, layout },
666
743
  },
667
- id,
668
- } = page;
744
+ } = blocks;
669
745
 
670
746
  // Create slice for each block
671
747
  await Promise.all(
672
748
  content.map(async (b) => {
673
749
  const name = b.props.component.name;
674
- const sliceId = `block--${pageUrl}--${name}--${id}--${crypto.randomUUID()}`;
750
+ const sliceId = `block--${pageUrl}--${name}--${page.id}--${crypto.randomUUID()}`;
675
751
 
676
752
  createSlice({
677
753
  id: sliceId,
@@ -692,7 +768,13 @@ exports.createPages = async ({ actions, store }, pluginOptions) => {
692
768
  );
693
769
 
694
770
  // Generate page file
695
- const pageSource = await generatePage(content, layout, parentPath);
771
+ const pageSource = await generatePage(
772
+ content,
773
+ layout,
774
+ parentPath,
775
+ environment,
776
+ page
777
+ );
696
778
  const folderPath = parentPath ? path.join(parentPath, pageUrl) : pageUrl;
697
779
  const outPath = path.join(siteRoot, "src/cms/pages", `${folderPath}.tsx`);
698
780
  const gatsbyPagePath = path.join(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gatsby-attainlabs-cms",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/tsconfig.json CHANGED
@@ -9,6 +9,5 @@
9
9
  "esModuleInterop": true,
10
10
  "moduleResolution": "node",
11
11
  "skipLibCheck": true
12
- },
13
- "include": ["src"]
12
+ }
14
13
  }
package/dist/index.d.ts DELETED
@@ -1,9 +0,0 @@
1
- import React from "react";
2
- import type { SliceComponentProps } from "gatsby";
3
- interface SliceWrapperProps extends SliceComponentProps {
4
- sliceContext: {
5
- componentPath: string;
6
- };
7
- }
8
- declare function SliceWrapper({ sliceContext }: SliceWrapperProps): React.DetailedReactHTMLElement<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
9
- export { SliceWrapper };
package/dist/index.js DELETED
@@ -1,8 +0,0 @@
1
- import React from "react";
2
- function SliceWrapper({ sliceContext }) {
3
- const { componentPath, ...props } = sliceContext;
4
- // Dynamically import your component
5
- const Component = require(componentPath).default;
6
- return React.createElement(Component, props);
7
- }
8
- export { SliceWrapper };
package/dist/map.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import React from "react";
2
- export declare const CMS_COMPONENTS: Record<string, React.ComponentType<any>>;
3
- export type CMSComponentName = keyof typeof CMS_COMPONENTS;
package/dist/map.js DELETED
@@ -1,9 +0,0 @@
1
- // This requires Webpack to include all .tsx files in the folder
2
- // @ts-ignore
3
- const context = require.context("./", true, /\.tsx$/);
4
- export const CMS_COMPONENTS = {};
5
- context.keys().forEach((key) => {
6
- // Remove "./" and ".tsx" from the path to get the component name
7
- const name = key.replace(/^.\//, "").replace(/\.tsx$/, "");
8
- CMS_COMPONENTS[name] = context(key).default;
9
- });
@@ -1,10 +0,0 @@
1
- interface SliceWrapperProps {
2
- sliceContext: {
3
- componentPath: string;
4
- [key: string]: any;
5
- };
6
- data: any;
7
- }
8
- export default function SliceWrapper({ sliceContext, data, }: SliceWrapperProps): import("react/jsx-runtime").JSX.Element | null;
9
- export declare const TrustPilotQuery: import("gatsby").StaticQueryDocument;
10
- export {};
@@ -1,139 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { graphql } from "gatsby";
3
- import { CMS_COMPONENTS } from "./map";
4
- export default function SliceWrapper({ sliceContext, data, }) {
5
- const { componentPath, ...props } = sliceContext;
6
- const pathFormatted = componentPath
7
- .replace("src/cms/components/", "")
8
- .replace(/^.\//, "")
9
- .replace(/\.tsx$/, "");
10
- const Component = CMS_COMPONENTS[pathFormatted];
11
- if (!Component) {
12
- console.warn(`Component "${pathFormatted}" not found in CMS_COMPONENTS`);
13
- return null;
14
- }
15
- return (_jsx(Component, { ...props, trustPilotData: data.allTrustPilotReviews.edges[0].node,
16
- /* CHECK_DISCLAIMERS_START */
17
- disclaimers: data.allDisclaimers,
18
- /* CHECK_DISCLAIMERS_END */
19
- /* CHECK_BLOGS_START */
20
- blogs: {
21
- sliderBlogs: {
22
- ...data.sliderBlogs,
23
- },
24
- allBlogs: {
25
- ...data.allBlogs,
26
- },
27
- } }));
28
- }
29
- export const TrustPilotQuery = graphql `
30
- query {
31
- allTrustPilotReviews {
32
- edges {
33
- node {
34
- recentReviews {
35
- reviews {
36
- numberOfLikes
37
- stars
38
- text
39
- title
40
- createdAt
41
- isVerified
42
- reviewVerificationLevel
43
- consumer {
44
- displayName
45
- }
46
- }
47
- }
48
- businessData {
49
- score {
50
- stars
51
- trustScore
52
- }
53
- displayName
54
- numberOfReviews {
55
- total
56
- usedForTrustScoreCalculation
57
- }
58
- }
59
- }
60
- }
61
- }
62
- # CHECK_DISCLAIMERS_START
63
- allDisclaimers: allAttainLabsCmsDisclaimers {
64
- edges {
65
- node {
66
- content
67
- published
68
- order
69
- }
70
- }
71
- }
72
- # CHECK_DISCLAIMERS_END
73
- # CHECK_BLOGS_START
74
- allBlogs: allAttainLabsCmsBlogs {
75
- edges {
76
- node {
77
- blocks {
78
- content {
79
- props {
80
- content {
81
- props {
82
- text
83
- image {
84
- desktop {
85
- url
86
- }
87
- }
88
- }
89
- # type
90
- }
91
- }
92
- }
93
- root {
94
- props {
95
- author
96
- datePublished
97
- pageUrl
98
- }
99
- }
100
- }
101
- }
102
- }
103
- }
104
- sliderBlogs: allAttainLabsCmsBlogs(
105
- limit: 9
106
- sort: { blocks: { root: { props: { datePublished: DESC } } } }
107
- ) {
108
- edges {
109
- node {
110
- blocks {
111
- content {
112
- props {
113
- content {
114
- props {
115
- text
116
- image {
117
- desktop {
118
- url
119
- }
120
- }
121
- }
122
- # type
123
- }
124
- }
125
- }
126
- root {
127
- props {
128
- author
129
- datePublished
130
- pageUrl
131
- }
132
- }
133
- }
134
- }
135
- }
136
- }
137
- # CHECK_BLOGS_END
138
- }
139
- `;
package/src/index.ts DELETED
@@ -1,18 +0,0 @@
1
- import React from "react";
2
- import type { SliceComponentProps } from "gatsby";
3
-
4
- interface SliceWrapperProps extends SliceComponentProps {
5
- sliceContext: {
6
- componentPath: string;
7
- };
8
- }
9
-
10
- function SliceWrapper({ sliceContext }: SliceWrapperProps) {
11
- const { componentPath, ...props } = sliceContext;
12
-
13
- // Dynamically import your component
14
- const Component = require(componentPath).default;
15
- return React.createElement(Component, props);
16
- }
17
-
18
- export { SliceWrapper };
package/src/map.ts DELETED
@@ -1,16 +0,0 @@
1
- // GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.
2
- import React from "react";
3
-
4
- // This requires Webpack to include all .tsx files in the folder
5
- // @ts-ignore
6
- const context = require.context("./", true, /\.tsx$/);
7
-
8
- export const CMS_COMPONENTS: Record<string, React.ComponentType<any>> = {};
9
-
10
- context.keys().forEach((key: string) => {
11
- // Remove "./" and ".tsx" from the path to get the component name
12
- const name = key.replace(/^.\//, "").replace(/\.tsx$/, "");
13
- CMS_COMPONENTS[name] = context(key).default;
14
- });
15
-
16
- export type CMSComponentName = keyof typeof CMS_COMPONENTS;
@@ -1,161 +0,0 @@
1
- // GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.
2
-
3
- import React from "react";
4
- import { graphql, type SliceComponentProps } from "gatsby";
5
- import { CMS_COMPONENTS } from "./map";
6
-
7
- interface SliceWrapperProps {
8
- sliceContext: {
9
- componentPath: string;
10
- [key: string]: any;
11
- };
12
- data: any;
13
- }
14
-
15
- export default function SliceWrapper({
16
- sliceContext,
17
- data,
18
- }: SliceWrapperProps) {
19
- const { componentPath, ...props } = sliceContext;
20
- const pathFormatted = componentPath
21
- .replace("src/cms/components/", "")
22
- .replace(/^.\//, "")
23
- .replace(/\.tsx$/, "");
24
- const Component = CMS_COMPONENTS[pathFormatted];
25
- if (!Component) {
26
- console.warn(`Component "${pathFormatted}" not found in CMS_COMPONENTS`);
27
- return null;
28
- }
29
-
30
- return (
31
- <Component
32
- {...props}
33
- trustPilotData={data.allTrustPilotReviews.edges[0].node}
34
- /* CHECK_DISCLAIMERS_START */
35
- disclaimers={data.allDisclaimers}
36
- /* CHECK_DISCLAIMERS_END */
37
- /* CHECK_BLOGS_START */
38
- blogs={{
39
- sliderBlogs: {
40
- ...data.sliderBlogs,
41
- },
42
- allBlogs: {
43
- ...data.allBlogs,
44
- },
45
- }}
46
- /* CHECK_BLOGS_END */
47
- />
48
- );
49
- }
50
-
51
- export const TrustPilotQuery = graphql`
52
- query {
53
- allTrustPilotReviews {
54
- edges {
55
- node {
56
- recentReviews {
57
- reviews {
58
- numberOfLikes
59
- stars
60
- text
61
- title
62
- createdAt
63
- isVerified
64
- reviewVerificationLevel
65
- consumer {
66
- displayName
67
- }
68
- }
69
- }
70
- businessData {
71
- score {
72
- stars
73
- trustScore
74
- }
75
- displayName
76
- numberOfReviews {
77
- total
78
- usedForTrustScoreCalculation
79
- }
80
- }
81
- }
82
- }
83
- }
84
- # CHECK_DISCLAIMERS_START
85
- allDisclaimers: allAttainLabsCmsDisclaimers {
86
- edges {
87
- node {
88
- content
89
- published
90
- order
91
- }
92
- }
93
- }
94
- # CHECK_DISCLAIMERS_END
95
- # CHECK_BLOGS_START
96
- allBlogs: allAttainLabsCmsBlogs {
97
- edges {
98
- node {
99
- blocks {
100
- content {
101
- props {
102
- content {
103
- props {
104
- text
105
- image {
106
- desktop {
107
- url
108
- }
109
- }
110
- }
111
- # type
112
- }
113
- }
114
- }
115
- root {
116
- props {
117
- author
118
- datePublished
119
- pageUrl
120
- }
121
- }
122
- }
123
- }
124
- }
125
- }
126
- sliderBlogs: allAttainLabsCmsBlogs(
127
- limit: 9
128
- sort: { blocks: { root: { props: { datePublished: DESC } } } }
129
- ) {
130
- edges {
131
- node {
132
- blocks {
133
- content {
134
- props {
135
- content {
136
- props {
137
- text
138
- image {
139
- desktop {
140
- url
141
- }
142
- }
143
- }
144
- # type
145
- }
146
- }
147
- }
148
- root {
149
- props {
150
- author
151
- datePublished
152
- pageUrl
153
- }
154
- }
155
- }
156
- }
157
- }
158
- }
159
- # CHECK_BLOGS_END
160
- }
161
- `;