@fydemy/cms 1.0.1 → 1.0.3

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/dist/index.js CHANGED
@@ -104,8 +104,10 @@ var init_rate_limit = __esm({
104
104
  // src/index.ts
105
105
  var index_exports = {};
106
106
  __export(index_exports, {
107
+ AdminDashboard: () => AdminDashboard,
107
108
  GitHubStorage: () => GitHubStorage,
108
109
  LocalStorage: () => LocalStorage,
110
+ Login: () => Login,
109
111
  MAX_FILE_SIZE: () => MAX_FILE_SIZE,
110
112
  MAX_PASSWORD_LENGTH: () => MAX_PASSWORD_LENGTH,
111
113
  MAX_USERNAME_LENGTH: () => MAX_USERNAME_LENGTH,
@@ -816,6 +818,1060 @@ async function handleUpload(request) {
816
818
  }
817
819
  }
818
820
 
821
+ // src/ui/AdminDashboard.tsx
822
+ var import_react = require("react");
823
+ var import_jsx_runtime = require("react/jsx-runtime");
824
+ function AdminDashboard() {
825
+ const [entries, setEntries] = (0, import_react.useState)([]);
826
+ const [currentPath, setCurrentPath] = (0, import_react.useState)("");
827
+ const [selectedFile, setSelectedFile] = (0, import_react.useState)(null);
828
+ const [fields, setFields] = (0, import_react.useState)({});
829
+ const [loading, setLoading] = (0, import_react.useState)(false);
830
+ const [message, setMessage] = (0, import_react.useState)("");
831
+ const [newFileName, setNewFileName] = (0, import_react.useState)("");
832
+ const [uploading, setUploading] = (0, import_react.useState)(false);
833
+ (0, import_react.useEffect)(() => {
834
+ loadDirectory(currentPath);
835
+ }, [currentPath]);
836
+ const loadDirectory = async (path4) => {
837
+ try {
838
+ const url = path4 ? `/api/cms/list/${path4}` : "/api/cms/list";
839
+ const response = await fetch(url);
840
+ const data = await response.json();
841
+ setEntries(data.entries || []);
842
+ } catch (error) {
843
+ console.error("Failed to load directory:", error);
844
+ }
845
+ };
846
+ const loadFile = async (filePath) => {
847
+ try {
848
+ const response = await fetch(`/api/cms/content/${filePath}`);
849
+ const data = await response.json();
850
+ setSelectedFile(filePath);
851
+ const loadedFields = {};
852
+ if (data.content !== void 0) {
853
+ loadedFields["content"] = { type: "markdown", value: data.content };
854
+ }
855
+ Object.entries(data.data || {}).forEach(([key, value]) => {
856
+ loadedFields[key] = {
857
+ type: detectFieldType(value),
858
+ value
859
+ };
860
+ });
861
+ setFields(loadedFields);
862
+ } catch (error) {
863
+ setMessage("Failed to load file");
864
+ }
865
+ };
866
+ const detectFieldType = (value) => {
867
+ if (typeof value === "number") return "number";
868
+ if (typeof value === "string") {
869
+ if (value.match(/^\d{4}-\d{2}-\d{2}/)) return "date";
870
+ if (value.startsWith("/uploads/") || value.startsWith("http"))
871
+ return "image";
872
+ if (value.length > 100) return "markdown";
873
+ }
874
+ return "text";
875
+ };
876
+ const saveFile = async () => {
877
+ if (!selectedFile) return;
878
+ setLoading(true);
879
+ setMessage("");
880
+ try {
881
+ const frontmatter = {};
882
+ let content = "";
883
+ Object.entries(fields).forEach(([key, field]) => {
884
+ if (key === "content") {
885
+ content = field.value;
886
+ } else {
887
+ frontmatter[key] = field.value;
888
+ }
889
+ });
890
+ const response = await fetch(`/api/cms/content/${selectedFile}`, {
891
+ method: "POST",
892
+ headers: { "Content-Type": "application/json" },
893
+ body: JSON.stringify({ data: frontmatter, content })
894
+ });
895
+ if (response.ok) {
896
+ setMessage("\u2705 File saved successfully!");
897
+ setTimeout(() => setMessage(""), 3e3);
898
+ } else {
899
+ const errorData = await response.json().catch(() => ({}));
900
+ setMessage(
901
+ `\u274C Failed to save file: ${errorData.error || response.statusText}`
902
+ );
903
+ }
904
+ } catch (error) {
905
+ setMessage(
906
+ `\u274C Error saving file: ${error instanceof Error ? error.message : "Unknown error"}`
907
+ );
908
+ } finally {
909
+ setLoading(false);
910
+ }
911
+ };
912
+ const createFile = async () => {
913
+ if (!newFileName) return;
914
+ const fileName = newFileName.endsWith(".md") ? newFileName : `${newFileName}.md`;
915
+ const fullPath = currentPath ? `${currentPath}/${fileName}` : fileName;
916
+ setLoading(true);
917
+ setMessage("");
918
+ try {
919
+ const response = await fetch(`/api/cms/content/${fullPath}`, {
920
+ method: "POST",
921
+ headers: { "Content-Type": "application/json" },
922
+ body: JSON.stringify({
923
+ data: {},
924
+ content: ""
925
+ })
926
+ });
927
+ if (response.ok) {
928
+ setMessage("\u2705 File created!");
929
+ setNewFileName("");
930
+ loadDirectory(currentPath);
931
+ loadFile(fullPath);
932
+ } else {
933
+ setMessage("\u274C Failed to create file");
934
+ }
935
+ } catch (error) {
936
+ setMessage("\u274C Error creating file");
937
+ } finally {
938
+ setLoading(false);
939
+ }
940
+ };
941
+ const duplicateFile = async () => {
942
+ if (!selectedFile) return;
943
+ const newName = prompt("Enter new filename (without .md extension):");
944
+ if (!newName) return;
945
+ const duplicateName = currentPath ? `${currentPath}/${newName}.md` : `${newName}.md`;
946
+ setLoading(true);
947
+ setMessage("");
948
+ try {
949
+ const frontmatter = {};
950
+ let content = "";
951
+ Object.entries(fields).forEach(([key, field]) => {
952
+ if (key === "content") {
953
+ content = field.value;
954
+ } else {
955
+ frontmatter[key] = field.value;
956
+ }
957
+ });
958
+ const response = await fetch(`/api/cms/content/${duplicateName}`, {
959
+ method: "POST",
960
+ headers: { "Content-Type": "application/json" },
961
+ body: JSON.stringify({ data: frontmatter, content })
962
+ });
963
+ if (response.ok) {
964
+ setMessage("\u2705 File duplicated!");
965
+ loadDirectory(currentPath);
966
+ loadFile(duplicateName);
967
+ } else {
968
+ const errorData = await response.json().catch(() => ({}));
969
+ setMessage(
970
+ `\u274C Failed to duplicate file: ${errorData.error || "Unknown error"}`
971
+ );
972
+ }
973
+ } catch (error) {
974
+ setMessage("\u274C Error duplicating file");
975
+ } finally {
976
+ setLoading(false);
977
+ }
978
+ };
979
+ const deleteFile = async (filePath) => {
980
+ if (!confirm(`Delete ${filePath}?`)) return;
981
+ setLoading(true);
982
+ setMessage("");
983
+ try {
984
+ const response = await fetch(`/api/cms/content/${filePath}`, {
985
+ method: "DELETE"
986
+ });
987
+ if (response.ok) {
988
+ setMessage("\u2705 File deleted!");
989
+ if (selectedFile === filePath) {
990
+ setSelectedFile(null);
991
+ setFields({});
992
+ }
993
+ loadDirectory(currentPath);
994
+ } else {
995
+ setMessage("\u274C Failed to delete file");
996
+ }
997
+ } catch (error) {
998
+ setMessage("\u274C Error deleting file");
999
+ } finally {
1000
+ setLoading(false);
1001
+ }
1002
+ };
1003
+ const navigateToDir = (dirPath) => {
1004
+ setCurrentPath(dirPath);
1005
+ setSelectedFile(null);
1006
+ };
1007
+ const goUp = () => {
1008
+ const parts = currentPath.split("/");
1009
+ parts.pop();
1010
+ setCurrentPath(parts.join("/"));
1011
+ setSelectedFile(null);
1012
+ };
1013
+ const handleLogout2 = async () => {
1014
+ await fetch("/api/cms/logout", { method: "POST" });
1015
+ window.location.href = "/admin/login";
1016
+ };
1017
+ const updateField = (key, value) => {
1018
+ setFields((prev) => ({
1019
+ ...prev,
1020
+ [key]: { ...prev[key], value }
1021
+ }));
1022
+ };
1023
+ const addField = () => {
1024
+ const fieldName = prompt("Enter field name:");
1025
+ if (!fieldName || fields[fieldName]) return;
1026
+ const fieldType = prompt(
1027
+ "Enter field type (text/date/number/markdown/image):",
1028
+ "text"
1029
+ );
1030
+ const validTypes = [
1031
+ "text",
1032
+ "date",
1033
+ "number",
1034
+ "markdown",
1035
+ "image"
1036
+ ];
1037
+ if (!validTypes.includes(fieldType)) {
1038
+ alert("Invalid field type! Use: text, date, number, markdown, or image");
1039
+ return;
1040
+ }
1041
+ const defaultValue = fieldType === "number" ? 0 : fieldType === "date" ? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] : "";
1042
+ setFields((prev) => ({
1043
+ ...prev,
1044
+ [fieldName]: { type: fieldType, value: defaultValue }
1045
+ }));
1046
+ };
1047
+ const removeField = (key) => {
1048
+ const newFields = { ...fields };
1049
+ delete newFields[key];
1050
+ setFields(newFields);
1051
+ };
1052
+ const handleImageUpload = async (key, event) => {
1053
+ const file = event.target.files?.[0];
1054
+ if (!file) return;
1055
+ setUploading(true);
1056
+ try {
1057
+ const formData = new FormData();
1058
+ formData.append("file", file);
1059
+ const response = await fetch("/api/cms/upload", {
1060
+ method: "POST",
1061
+ body: formData
1062
+ });
1063
+ const data = await response.json();
1064
+ if (response.ok) {
1065
+ updateField(key, data.url);
1066
+ setMessage(`\u2705 Image uploaded!`);
1067
+ setTimeout(() => setMessage(""), 3e3);
1068
+ } else {
1069
+ setMessage("\u274C Failed to upload image");
1070
+ }
1071
+ } catch (error) {
1072
+ setMessage("\u274C Error uploading image");
1073
+ } finally {
1074
+ setUploading(false);
1075
+ event.target.value = "";
1076
+ }
1077
+ };
1078
+ const handleFileUpload = async (event) => {
1079
+ const file = event.target.files?.[0];
1080
+ if (!file) return;
1081
+ setUploading(true);
1082
+ setMessage("");
1083
+ try {
1084
+ const formData = new FormData();
1085
+ formData.append("file", file);
1086
+ const response = await fetch("/api/cms/upload", {
1087
+ method: "POST",
1088
+ body: formData
1089
+ });
1090
+ const data = await response.json();
1091
+ if (response.ok) {
1092
+ setMessage(`\u2705 File uploaded! Path: ${data.url}`);
1093
+ setTimeout(() => setMessage(""), 5e3);
1094
+ } else {
1095
+ setMessage("\u274C Failed to upload file");
1096
+ }
1097
+ } catch (error) {
1098
+ setMessage("\u274C Error uploading file");
1099
+ } finally {
1100
+ setUploading(false);
1101
+ event.target.value = "";
1102
+ }
1103
+ };
1104
+ const renderField = (key, field) => {
1105
+ switch (field.type) {
1106
+ case "text":
1107
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1108
+ "input",
1109
+ {
1110
+ id: key,
1111
+ type: "text",
1112
+ value: field.value,
1113
+ onChange: (e) => updateField(key, e.target.value),
1114
+ style: {
1115
+ width: "100%",
1116
+ padding: "0.5rem",
1117
+ border: "1px solid #ddd",
1118
+ borderRadius: "4px"
1119
+ }
1120
+ }
1121
+ );
1122
+ case "number":
1123
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1124
+ "input",
1125
+ {
1126
+ id: key,
1127
+ type: "number",
1128
+ value: field.value,
1129
+ onChange: (e) => updateField(key, parseFloat(e.target.value) || 0),
1130
+ style: {
1131
+ width: "100%",
1132
+ padding: "0.5rem",
1133
+ border: "1px solid #ddd",
1134
+ borderRadius: "4px"
1135
+ }
1136
+ }
1137
+ );
1138
+ case "date":
1139
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1140
+ "input",
1141
+ {
1142
+ id: key,
1143
+ type: "date",
1144
+ value: field.value,
1145
+ onChange: (e) => updateField(key, e.target.value),
1146
+ style: {
1147
+ width: "100%",
1148
+ padding: "0.5rem",
1149
+ border: "1px solid #ddd",
1150
+ borderRadius: "4px"
1151
+ }
1152
+ }
1153
+ );
1154
+ case "markdown":
1155
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1156
+ "textarea",
1157
+ {
1158
+ id: key,
1159
+ value: field.value,
1160
+ onChange: (e) => updateField(key, e.target.value),
1161
+ style: {
1162
+ width: "100%",
1163
+ minHeight: key === "content" ? "400px" : "150px",
1164
+ fontFamily: "monospace",
1165
+ padding: "0.5rem",
1166
+ border: "1px solid #ddd",
1167
+ borderRadius: "4px"
1168
+ },
1169
+ placeholder: key === "content" ? "Write your markdown content here..." : "Markdown text..."
1170
+ }
1171
+ );
1172
+ case "image":
1173
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1175
+ "input",
1176
+ {
1177
+ id: key,
1178
+ type: "text",
1179
+ value: field.value,
1180
+ onChange: (e) => updateField(key, e.target.value),
1181
+ placeholder: "/uploads/image.jpg or https://...",
1182
+ style: {
1183
+ width: "100%",
1184
+ padding: "0.5rem",
1185
+ marginBottom: "0.5rem",
1186
+ border: "1px solid #ddd",
1187
+ borderRadius: "4px"
1188
+ }
1189
+ }
1190
+ ),
1191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1192
+ "input",
1193
+ {
1194
+ type: "file",
1195
+ accept: "image/*",
1196
+ onChange: (e) => handleImageUpload(key, e),
1197
+ disabled: uploading,
1198
+ style: { marginBottom: "0.5rem" }
1199
+ }
1200
+ ),
1201
+ field.value && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1202
+ "img",
1203
+ {
1204
+ src: field.value,
1205
+ alt: key,
1206
+ style: {
1207
+ maxWidth: "200px",
1208
+ display: "block",
1209
+ marginTop: "0.5rem",
1210
+ borderRadius: "4px",
1211
+ border: "1px solid #ddd"
1212
+ }
1213
+ }
1214
+ )
1215
+ ] });
1216
+ default:
1217
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1218
+ "input",
1219
+ {
1220
+ id: key,
1221
+ type: "text",
1222
+ value: field.value,
1223
+ onChange: (e) => updateField(key, e.target.value)
1224
+ }
1225
+ );
1226
+ }
1227
+ };
1228
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1229
+ "div",
1230
+ {
1231
+ className: "container",
1232
+ style: { maxWidth: "1200px", margin: "0 auto", padding: "2rem" },
1233
+ children: [
1234
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1235
+ "div",
1236
+ {
1237
+ style: {
1238
+ display: "flex",
1239
+ justifyContent: "space-between",
1240
+ alignItems: "center",
1241
+ marginBottom: "2rem"
1242
+ },
1243
+ children: [
1244
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: "\u{1F4DD} Admin Dashboard" }),
1245
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1246
+ "button",
1247
+ {
1248
+ onClick: handleLogout2,
1249
+ style: {
1250
+ padding: "0.5rem 1rem",
1251
+ background: "#f5f5f5",
1252
+ border: "1px solid #ddd",
1253
+ borderRadius: "4px",
1254
+ cursor: "pointer"
1255
+ },
1256
+ children: "Logout"
1257
+ }
1258
+ )
1259
+ ]
1260
+ }
1261
+ ),
1262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1263
+ "div",
1264
+ {
1265
+ style: {
1266
+ display: "grid",
1267
+ gridTemplateColumns: "minmax(250px, 300px) 1fr",
1268
+ gap: "2rem"
1269
+ },
1270
+ children: [
1271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1272
+ "div",
1273
+ {
1274
+ style: {
1275
+ background: "white",
1276
+ padding: "1.5rem",
1277
+ borderRadius: "8px",
1278
+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)"
1279
+ },
1280
+ children: [
1281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: { marginTop: 0, marginBottom: "1rem" }, children: "Files" }),
1282
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1283
+ "div",
1284
+ {
1285
+ style: {
1286
+ marginBottom: "1rem",
1287
+ fontSize: "0.875rem",
1288
+ color: "#666"
1289
+ },
1290
+ children: [
1291
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1292
+ "span",
1293
+ {
1294
+ onClick: () => setCurrentPath(""),
1295
+ style: {
1296
+ cursor: "pointer",
1297
+ color: "#0070f3",
1298
+ textDecoration: "underline"
1299
+ },
1300
+ children: "content"
1301
+ }
1302
+ ),
1303
+ currentPath && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1304
+ " / ",
1305
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: "bold" }, children: currentPath })
1306
+ ] })
1307
+ ]
1308
+ }
1309
+ ),
1310
+ currentPath && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1311
+ "button",
1312
+ {
1313
+ onClick: goUp,
1314
+ style: {
1315
+ width: "100%",
1316
+ marginBottom: "1rem",
1317
+ padding: "0.5rem",
1318
+ background: "#f5f5f5",
1319
+ border: "1px solid #ddd",
1320
+ borderRadius: "4px",
1321
+ cursor: "pointer"
1322
+ },
1323
+ children: "\u2B06\uFE0F Go Up"
1324
+ }
1325
+ ),
1326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1327
+ "div",
1328
+ {
1329
+ style: {
1330
+ marginBottom: "1.5rem",
1331
+ paddingBottom: "1.5rem",
1332
+ borderBottom: "1px solid #eee"
1333
+ },
1334
+ children: [
1335
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1336
+ "input",
1337
+ {
1338
+ type: "text",
1339
+ placeholder: "new-file.md",
1340
+ value: newFileName,
1341
+ onChange: (e) => setNewFileName(e.target.value),
1342
+ style: {
1343
+ width: "100%",
1344
+ padding: "0.5rem",
1345
+ marginBottom: "0.5rem",
1346
+ border: "1px solid #ddd",
1347
+ borderRadius: "4px"
1348
+ }
1349
+ }
1350
+ ),
1351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1352
+ "button",
1353
+ {
1354
+ onClick: createFile,
1355
+ disabled: loading || !newFileName,
1356
+ style: {
1357
+ width: "100%",
1358
+ padding: "0.5rem",
1359
+ background: "#0070f3",
1360
+ color: "white",
1361
+ border: "none",
1362
+ borderRadius: "4px",
1363
+ cursor: "pointer",
1364
+ opacity: loading ? 0.7 : 1
1365
+ },
1366
+ children: "+ New File"
1367
+ }
1368
+ )
1369
+ ]
1370
+ }
1371
+ ),
1372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: [
1373
+ entries.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { style: { color: "#999", fontStyle: "italic" }, children: "Empty directory" }),
1374
+ entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1375
+ "li",
1376
+ {
1377
+ style: {
1378
+ marginBottom: "0.5rem",
1379
+ display: "flex",
1380
+ flexDirection: "column"
1381
+ },
1382
+ children: entry.type === "directory" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1383
+ "button",
1384
+ {
1385
+ onClick: () => navigateToDir(entry.path),
1386
+ style: {
1387
+ background: "none",
1388
+ border: "none",
1389
+ color: "#0070f3",
1390
+ cursor: "pointer",
1391
+ textAlign: "left",
1392
+ fontSize: "1rem",
1393
+ padding: "0.25rem 0",
1394
+ fontWeight: "bold"
1395
+ },
1396
+ children: [
1397
+ "\u{1F4C1} ",
1398
+ entry.name
1399
+ ]
1400
+ }
1401
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1402
+ "div",
1403
+ {
1404
+ style: {
1405
+ display: "flex",
1406
+ justifyContent: "space-between",
1407
+ alignItems: "center"
1408
+ },
1409
+ children: [
1410
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1411
+ "button",
1412
+ {
1413
+ onClick: () => loadFile(entry.path),
1414
+ style: {
1415
+ background: "none",
1416
+ border: "none",
1417
+ color: selectedFile === entry.path ? "#000" : "#444",
1418
+ cursor: "pointer",
1419
+ textAlign: "left",
1420
+ padding: "0.25rem 0",
1421
+ fontWeight: selectedFile === entry.path ? "bold" : "normal",
1422
+ textDecoration: selectedFile === entry.path ? "underline" : "none",
1423
+ flex: 1
1424
+ },
1425
+ children: [
1426
+ "\u{1F4C4} ",
1427
+ entry.name
1428
+ ]
1429
+ }
1430
+ ),
1431
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1432
+ "button",
1433
+ {
1434
+ onClick: () => deleteFile(entry.path),
1435
+ disabled: loading,
1436
+ style: {
1437
+ background: "none",
1438
+ border: "none",
1439
+ color: "#d32f2f",
1440
+ cursor: "pointer",
1441
+ fontSize: "0.8rem",
1442
+ opacity: 0.7
1443
+ },
1444
+ children: "\u2715"
1445
+ }
1446
+ )
1447
+ ]
1448
+ }
1449
+ )
1450
+ },
1451
+ entry.path
1452
+ ))
1453
+ ] })
1454
+ ]
1455
+ }
1456
+ ),
1457
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1458
+ "div",
1459
+ {
1460
+ style: {
1461
+ background: "white",
1462
+ padding: "2rem",
1463
+ borderRadius: "8px",
1464
+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)"
1465
+ },
1466
+ children: selectedFile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1467
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1468
+ "div",
1469
+ {
1470
+ style: {
1471
+ display: "flex",
1472
+ justifyContent: "space-between",
1473
+ alignItems: "center",
1474
+ marginBottom: "2rem",
1475
+ paddingBottom: "1rem",
1476
+ borderBottom: "1px solid #eee"
1477
+ },
1478
+ children: [
1479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", { style: { margin: 0 }, children: [
1480
+ "Edit: ",
1481
+ selectedFile
1482
+ ] }),
1483
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "0.5rem" }, children: [
1484
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1485
+ "button",
1486
+ {
1487
+ onClick: duplicateFile,
1488
+ disabled: loading,
1489
+ style: {
1490
+ padding: "0.5rem 1rem",
1491
+ background: "#f5f5f5",
1492
+ border: "1px solid #ddd",
1493
+ borderRadius: "4px",
1494
+ cursor: "pointer"
1495
+ },
1496
+ children: "\u{1F4CB} Duplicate"
1497
+ }
1498
+ ),
1499
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1500
+ "button",
1501
+ {
1502
+ onClick: saveFile,
1503
+ disabled: loading,
1504
+ style: {
1505
+ padding: "0.5rem 1rem",
1506
+ background: "#0070f3",
1507
+ color: "white",
1508
+ border: "none",
1509
+ borderRadius: "4px",
1510
+ cursor: "pointer",
1511
+ opacity: loading ? 0.7 : 1
1512
+ },
1513
+ children: loading ? "Saving..." : "\u{1F4BE} Save Changes"
1514
+ }
1515
+ )
1516
+ ] })
1517
+ ]
1518
+ }
1519
+ ),
1520
+ message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1521
+ "div",
1522
+ {
1523
+ style: {
1524
+ marginBottom: "1.5rem",
1525
+ padding: "0.75rem",
1526
+ background: message.includes("\u2705") ? "#e6ffe6" : "#ffe6e6",
1527
+ borderRadius: "4px",
1528
+ border: message.includes("\u2705") ? "1px solid #a5d6a7" : "1px solid #ef9a9a"
1529
+ },
1530
+ children: message
1531
+ }
1532
+ ),
1533
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: "2rem" }, children: [
1534
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1535
+ "div",
1536
+ {
1537
+ style: {
1538
+ display: "flex",
1539
+ justifyContent: "space-between",
1540
+ alignItems: "center",
1541
+ marginBottom: "1rem"
1542
+ },
1543
+ children: [
1544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: { fontSize: "1.1rem", margin: 0 }, children: "Fields" }),
1545
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1546
+ "button",
1547
+ {
1548
+ onClick: addField,
1549
+ style: {
1550
+ fontSize: "0.875rem",
1551
+ padding: "0.4rem 0.8rem",
1552
+ background: "#e1f5fe",
1553
+ color: "#0288d1",
1554
+ border: "none",
1555
+ borderRadius: "4px",
1556
+ cursor: "pointer"
1557
+ },
1558
+ children: "+ Add Field"
1559
+ }
1560
+ )
1561
+ ]
1562
+ }
1563
+ ),
1564
+ Object.keys(fields).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1565
+ "div",
1566
+ {
1567
+ style: {
1568
+ padding: "2rem",
1569
+ textAlign: "center",
1570
+ background: "#f9f9f9",
1571
+ borderRadius: "4px",
1572
+ border: "1px dashed #ccc"
1573
+ },
1574
+ children: [
1575
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1576
+ "p",
1577
+ {
1578
+ style: { color: "#666", fontStyle: "italic", margin: 0 },
1579
+ children: 'No fields yet. Click "+ Add Field" to start.'
1580
+ }
1581
+ ),
1582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1583
+ "small",
1584
+ {
1585
+ style: {
1586
+ color: "#999",
1587
+ marginTop: "0.5rem",
1588
+ display: "block"
1589
+ },
1590
+ children: "Add 'content' field for the main body"
1591
+ }
1592
+ )
1593
+ ]
1594
+ }
1595
+ ) : Object.entries(fields).map(([key, field]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1596
+ "div",
1597
+ {
1598
+ style: {
1599
+ marginBottom: "1.5rem",
1600
+ background: "#fff",
1601
+ border: "1px solid #eee",
1602
+ padding: "1rem",
1603
+ borderRadius: "6px"
1604
+ },
1605
+ children: [
1606
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1607
+ "div",
1608
+ {
1609
+ style: {
1610
+ display: "flex",
1611
+ justifyContent: "space-between",
1612
+ alignItems: "center",
1613
+ marginBottom: "0.5rem"
1614
+ },
1615
+ children: [
1616
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: key, style: { fontWeight: 500 }, children: [
1617
+ key,
1618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1619
+ "span",
1620
+ {
1621
+ style: {
1622
+ marginLeft: "0.5rem",
1623
+ fontSize: "0.75rem",
1624
+ color: "#999",
1625
+ background: "#f0f0f0",
1626
+ padding: "0.1rem 0.4rem",
1627
+ borderRadius: "4px"
1628
+ },
1629
+ children: field.type
1630
+ }
1631
+ )
1632
+ ] }),
1633
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1634
+ "button",
1635
+ {
1636
+ onClick: () => removeField(key),
1637
+ style: {
1638
+ background: "none",
1639
+ border: "none",
1640
+ color: "#e57373",
1641
+ cursor: "pointer",
1642
+ fontSize: "0.875rem"
1643
+ },
1644
+ children: "\u2715 Remove"
1645
+ }
1646
+ )
1647
+ ]
1648
+ }
1649
+ ),
1650
+ renderField(key, field)
1651
+ ]
1652
+ },
1653
+ key
1654
+ ))
1655
+ ] }),
1656
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1657
+ "div",
1658
+ {
1659
+ style: {
1660
+ marginTop: "3rem",
1661
+ paddingTop: "1rem",
1662
+ borderTop: "1px solid #eee"
1663
+ },
1664
+ children: [
1665
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1666
+ "h3",
1667
+ {
1668
+ style: {
1669
+ fontSize: "1rem",
1670
+ marginBottom: "0.5rem",
1671
+ color: "#666"
1672
+ },
1673
+ children: "Quick File Upload"
1674
+ }
1675
+ ),
1676
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "0.5rem" }, children: [
1677
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1678
+ "input",
1679
+ {
1680
+ type: "file",
1681
+ onChange: handleFileUpload,
1682
+ disabled: uploading,
1683
+ style: { flex: 1 }
1684
+ }
1685
+ ),
1686
+ uploading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#666" }, children: "Uploading..." })
1687
+ ] })
1688
+ ]
1689
+ }
1690
+ )
1691
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1692
+ "div",
1693
+ {
1694
+ style: {
1695
+ textAlign: "center",
1696
+ padding: "5rem 2rem",
1697
+ color: "#999"
1698
+ },
1699
+ children: [
1700
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: "3rem", marginBottom: "1rem" }, children: "\u{1F448}" }),
1701
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: "1.1rem" }, children: "Select a file from the sidebar to edit" }),
1702
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "or create a new file to get started" })
1703
+ ]
1704
+ }
1705
+ )
1706
+ }
1707
+ )
1708
+ ]
1709
+ }
1710
+ )
1711
+ ]
1712
+ }
1713
+ );
1714
+ }
1715
+
1716
+ // src/ui/Login.tsx
1717
+ var import_react2 = require("react");
1718
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1719
+ function Login() {
1720
+ const [username, setUsername] = (0, import_react2.useState)("");
1721
+ const [password, setPassword] = (0, import_react2.useState)("");
1722
+ const [error, setError] = (0, import_react2.useState)("");
1723
+ const [loading, setLoading] = (0, import_react2.useState)(false);
1724
+ const handleSubmit = async (e) => {
1725
+ e.preventDefault();
1726
+ setError("");
1727
+ setLoading(true);
1728
+ try {
1729
+ const response = await fetch("/api/cms/login", {
1730
+ method: "POST",
1731
+ headers: { "Content-Type": "application/json" },
1732
+ body: JSON.stringify({ username, password })
1733
+ });
1734
+ const data = await response.json();
1735
+ if (response.ok) {
1736
+ window.location.href = "/admin";
1737
+ } else {
1738
+ setError(data.error || "Login failed");
1739
+ }
1740
+ } catch (err) {
1741
+ setError("An error occurred. Please try again.");
1742
+ } finally {
1743
+ setLoading(false);
1744
+ }
1745
+ };
1746
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1747
+ "div",
1748
+ {
1749
+ style: {
1750
+ display: "flex",
1751
+ justifyContent: "center",
1752
+ alignItems: "center",
1753
+ minHeight: "100vh",
1754
+ background: "#f5f5f5",
1755
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
1756
+ },
1757
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1758
+ "div",
1759
+ {
1760
+ style: {
1761
+ width: "100%",
1762
+ maxWidth: "400px",
1763
+ padding: "2rem",
1764
+ background: "white",
1765
+ borderRadius: "8px",
1766
+ boxShadow: "0 4px 6px rgba(0,0,0,0.1)"
1767
+ },
1768
+ children: [
1769
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { style: { marginBottom: "0.5rem", textAlign: "center" }, children: "Admin Login" }),
1770
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "#666", marginBottom: "2rem", textAlign: "center" }, children: "Enter your credentials to access the CMS" }),
1771
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, children: [
1772
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: "1rem" }, children: [
1773
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1774
+ "label",
1775
+ {
1776
+ htmlFor: "username",
1777
+ style: {
1778
+ display: "block",
1779
+ marginBottom: "0.5rem",
1780
+ fontWeight: 500
1781
+ },
1782
+ children: "Username"
1783
+ }
1784
+ ),
1785
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1786
+ "input",
1787
+ {
1788
+ id: "username",
1789
+ type: "text",
1790
+ value: username,
1791
+ onChange: (e) => setUsername(e.target.value),
1792
+ required: true,
1793
+ autoFocus: true,
1794
+ style: {
1795
+ width: "100%",
1796
+ padding: "0.75rem",
1797
+ border: "1px solid #ddd",
1798
+ borderRadius: "4px"
1799
+ }
1800
+ }
1801
+ )
1802
+ ] }),
1803
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: "1.5rem" }, children: [
1804
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1805
+ "label",
1806
+ {
1807
+ htmlFor: "password",
1808
+ style: {
1809
+ display: "block",
1810
+ marginBottom: "0.5rem",
1811
+ fontWeight: 500
1812
+ },
1813
+ children: "Password"
1814
+ }
1815
+ ),
1816
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1817
+ "input",
1818
+ {
1819
+ id: "password",
1820
+ type: "password",
1821
+ value: password,
1822
+ onChange: (e) => setPassword(e.target.value),
1823
+ required: true,
1824
+ style: {
1825
+ width: "100%",
1826
+ padding: "0.75rem",
1827
+ border: "1px solid #ddd",
1828
+ borderRadius: "4px"
1829
+ }
1830
+ }
1831
+ )
1832
+ ] }),
1833
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1834
+ "div",
1835
+ {
1836
+ style: {
1837
+ marginBottom: "1rem",
1838
+ padding: "0.75rem",
1839
+ background: "#ffe6e6",
1840
+ color: "#d32f2f",
1841
+ borderRadius: "4px",
1842
+ fontSize: "0.875rem"
1843
+ },
1844
+ children: error
1845
+ }
1846
+ ),
1847
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1848
+ "button",
1849
+ {
1850
+ type: "submit",
1851
+ disabled: loading,
1852
+ style: {
1853
+ width: "100%",
1854
+ padding: "0.75rem",
1855
+ background: "#0070f3",
1856
+ color: "white",
1857
+ border: "none",
1858
+ borderRadius: "4px",
1859
+ fontSize: "1rem",
1860
+ fontWeight: 500,
1861
+ cursor: "pointer",
1862
+ opacity: loading ? 0.7 : 1
1863
+ },
1864
+ children: loading ? "Logging in..." : "Login"
1865
+ }
1866
+ )
1867
+ ] })
1868
+ ]
1869
+ }
1870
+ )
1871
+ }
1872
+ );
1873
+ }
1874
+
819
1875
  // src/middleware/auth.ts
820
1876
  var import_server3 = require("next/server");
821
1877
  function createAuthMiddleware(options = {}) {
@@ -846,9 +1902,64 @@ function createAuthMiddleware(options = {}) {
846
1902
  // src/init/setup.ts
847
1903
  var import_promises2 = __toESM(require("fs/promises"));
848
1904
  var import_path3 = __toESM(require("path"));
1905
+ var import_child_process = require("child_process");
1906
+ var import_util = require("util");
1907
+ var execAsync = (0, import_util.promisify)(import_child_process.exec);
849
1908
  async function initCMS(config = {}) {
850
1909
  const contentDir = config.contentDir || "public/content";
851
1910
  const fullPath = import_path3.default.join(process.cwd(), contentDir);
1911
+ const appDir = import_path3.default.join(process.cwd(), "app");
1912
+ try {
1913
+ await import_promises2.default.access(appDir);
1914
+ } catch {
1915
+ console.error(
1916
+ '\u274C Error: "app" directory not found. This init script requires Next.js App Router.'
1917
+ );
1918
+ return;
1919
+ }
1920
+ console.log("\u{1F680} Initializing @fydemy/cms...");
1921
+ console.log("\u{1F4E6} Checking dependencies...");
1922
+ try {
1923
+ const packageJsonPath = import_path3.default.join(process.cwd(), "package.json");
1924
+ const packageJson = JSON.parse(await import_promises2.default.readFile(packageJsonPath, "utf-8"));
1925
+ const dependencies = {
1926
+ ...packageJson.dependencies,
1927
+ ...packageJson.devDependencies
1928
+ };
1929
+ const missingDeps = [];
1930
+ if (!dependencies["@fydemy/cms"]) missingDeps.push("@fydemy/cms");
1931
+ if (!dependencies["tailwindcss"]) missingDeps.push("tailwindcss");
1932
+ if (!dependencies["class-variance-authority"])
1933
+ missingDeps.push("class-variance-authority");
1934
+ if (!dependencies["clsx"]) missingDeps.push("clsx");
1935
+ if (!dependencies["tailwind-merge"]) missingDeps.push("tailwind-merge");
1936
+ if (!dependencies["@radix-ui/react-slot"])
1937
+ missingDeps.push("@radix-ui/react-slot");
1938
+ if (!dependencies["@radix-ui/react-label"])
1939
+ missingDeps.push("@radix-ui/react-label");
1940
+ if (missingDeps.length > 0) {
1941
+ console.log(
1942
+ `\u{1F527} Installing missing dependencies: ${missingDeps.join(", ")}...`
1943
+ );
1944
+ let installCmd = "npm install";
1945
+ try {
1946
+ await import_promises2.default.access("pnpm-lock.yaml");
1947
+ installCmd = "pnpm add";
1948
+ } catch {
1949
+ try {
1950
+ await import_promises2.default.access("yarn.lock");
1951
+ installCmd = "yarn add";
1952
+ } catch {
1953
+ }
1954
+ }
1955
+ await execAsync(`${installCmd} ${missingDeps.join(" ")}`);
1956
+ console.log("\u2705 Dependencies installed");
1957
+ }
1958
+ } catch (error) {
1959
+ console.warn(
1960
+ "\u26A0\uFE0F Could not check/install dependencies automatically. Please ensure all dependencies are installed."
1961
+ );
1962
+ }
852
1963
  await import_promises2.default.mkdir(fullPath, { recursive: true });
853
1964
  const exampleContent = `---
854
1965
  title: Example Post
@@ -870,25 +1981,177 @@ This is an example markdown file. You can edit or delete it from the admin dashb
870
1981
  `;
871
1982
  const examplePath = import_path3.default.join(fullPath, "example.md");
872
1983
  await import_promises2.default.writeFile(examplePath, exampleContent, "utf-8");
873
- console.log("\u2705 CMS initialized successfully!");
874
- console.log(`\u{1F4C1} Content directory: ${contentDir}`);
875
- console.log(`\u{1F4DD} Example file created: ${contentDir}/example.md`);
1984
+ console.log("\u2705 Created content directory and example file");
1985
+ const adminDir = import_path3.default.join(appDir, "admin");
1986
+ const loginDir = import_path3.default.join(adminDir, "login");
1987
+ await import_promises2.default.mkdir(loginDir, { recursive: true });
1988
+ const loginTemplate = await import_promises2.default.readFile(
1989
+ import_path3.default.join(__dirname, "login.template.tsx"),
1990
+ "utf-8"
1991
+ );
1992
+ const adminTemplate = await import_promises2.default.readFile(
1993
+ import_path3.default.join(__dirname, "admin.template.tsx"),
1994
+ "utf-8"
1995
+ );
1996
+ await import_promises2.default.writeFile(import_path3.default.join(adminDir, "page.tsx"), adminTemplate, "utf-8");
1997
+ await import_promises2.default.writeFile(import_path3.default.join(loginDir, "page.tsx"), loginTemplate, "utf-8");
1998
+ console.log("\u2705 Scaffolded Admin UI (shadcn/ui components)");
1999
+ const componentsDir = import_path3.default.join(process.cwd(), "components", "ui");
2000
+ const libDir = import_path3.default.join(process.cwd(), "lib");
2001
+ await import_promises2.default.mkdir(componentsDir, { recursive: true });
2002
+ await import_promises2.default.mkdir(libDir, { recursive: true });
2003
+ const utilsTemplate = await import_promises2.default.readFile(
2004
+ import_path3.default.join(__dirname, "lib", "utils.ts"),
2005
+ "utf-8"
2006
+ );
2007
+ await import_promises2.default.writeFile(import_path3.default.join(libDir, "utils.ts"), utilsTemplate, "utf-8");
2008
+ const componentFiles = [
2009
+ "button.tsx",
2010
+ "input.tsx",
2011
+ "card.tsx",
2012
+ "label.tsx",
2013
+ "textarea.tsx",
2014
+ "badge.tsx"
2015
+ ];
2016
+ for (const componentFile of componentFiles) {
2017
+ const componentTemplate = await import_promises2.default.readFile(
2018
+ import_path3.default.join(__dirname, "components", "ui", componentFile),
2019
+ "utf-8"
2020
+ );
2021
+ await import_promises2.default.writeFile(
2022
+ import_path3.default.join(componentsDir, componentFile),
2023
+ componentTemplate,
2024
+ "utf-8"
2025
+ );
2026
+ }
2027
+ const componentsJsonTemplate = await import_promises2.default.readFile(
2028
+ import_path3.default.join(__dirname, "components.json"),
2029
+ "utf-8"
2030
+ );
2031
+ await import_promises2.default.writeFile(
2032
+ import_path3.default.join(process.cwd(), "components.json"),
2033
+ componentsJsonTemplate,
2034
+ "utf-8"
2035
+ );
2036
+ console.log("\u2705 Scaffolded shadcn/ui components");
2037
+ const apiCmsDir = import_path3.default.join(appDir, "api", "cms");
2038
+ await import_promises2.default.mkdir(import_path3.default.join(apiCmsDir, "login"), { recursive: true });
2039
+ await import_promises2.default.writeFile(
2040
+ import_path3.default.join(apiCmsDir, "login", "route.ts"),
2041
+ `import { handleLogin } from '@fydemy/cms';
2042
+ export { handleLogin as POST };
2043
+ `,
2044
+ "utf-8"
2045
+ );
2046
+ await import_promises2.default.mkdir(import_path3.default.join(apiCmsDir, "logout"), { recursive: true });
2047
+ await import_promises2.default.writeFile(
2048
+ import_path3.default.join(apiCmsDir, "logout", "route.ts"),
2049
+ `import { handleLogout } from '@fydemy/cms';
2050
+ export { handleLogout as POST };
2051
+ `,
2052
+ "utf-8"
2053
+ );
2054
+ await import_promises2.default.mkdir(import_path3.default.join(apiCmsDir, "upload"), { recursive: true });
2055
+ await import_promises2.default.writeFile(
2056
+ import_path3.default.join(apiCmsDir, "upload", "route.ts"),
2057
+ `import { handleUpload } from '@fydemy/cms';
2058
+ export { handleUpload as POST };
2059
+ `,
2060
+ "utf-8"
2061
+ );
2062
+ await import_promises2.default.mkdir(import_path3.default.join(apiCmsDir, "list", "[[...path]]"), {
2063
+ recursive: true
2064
+ });
2065
+ await import_promises2.default.writeFile(
2066
+ import_path3.default.join(apiCmsDir, "list", "[[...path]]", "route.ts"),
2067
+ `import { createListApiHandlers } from '@fydemy/cms';
2068
+
2069
+ const handlers = createListApiHandlers();
2070
+ export const GET = handlers.GET;
2071
+ `,
2072
+ "utf-8"
2073
+ );
2074
+ await import_promises2.default.mkdir(import_path3.default.join(apiCmsDir, "content", "[...path]"), {
2075
+ recursive: true
2076
+ });
2077
+ await import_promises2.default.writeFile(
2078
+ import_path3.default.join(apiCmsDir, "content", "[...path]", "route.ts"),
2079
+ `import { createContentApiHandlers } from '@fydemy/cms';
2080
+
2081
+ const handlers = createContentApiHandlers();
2082
+ export const GET = handlers.GET;
2083
+ export const POST = handlers.POST;
2084
+ export const DELETE = handlers.DELETE;
2085
+ `,
2086
+ "utf-8"
2087
+ );
2088
+ console.log("\u2705 Created API routes");
2089
+ const middlewarePath = import_path3.default.join(process.cwd(), "middleware.ts");
2090
+ try {
2091
+ await import_promises2.default.access(middlewarePath);
2092
+ console.log(
2093
+ "\u26A0\uFE0F middleware.ts already exists. Please manually add the CMS auth middleware:"
2094
+ );
2095
+ console.log(`
2096
+ import { createAuthMiddleware } from '@fydemy/cms';
2097
+ // ... existing imports
2098
+
2099
+ export function middleware(request: NextRequest) {
2100
+ // Add this:
2101
+ const authResponse = createAuthMiddleware()(request);
2102
+ if (authResponse) return authResponse;
2103
+
2104
+ // ... existing middleware logic
2105
+ }
2106
+ `);
2107
+ } catch {
2108
+ await import_promises2.default.writeFile(
2109
+ middlewarePath,
2110
+ `import { createAuthMiddleware } from '@fydemy/cms';
2111
+ import { NextRequest } from 'next/server';
2112
+
2113
+ export function middleware(request: NextRequest) {
2114
+ return createAuthMiddleware()(request);
2115
+ }
2116
+
2117
+ export const config = {
2118
+ matcher: ['/admin/:path*'],
2119
+ };
2120
+ `,
2121
+ "utf-8"
2122
+ );
2123
+ console.log("\u2705 Created middleware.ts");
2124
+ }
2125
+ const envExamplePath = import_path3.default.join(process.cwd(), ".env.local.example");
2126
+ await import_promises2.default.writeFile(
2127
+ envExamplePath,
2128
+ `CMS_ADMIN_USERNAME=admin
2129
+ CMS_ADMIN_PASSWORD=password
2130
+ CMS_SESSION_SECRET=ensure_this_is_at_least_32_chars_long_random_string
2131
+
2132
+ # GitHub Storage (Production)
2133
+ GITHUB_TOKEN=
2134
+ GITHUB_REPO=owner/repo
2135
+ GITHUB_BRANCH=main
2136
+ `,
2137
+ "utf-8"
2138
+ );
876
2139
  console.log("");
877
- console.log("Next steps:");
878
- console.log("1. Set up environment variables in .env.local:");
879
- console.log(" - CMS_ADMIN_USERNAME");
880
- console.log(" - CMS_ADMIN_PASSWORD");
881
- console.log(" - CMS_SESSION_SECRET (min 32 characters)");
882
- console.log("2. For production: GITHUB_TOKEN, GITHUB_REPO, GITHUB_BRANCH");
883
- console.log("3. Import and use the CMS utilities in your Next.js app");
2140
+ console.log("\u{1F389} CMS initialized successfully!");
2141
+ console.log(
2142
+ "1. Copy .env.local.example to .env.local and set your credentials"
2143
+ );
2144
+ console.log("2. Run your dev server and visit /admin");
884
2145
  }
885
2146
 
886
2147
  // src/index.ts
887
2148
  init_rate_limit();
888
2149
  // Annotate the CommonJS export names for ESM import in node:
889
2150
  0 && (module.exports = {
2151
+ AdminDashboard,
890
2152
  GitHubStorage,
891
2153
  LocalStorage,
2154
+ Login,
892
2155
  MAX_FILE_SIZE,
893
2156
  MAX_PASSWORD_LENGTH,
894
2157
  MAX_USERNAME_LENGTH,