gatsby-attainlabs-cms 1.0.11 → 1.0.13
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 +1 -0
- package/dist/map.d.ts +3 -0
- package/dist/map.js +9 -0
- package/dist/sliceWrapper.d.ts +8 -0
- package/dist/sliceWrapper.js +15 -0
- package/gatsby-node.js +165 -15
- package/package.json +2 -1
- package/src/map.ts +16 -0
- package/src/sliceWrapper.tsx +28 -0
- package/tsconfig.json +1 -0
package/README.md
CHANGED
package/dist/map.d.ts
ADDED
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,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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
108
|
-
|
|
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(
|
|
@@ -160,8 +240,14 @@ exports.onPreInit = async (_, pluginOptions) => {
|
|
|
160
240
|
res.pipe(fileStream);
|
|
161
241
|
fileStream.on("finish", () => {
|
|
162
242
|
fileStream.close(() => {
|
|
163
|
-
|
|
164
|
-
|
|
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.
|
|
3
|
+
"version": "1.0.13",
|
|
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
|
+
}
|