gatsby-attainlabs-cms 1.0.11 → 1.0.14

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 CHANGED
@@ -81,6 +81,7 @@ module.exports = {
81
81
  - `LendDirect`
82
82
  - `Cash Money`
83
83
  - `Heights Finance`
84
+ - `Attain Finance`
84
85
 
85
86
  ---
86
87
 
package/dist/map.d.ts ADDED
@@ -0,0 +1,3 @@
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 ADDED
@@ -0,0 +1,9 @@
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
+ });
@@ -0,0 +1,8 @@
1
+ interface SliceWrapperProps {
2
+ sliceContext: {
3
+ componentPath: string;
4
+ [key: string]: any;
5
+ };
6
+ }
7
+ export default function SliceWrapper({ sliceContext }: SliceWrapperProps): import("react/jsx-runtime").JSX.Element | null;
8
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { CMS_COMPONENTS } from "./map";
3
+ export default function SliceWrapper({ sliceContext }) {
4
+ const { componentPath, ...props } = sliceContext;
5
+ const pathFormatted = componentPath
6
+ .replace("src/cms/components//", "")
7
+ .replace(/^.\//, "")
8
+ .replace(/\.tsx$/, "");
9
+ const Component = CMS_COMPONENTS[pathFormatted];
10
+ if (!Component) {
11
+ console.warn(`Component "${pathFormatted}" not found in CMS_COMPONENTS`);
12
+ return null;
13
+ }
14
+ return _jsx(Component, { ...props });
15
+ }
package/gatsby-node.js CHANGED
@@ -1,6 +1,60 @@
1
1
  const fs = require("fs");
2
2
  const https = require("https");
3
3
  const path = require("path");
4
+ const prettier = require("prettier");
5
+ const fse = require("fs-extra");
6
+
7
+ const brands = {
8
+ LendDirect: "lenddirect",
9
+ "Cash Money": "cashmoney",
10
+ "Heights Finance": "heightsfinance",
11
+ "Attain Finance": "attainfinance",
12
+ };
13
+
14
+ const createPage = async (blocks, slug, id) => {
15
+ // Validate input parameters
16
+ if (!Array.isArray(blocks) || !slug || !id) {
17
+ throw new Error("Invalid parameters passed to createPage.");
18
+ }
19
+
20
+ // Helper function to generate Slice components
21
+ const generateSlices = (blocks, slug, id) => {
22
+ return blocks
23
+ .map(
24
+ (b) =>
25
+ `<Slice alias="block--${slug}--${b.props.component.name}--${id}" />`
26
+ )
27
+ .join("\n");
28
+ };
29
+
30
+ // Generate the page content
31
+ const pageContent = `
32
+ /* This is a generated file by the gatsby-attainlabs-cms plugin. Any changes will be overwritten on the next build. */
33
+ import { Slice } from "gatsby";
34
+ import { SEO } from "../components/SEO";
35
+ import { DefaultLayout as Layout } from "../layouts/Default/";
36
+
37
+ const metaTitle = "{page.root.props.title}";
38
+ const metaDescription = "{page.root.props.description}";
39
+
40
+ export const Head = () => (
41
+ <SEO title={metaTitle} description={metaDescription} noIndex={true} />
42
+ );
43
+
44
+ const Page = () => {
45
+ return (
46
+ <Layout>
47
+ ${generateSlices(blocks, slug, id)}
48
+ </Layout>
49
+ );
50
+ };
51
+
52
+ export default Page;
53
+ `;
54
+
55
+ // Format the generated code using Prettier (must await!)
56
+ return await prettier.format(pageContent, { parser: "babel-ts" });
57
+ };
4
58
 
5
59
  require("dotenv").config();
6
60
  // Load PAT from env instead of hardcoding (fallback to old const for now but warn)
@@ -12,7 +66,7 @@ exports.onPreInit = async (_, pluginOptions) => {
12
66
 
13
67
  if (!pat) {
14
68
  console.warn(
15
- "⚠️ [gatsby-plugin-your-plugin] No PERSONAL_ACCESS_TOKEN found. " +
69
+ "⚠️ [gatsby-attainlabs-cms] No PERSONAL_ACCESS_TOKEN found. " +
16
70
  "Set it in your project's .env file (recommended) or pass via plugin options.\n" +
17
71
  "Example .env:\n" +
18
72
  "PERSONAL_ACCESS_TOKEN=xxxxxxx\n"
@@ -20,20 +74,14 @@ exports.onPreInit = async (_, pluginOptions) => {
20
74
  return; // stop execution early
21
75
  }
22
76
 
23
- const brands = {
24
- LendDirect: "lenddirect",
25
- "Cash Money": "cashmoney",
26
- "Heights Finance": "heightsfinance",
27
- };
28
-
29
77
  // 🚨 Validate brand option
30
78
  if (!brand || !brands[brand]) {
31
79
  throw new Error(
32
- `[gatsby-plugin-your-plugin] Invalid or missing "brand" option.\n` +
80
+ `[gatsby-attainlabs-cms] Invalid or missing "brand" option.\n` +
33
81
  `You must specify one of: ${Object.keys(brands).join(", ")}\n` +
34
82
  `Example:\n` +
35
83
  `{\n` +
36
- ` resolve: "gatsby-plugin-your-plugin",\n` +
84
+ ` resolve: "gatsby-attainlabs-cms",\n` +
37
85
  ` options: { brand: "Cash Money" }\n` +
38
86
  `}`
39
87
  );
@@ -44,6 +92,17 @@ exports.onPreInit = async (_, pluginOptions) => {
44
92
  const repo = "Attain Labs";
45
93
  const branch = azureBranch || "feature/adding-puck-for-visual-code-editing";
46
94
 
95
+ const localTargets = [
96
+ {
97
+ localPath: path.resolve(__dirname, "src/sliceWrapper.tsx"),
98
+ targetPath: path.resolve("./src/cms/components/"),
99
+ },
100
+ {
101
+ localPath: path.resolve(__dirname, "src/map.ts"),
102
+ targetPath: path.resolve("./src/cms/components/"),
103
+ },
104
+ ];
105
+
47
106
  // List of folders to download from
48
107
  const targets = [
49
108
  {
@@ -68,6 +127,18 @@ exports.onPreInit = async (_, pluginOptions) => {
68
127
  },
69
128
  };
70
129
 
130
+ // Copy local files first
131
+ localTargets.forEach(({ localPath, targetPath }) => {
132
+ if (!fs.existsSync(targetPath)) {
133
+ fs.mkdirSync(targetPath, { recursive: true });
134
+ // console.log(`📂 Created directory: ${targetPath}`);
135
+ }
136
+ const fileName = path.basename(localPath);
137
+ const destFile = path.join(targetPath, fileName);
138
+ fs.copyFileSync(localPath, destFile);
139
+ console.log(`✅ Copied ${destFile}`);
140
+ });
141
+
71
142
  // Loop through targets
72
143
  targets.forEach(({ repoPath, localBasePath }) => {
73
144
  const listUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
@@ -104,9 +175,18 @@ exports.onPreInit = async (_, pluginOptions) => {
104
175
  );
105
176
 
106
177
  console.log(`Found ${items.length} files in ${repoPath}`);
107
- items.forEach((item) =>
108
- downloadFile(item.path, repoPath, localBasePath)
109
- );
178
+ let completedDownloads = 0;
179
+
180
+ items.forEach((item) => {
181
+ downloadFile(item.path, repoPath, localBasePath, () => {
182
+ completedDownloads++;
183
+ if (completedDownloads === items.length) {
184
+ console.log(
185
+ `✅ Completed downloading all files for target: ${repoPath}`
186
+ );
187
+ }
188
+ });
189
+ });
110
190
  });
111
191
  })
112
192
  .on("error", (err) => {
@@ -115,7 +195,7 @@ exports.onPreInit = async (_, pluginOptions) => {
115
195
  });
116
196
 
117
197
  // Download a single file preserving folder structure under localBasePath
118
- function downloadFile(filePath, repoPath, localBasePath) {
198
+ function downloadFile(filePath, repoPath, localBasePath, callback) {
119
199
  const fileUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
120
200
  repo
121
201
  )}/items?path=${encodeURIComponent(
@@ -131,7 +211,7 @@ exports.onPreInit = async (_, pluginOptions) => {
131
211
 
132
212
  if (!fs.existsSync(destDir)) {
133
213
  fs.mkdirSync(destDir, { recursive: true });
134
- console.log(`📂 Created directory: ${destDir}`);
214
+ // console.log(`📂 Created directory: ${destDir}`);
135
215
  }
136
216
 
137
217
  const doRequest = (url) => {
@@ -160,8 +240,14 @@ exports.onPreInit = async (_, pluginOptions) => {
160
240
  res.pipe(fileStream);
161
241
  fileStream.on("finish", () => {
162
242
  fileStream.close(() => {
163
- fs.renameSync(tmpFile, destFile);
164
- console.log(`✅ Downloaded ${destFile}`);
243
+ const generatedComment =
244
+ "// GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.\n";
245
+ const fileContent = fs.readFileSync(tmpFile, "utf8");
246
+ fs.writeFileSync(destFile, generatedComment + fileContent);
247
+
248
+ // Remove the temporary file
249
+ fs.unlinkSync(tmpFile);
250
+ if (callback) callback();
165
251
  });
166
252
  });
167
253
  })
@@ -173,3 +259,67 @@ exports.onPreInit = async (_, pluginOptions) => {
173
259
  doRequest(fileUrl);
174
260
  }
175
261
  };
262
+
263
+ exports.createPages = async ({ actions, store }, pluginOptions) => {
264
+ const { brand } = pluginOptions;
265
+ const { createSlice } = actions;
266
+ const siteRoot = store.getState().program.directory;
267
+ // 🚨 Validate brand option
268
+ if (!brand || !brands[brand]) {
269
+ throw new Error(
270
+ `[gatsby-attainlabs-cms] Invalid or missing "brand" option.\n` +
271
+ `You must specify one of: ${Object.keys(brands).join(", ")}\n` +
272
+ `Example:\n` +
273
+ `{\n` +
274
+ ` resolve: "gatsby-attainlabs-cms",\n` +
275
+ ` options: { brand: "Cash Money" }\n` +
276
+ `}`
277
+ );
278
+ }
279
+ const firebaseData = await fetch(
280
+ `https://attain-finance-cms-default-rtdb.firebaseio.com/cms/brands/${brands[brand]}/pages.json`
281
+ );
282
+ const firebaseJson = await firebaseData.json();
283
+ for (const page of Object.values(firebaseJson)) {
284
+ if (page.template === "visual-editor") {
285
+ const {
286
+ draft: {
287
+ blocks: {
288
+ content,
289
+ root: {
290
+ props: { slug },
291
+ },
292
+ },
293
+ },
294
+ id,
295
+ } = page;
296
+
297
+ //Generate page's blocks slices
298
+ await Promise.all(
299
+ content.map(async (b) => {
300
+ const name = b.props.component.name;
301
+ const blockId = `block--${slug}--${name}--${id}`;
302
+ console.log(`Creating slice: ${blockId}`);
303
+ // here we could await something per slice if needed later
304
+ createSlice({
305
+ id: blockId,
306
+ component: path.resolve(
307
+ siteRoot,
308
+ "src/cms/components/sliceWrapper.tsx"
309
+ ),
310
+ context: {
311
+ componentPath: `./src/cms/components/${b.props.component.path}/index.tsx`,
312
+ ...b.props,
313
+ },
314
+ });
315
+ })
316
+ );
317
+
318
+ const pageSource = await createPage(content, slug, id);
319
+ const outPath = path.join(siteRoot, "src/pages", `${slug}.tsx`);
320
+ // Generate the page itself
321
+ await fse.outputFile(outPath, pageSource);
322
+ console.log(`✅ Wrote page to ${outPath}`);
323
+ }
324
+ }
325
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gatsby-attainlabs-cms",
3
- "version": "1.0.11",
3
+ "version": "1.0.14",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,6 +16,7 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "dotenv": "^17.2.1",
19
+ "prettier": "^3.6.2",
19
20
  "react-slick": "^0.31.0"
20
21
  },
21
22
  "devDependencies": {
package/src/map.ts ADDED
@@ -0,0 +1,16 @@
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;
@@ -0,0 +1,28 @@
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 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
+ }
13
+
14
+ export default function SliceWrapper({ sliceContext }: SliceWrapperProps) {
15
+ const { componentPath, ...props } = sliceContext;
16
+
17
+ const pathFormatted = componentPath
18
+ .replace("src/cms/components//", "")
19
+ .replace(/^.\//, "")
20
+ .replace(/\.tsx$/, "");
21
+ const Component = CMS_COMPONENTS[pathFormatted];
22
+ if (!Component) {
23
+ console.warn(`Component "${pathFormatted}" not found in CMS_COMPONENTS`);
24
+ return null;
25
+ }
26
+
27
+ return <Component {...props} />;
28
+ }
package/tsconfig.json CHANGED
@@ -5,6 +5,7 @@
5
5
  "declaration": true,
6
6
  "outDir": "./dist",
7
7
  "strict": true,
8
+ "jsx": "react-jsx",
8
9
  "esModuleInterop": true,
9
10
  "moduleResolution": "node",
10
11
  "skipLibCheck": true