gatsby-attainlabs-cms 1.0.10 → 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 +166 -17
- 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,11 +1,64 @@
|
|
|
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)
|
|
7
|
-
|
|
8
|
-
export const onPreInit = async (_, pluginOptions) => {
|
|
61
|
+
exports.onPreInit = async (_, pluginOptions) => {
|
|
9
62
|
const { brand, azureBranch, personalAccessToken: patOption } = pluginOptions;
|
|
10
63
|
|
|
11
64
|
// Try env first, then plugin option fallback
|
|
@@ -13,7 +66,7 @@ export const onPreInit = async (_, pluginOptions) => {
|
|
|
13
66
|
|
|
14
67
|
if (!pat) {
|
|
15
68
|
console.warn(
|
|
16
|
-
"⚠️ [gatsby-
|
|
69
|
+
"⚠️ [gatsby-attainlabs-cms] No PERSONAL_ACCESS_TOKEN found. " +
|
|
17
70
|
"Set it in your project's .env file (recommended) or pass via plugin options.\n" +
|
|
18
71
|
"Example .env:\n" +
|
|
19
72
|
"PERSONAL_ACCESS_TOKEN=xxxxxxx\n"
|
|
@@ -21,20 +74,14 @@ export const onPreInit = async (_, pluginOptions) => {
|
|
|
21
74
|
return; // stop execution early
|
|
22
75
|
}
|
|
23
76
|
|
|
24
|
-
const brands = {
|
|
25
|
-
LendDirect: "lenddirect",
|
|
26
|
-
"Cash Money": "cashmoney",
|
|
27
|
-
"Heights Finance": "heightsfinance",
|
|
28
|
-
};
|
|
29
|
-
|
|
30
77
|
// 🚨 Validate brand option
|
|
31
78
|
if (!brand || !brands[brand]) {
|
|
32
79
|
throw new Error(
|
|
33
|
-
`[gatsby-
|
|
80
|
+
`[gatsby-attainlabs-cms] Invalid or missing "brand" option.\n` +
|
|
34
81
|
`You must specify one of: ${Object.keys(brands).join(", ")}\n` +
|
|
35
82
|
`Example:\n` +
|
|
36
83
|
`{\n` +
|
|
37
|
-
` resolve: "gatsby-
|
|
84
|
+
` resolve: "gatsby-attainlabs-cms",\n` +
|
|
38
85
|
` options: { brand: "Cash Money" }\n` +
|
|
39
86
|
`}`
|
|
40
87
|
);
|
|
@@ -45,6 +92,17 @@ export const onPreInit = async (_, pluginOptions) => {
|
|
|
45
92
|
const repo = "Attain Labs";
|
|
46
93
|
const branch = azureBranch || "feature/adding-puck-for-visual-code-editing";
|
|
47
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
|
+
|
|
48
106
|
// List of folders to download from
|
|
49
107
|
const targets = [
|
|
50
108
|
{
|
|
@@ -69,6 +127,18 @@ export const onPreInit = async (_, pluginOptions) => {
|
|
|
69
127
|
},
|
|
70
128
|
};
|
|
71
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
|
+
|
|
72
142
|
// Loop through targets
|
|
73
143
|
targets.forEach(({ repoPath, localBasePath }) => {
|
|
74
144
|
const listUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
|
|
@@ -105,9 +175,18 @@ export const onPreInit = async (_, pluginOptions) => {
|
|
|
105
175
|
);
|
|
106
176
|
|
|
107
177
|
console.log(`Found ${items.length} files in ${repoPath}`);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
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
|
+
});
|
|
111
190
|
});
|
|
112
191
|
})
|
|
113
192
|
.on("error", (err) => {
|
|
@@ -116,7 +195,7 @@ export const onPreInit = async (_, pluginOptions) => {
|
|
|
116
195
|
});
|
|
117
196
|
|
|
118
197
|
// Download a single file preserving folder structure under localBasePath
|
|
119
|
-
function downloadFile(filePath, repoPath, localBasePath) {
|
|
198
|
+
function downloadFile(filePath, repoPath, localBasePath, callback) {
|
|
120
199
|
const fileUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
|
|
121
200
|
repo
|
|
122
201
|
)}/items?path=${encodeURIComponent(
|
|
@@ -161,8 +240,14 @@ export const onPreInit = async (_, pluginOptions) => {
|
|
|
161
240
|
res.pipe(fileStream);
|
|
162
241
|
fileStream.on("finish", () => {
|
|
163
242
|
fileStream.close(() => {
|
|
164
|
-
|
|
165
|
-
|
|
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();
|
|
166
251
|
});
|
|
167
252
|
});
|
|
168
253
|
})
|
|
@@ -174,3 +259,67 @@ export const onPreInit = async (_, pluginOptions) => {
|
|
|
174
259
|
doRequest(fileUrl);
|
|
175
260
|
}
|
|
176
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
|
+
}
|