@monkeyplus/flow 6.0.12 → 6.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 +5 -0
- package/modules/content/module.mjs +12 -0
- package/modules/content/query.d.ts +1 -1
- package/modules/content/query.mjs +157 -57
- package/modules/netlify-cms/handler.d.ts +2 -0
- package/modules/netlify-cms/handler.mjs +5 -0
- package/modules/netlify-cms/module.d.ts +6 -0
- package/modules/netlify-cms/module.mjs +66 -0
- package/modules/netlify-cms/resources/admin.html +16 -0
- package/modules/netlify-cms/resources/adminV2.html +15 -0
- package/modules/netlify-cms/runtime/cms.d.ts +3 -0
- package/modules/netlify-cms/runtime/cms.mjs +3 -0
- package/modules/netlify-cms/server/api/admin.d.ts +2 -0
- package/modules/netlify-cms/server/api/admin.mjs +20 -0
- package/modules/netlify-cms/server/api/config.d.ts +2 -0
- package/modules/netlify-cms/server/api/config.mjs +72 -0
- package/modules/netlify-cms/server/api/local-fs.d.ts +2 -0
- package/modules/netlify-cms/server/api/local-fs.mjs +189 -0
- package/modules/netlify-cms/server/api/meta.d.ts +2 -0
- package/modules/netlify-cms/server/api/meta.mjs +8 -0
- package/modules/netlify-cms/server/lib/cms/handler.d.ts +2 -0
- package/modules/netlify-cms/server/lib/cms/handler.mjs +37 -0
- package/modules/netlify-cms/server/lib/cms/handlerV1.d.ts +2 -0
- package/modules/netlify-cms/server/lib/cms/handlerV1.mjs +40 -0
- package/modules/netlify-cms/server/lib/cms/helpers.d.ts +14 -0
- package/modules/netlify-cms/server/lib/cms/helpers.mjs +76 -0
- package/modules/netlify-cms/server/lib/cms/widgets.d.ts +113 -0
- package/modules/netlify-cms/server/lib/cms/widgets.mjs +168 -0
- package/modules/netlify-cms/server/lib/composables.d.ts +28 -0
- package/modules/netlify-cms/server/lib/composables.mjs +14 -0
- package/modules/netlify-cms/server/lib/entries.d.ts +25 -0
- package/modules/netlify-cms/server/lib/entries.mjs +39 -0
- package/modules/netlify-cms/server/lib/fs.d.ts +5 -0
- package/modules/netlify-cms/server/lib/fs.mjs +43 -0
- package/modules/netlify-cms/server/lib/types/collections.d.ts +16 -0
- package/modules/netlify-cms/server/lib/types/collections.mjs +0 -0
- package/modules/netlify-cms/server/lib/types/helpers.d.ts +23 -0
- package/modules/netlify-cms/server/lib/types/helpers.mjs +0 -0
- package/modules/netlify-cms/server/lib/types/index.d.ts +23 -0
- package/modules/netlify-cms/server/lib/types/index.mjs +11 -0
- package/modules/netlify-cms/server/lib/types/widgets.d.ts +39 -0
- package/modules/netlify-cms/server/lib/types/widgets.mjs +0 -0
- package/package.json +1 -1
- package/server/lib/handler.mjs +2 -2
- package/server/lib/pages.mjs +24 -3
- package/server/renderer.mjs +4 -0
- package/src/public/nitro.mjs +2 -0
- package/src/public/query-content.d.ts +8 -0
- package/src/public/query-content.mjs +103 -37
- package/src/runtime/config.d.ts +7 -0
- package/src/runtime/modules.mjs +2 -1
- package/src/runtime/nitro-plugin.d.ts +1 -0
- package/src/runtime/nitro-plugin.mjs +5 -0
- package/src/runtime/page-discovery.mjs +24 -3
package/README.md
CHANGED
|
@@ -25,6 +25,11 @@ La guia detallada de authoring vive en `docs/authoring.md` e incluye:
|
|
|
25
25
|
- como organizar templates, layouts y `*.context.ts`
|
|
26
26
|
- como trabajar con `FlowIsland` y `client/islands/*`
|
|
27
27
|
|
|
28
|
+
Documentacion complementaria:
|
|
29
|
+
|
|
30
|
+
- `docs/modules.md`: referencia de modulos builtin
|
|
31
|
+
- `docs/recipes.md`: recetas basadas en el playground actual
|
|
32
|
+
|
|
28
33
|
Si vas a crear o mantener una app en Flow, ese archivo deberia ser el punto de entrada principal.
|
|
29
34
|
|
|
30
35
|
## API pública
|
|
@@ -29,6 +29,18 @@ export default defineFlowModule({
|
|
|
29
29
|
context.nitro.routeRules[`${options.apiBase}/**`] = {
|
|
30
30
|
cors: true
|
|
31
31
|
};
|
|
32
|
+
const fetchPluginPath = context.projectRoot === resolvePackagePath() ? resolve(context.projectRoot, "src/runtime/nitro-plugin.ts") : resolvePackageFile("src/runtime/nitro-plugin.ts", "src/runtime/nitro-plugin.mjs", "src/runtime/nitro-plugin.js");
|
|
33
|
+
context.nitro.plugins = context.nitro.plugins || [];
|
|
34
|
+
if (!context.nitro.plugins.includes(fetchPluginPath)) {
|
|
35
|
+
context.nitro.plugins.push(fetchPluginPath);
|
|
36
|
+
}
|
|
37
|
+
context.nitro.storage = {
|
|
38
|
+
...context.nitro.storage || {},
|
|
39
|
+
content: {
|
|
40
|
+
driver: "fs",
|
|
41
|
+
base: contentDir
|
|
42
|
+
}
|
|
43
|
+
};
|
|
32
44
|
context.nitro.runtimeConfig.flow = {
|
|
33
45
|
...typeof context.nitro.runtimeConfig.flow === "object" && context.nitro.runtimeConfig.flow ? context.nitro.runtimeConfig.flow : {},
|
|
34
46
|
content: {
|
|
@@ -20,7 +20,7 @@ export interface ContentFileNode extends ContentEntry {
|
|
|
20
20
|
}
|
|
21
21
|
export type ContentTreeNode = ContentDirectoryNode | ContentFileNode;
|
|
22
22
|
export declare function findContentEntries(entries: ContentEntry[], path?: string): ContentEntry[];
|
|
23
|
-
export declare function readContentEntries(
|
|
23
|
+
export declare function readContentEntries(): Promise<ContentEntry[]>;
|
|
24
24
|
export declare function buildContentTree(entries: ContentEntry[]): ContentTreeNode[];
|
|
25
25
|
export declare function findContentTree(tree: ContentTreeNode[], path?: string): ContentTreeNode[];
|
|
26
26
|
declare const _default: any;
|
|
@@ -1,33 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { extname, relative, resolve } from "node:path";
|
|
1
|
+
import { extname } from "node:path";
|
|
3
2
|
import { defineEventHandler, getQuery, getRequestURL } from "nitro/h3";
|
|
4
|
-
import {
|
|
5
|
-
function collectFiles(rootDir, currentDir = rootDir) {
|
|
6
|
-
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
7
|
-
const files = [];
|
|
8
|
-
for (const entry of entries) {
|
|
9
|
-
const fullPath = resolve(currentDir, entry.name);
|
|
10
|
-
if (entry.isDirectory()) {
|
|
11
|
-
files.push(...collectFiles(rootDir, fullPath));
|
|
12
|
-
continue;
|
|
13
|
-
}
|
|
14
|
-
if (!entry.isFile()) {
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
const extension = extname(entry.name);
|
|
18
|
-
if (![".md", ".json", ".yml", ".yaml", ".txt"].includes(extension)) {
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
files.push(fullPath);
|
|
22
|
-
}
|
|
23
|
-
return files.sort((left, right) => left.localeCompare(right));
|
|
24
|
-
}
|
|
3
|
+
import { useStorage } from "nitro/storage";
|
|
25
4
|
function normalizeQueryPath(path) {
|
|
26
|
-
if (!path) {
|
|
5
|
+
if (!path || path === "/") {
|
|
27
6
|
return "/";
|
|
28
7
|
}
|
|
29
|
-
|
|
30
|
-
return normalized.length > 1 && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
8
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
31
9
|
}
|
|
32
10
|
export function findContentEntries(entries, path) {
|
|
33
11
|
const normalizedPath = normalizeQueryPath(path);
|
|
@@ -36,8 +14,8 @@ export function findContentEntries(entries, path) {
|
|
|
36
14
|
}
|
|
37
15
|
return entries.filter((entry) => entry.path === normalizedPath || entry.path.startsWith(`${normalizedPath}/`));
|
|
38
16
|
}
|
|
39
|
-
function normalizeContentPath(
|
|
40
|
-
const shortPath =
|
|
17
|
+
function normalizeContentPath(keyPath) {
|
|
18
|
+
const shortPath = keyPath;
|
|
41
19
|
const stem = shortPath.replace(/\.(md|json|ya?ml|txt)$/i, "");
|
|
42
20
|
const segments = stem.split("/").filter(Boolean);
|
|
43
21
|
return {
|
|
@@ -59,35 +37,36 @@ function parseKeyValueBlock(block) {
|
|
|
59
37
|
return data;
|
|
60
38
|
}, {});
|
|
61
39
|
}
|
|
62
|
-
function parseContentFile(
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const normalizedPath = normalizeContentPath(rootDir, filePath);
|
|
40
|
+
function parseContentFile(keyPath, raw) {
|
|
41
|
+
const extension = extname(keyPath).toLowerCase();
|
|
42
|
+
const normalizedPath = normalizeContentPath(keyPath);
|
|
66
43
|
if (extension === ".json") {
|
|
67
|
-
const parsed = JSON.parse(raw);
|
|
44
|
+
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
45
|
+
const body = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
68
46
|
return {
|
|
69
47
|
...normalizedPath,
|
|
70
48
|
extension,
|
|
71
49
|
title: typeof parsed.title === "string" ? parsed.title : void 0,
|
|
72
|
-
body
|
|
50
|
+
body,
|
|
73
51
|
data: parsed
|
|
74
52
|
};
|
|
75
53
|
}
|
|
76
|
-
|
|
77
|
-
|
|
54
|
+
const rawString = String(raw);
|
|
55
|
+
if ((extension === ".yml" || extension === ".yaml") && rawString.trim()) {
|
|
56
|
+
const data = parseKeyValueBlock(rawString);
|
|
78
57
|
return {
|
|
79
58
|
...normalizedPath,
|
|
80
59
|
extension,
|
|
81
60
|
title: data.title,
|
|
82
|
-
body:
|
|
61
|
+
body: rawString,
|
|
83
62
|
data
|
|
84
63
|
};
|
|
85
64
|
}
|
|
86
|
-
if (
|
|
87
|
-
const end =
|
|
65
|
+
if (rawString.startsWith("---\n")) {
|
|
66
|
+
const end = rawString.indexOf("\n---\n", 4);
|
|
88
67
|
if (end >= 0) {
|
|
89
|
-
const frontmatter =
|
|
90
|
-
const body =
|
|
68
|
+
const frontmatter = rawString.slice(4, end);
|
|
69
|
+
const body = rawString.slice(end + 5).trim();
|
|
91
70
|
const data = parseKeyValueBlock(frontmatter);
|
|
92
71
|
return {
|
|
93
72
|
...normalizedPath,
|
|
@@ -101,15 +80,60 @@ function parseContentFile(rootDir, filePath) {
|
|
|
101
80
|
return {
|
|
102
81
|
...normalizedPath,
|
|
103
82
|
extension,
|
|
104
|
-
body:
|
|
83
|
+
body: rawString,
|
|
105
84
|
data: {}
|
|
106
85
|
};
|
|
107
86
|
}
|
|
108
|
-
export function readContentEntries(
|
|
109
|
-
|
|
110
|
-
|
|
87
|
+
export async function readContentEntries() {
|
|
88
|
+
const storage = useStorage("content");
|
|
89
|
+
const keys = await storage.getKeys();
|
|
90
|
+
const entries = [];
|
|
91
|
+
if (keys.length === 0) {
|
|
92
|
+
try {
|
|
93
|
+
const fs = await import("node:fs/promises");
|
|
94
|
+
const path = await import("node:path");
|
|
95
|
+
const contentDir = path.resolve(process.cwd(), "content");
|
|
96
|
+
async function walkDir(dir, baseDir) {
|
|
97
|
+
const files = await fs.readdir(dir, { withFileTypes: true });
|
|
98
|
+
for (const file of files) {
|
|
99
|
+
const res = path.resolve(dir, file.name);
|
|
100
|
+
if (file.isDirectory()) {
|
|
101
|
+
await walkDir(res, baseDir);
|
|
102
|
+
} else {
|
|
103
|
+
const extension = path.extname(res).toLowerCase();
|
|
104
|
+
if ([".md", ".json", ".yml", ".yaml", ".txt"].includes(extension)) {
|
|
105
|
+
const relativePath = path.relative(baseDir, res);
|
|
106
|
+
const normalizedKey = relativePath.replace(/\\/g, "/");
|
|
107
|
+
const raw = await fs.readFile(res, "utf-8");
|
|
108
|
+
const entry = parseContentFile(normalizedKey, raw);
|
|
109
|
+
entries.push(entry);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
await fs.access(contentDir);
|
|
116
|
+
await walkDir(contentDir, contentDir);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
}
|
|
119
|
+
return entries.sort((left, right) => left.path.localeCompare(right.path));
|
|
120
|
+
} catch (e) {
|
|
121
|
+
console.error("[Flow Content] fs fallback failed:", e);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const key of keys) {
|
|
125
|
+
const normalizedKey = key.replace(/:/g, "/");
|
|
126
|
+
const extension = extname(normalizedKey).toLowerCase();
|
|
127
|
+
if (![".md", ".json", ".yml", ".yaml", ".txt"].includes(extension)) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const raw = await storage.getItem(key);
|
|
131
|
+
if (raw !== null && raw !== void 0) {
|
|
132
|
+
const entry = parseContentFile(normalizedKey, raw);
|
|
133
|
+
entries.push(entry);
|
|
134
|
+
}
|
|
111
135
|
}
|
|
112
|
-
return
|
|
136
|
+
return entries.sort((left, right) => left.path.localeCompare(right.path));
|
|
113
137
|
}
|
|
114
138
|
function sortTree(nodes) {
|
|
115
139
|
nodes.sort((left, right) => {
|
|
@@ -129,17 +153,19 @@ export function buildContentTree(entries) {
|
|
|
129
153
|
const roots = [];
|
|
130
154
|
const directories = /* @__PURE__ */ new Map();
|
|
131
155
|
for (const entry of entries) {
|
|
132
|
-
const
|
|
133
|
-
|
|
156
|
+
const parts = entry.path.split("/").filter(Boolean);
|
|
157
|
+
let currentPath = "";
|
|
134
158
|
let siblings = roots;
|
|
135
|
-
for (let
|
|
136
|
-
const
|
|
159
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
160
|
+
const part = parts[i];
|
|
161
|
+
currentPath += `/${part}`;
|
|
162
|
+
const stem = currentPath.slice(1);
|
|
137
163
|
let directory = directories.get(stem);
|
|
138
164
|
if (!directory) {
|
|
139
165
|
directory = {
|
|
140
166
|
kind: "directory",
|
|
141
|
-
name:
|
|
142
|
-
path:
|
|
167
|
+
name: part,
|
|
168
|
+
path: currentPath,
|
|
143
169
|
stem,
|
|
144
170
|
children: []
|
|
145
171
|
};
|
|
@@ -172,19 +198,93 @@ export function findContentTree(tree, path) {
|
|
|
172
198
|
}
|
|
173
199
|
return [];
|
|
174
200
|
}
|
|
175
|
-
|
|
176
|
-
|
|
201
|
+
function getField(obj, path) {
|
|
202
|
+
return path.split(".").reduce((acc, part) => acc && acc[part], obj);
|
|
203
|
+
}
|
|
204
|
+
function setField(obj, path, value) {
|
|
205
|
+
const parts = path.split(".");
|
|
206
|
+
const last = parts.pop();
|
|
207
|
+
const target = parts.reduce((acc, part) => {
|
|
208
|
+
if (!acc[part])
|
|
209
|
+
acc[part] = {};
|
|
210
|
+
return acc[part];
|
|
211
|
+
}, obj);
|
|
212
|
+
target[last] = value;
|
|
213
|
+
}
|
|
214
|
+
export default defineEventHandler(async (event) => {
|
|
177
215
|
const query = getQuery(event);
|
|
178
|
-
const contentDir = runtimeConfig.flow?.content?.dir;
|
|
179
216
|
const requestUrl = getRequestURL(event);
|
|
180
217
|
const isTreeRequest = requestUrl.pathname.endsWith("/tree") || query.tree === true || query.tree === "true" || query.tree === "1";
|
|
181
|
-
|
|
218
|
+
let entries = await readContentEntries();
|
|
182
219
|
if (isTreeRequest) {
|
|
183
220
|
const tree = buildContentTree(entries);
|
|
184
221
|
return findContentTree(tree, query.path);
|
|
185
222
|
}
|
|
186
223
|
if (query.path) {
|
|
187
|
-
|
|
224
|
+
entries = findContentEntries(entries, query.path);
|
|
225
|
+
}
|
|
226
|
+
if (query.where) {
|
|
227
|
+
try {
|
|
228
|
+
const whereConditions = JSON.parse(query.where);
|
|
229
|
+
entries = entries.filter((entry) => {
|
|
230
|
+
return whereConditions.every((cond) => {
|
|
231
|
+
const val = getField(entry, cond.field);
|
|
232
|
+
switch (cond.operator.toUpperCase()) {
|
|
233
|
+
case "=":
|
|
234
|
+
return val === cond.value;
|
|
235
|
+
case "!=":
|
|
236
|
+
return val !== cond.value;
|
|
237
|
+
case ">":
|
|
238
|
+
return val > cond.value;
|
|
239
|
+
case ">=":
|
|
240
|
+
return val >= cond.value;
|
|
241
|
+
case "<":
|
|
242
|
+
return val < cond.value;
|
|
243
|
+
case "<=":
|
|
244
|
+
return val <= cond.value;
|
|
245
|
+
case "IN":
|
|
246
|
+
return Array.isArray(cond.value) && cond.value.includes(val);
|
|
247
|
+
case "LIKE":
|
|
248
|
+
return typeof val === "string" && val.includes(cond.value);
|
|
249
|
+
default:
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
} catch {
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (query.order) {
|
|
258
|
+
try {
|
|
259
|
+
const orderConditions = JSON.parse(query.order);
|
|
260
|
+
entries = entries.sort((a, b) => {
|
|
261
|
+
for (const cond of orderConditions) {
|
|
262
|
+
const valA = getField(a, cond.field);
|
|
263
|
+
const valB = getField(b, cond.field);
|
|
264
|
+
if (valA === valB)
|
|
265
|
+
continue;
|
|
266
|
+
const dir = cond.direction.toUpperCase() === "DESC" ? -1 : 1;
|
|
267
|
+
return valA > valB ? dir : -dir;
|
|
268
|
+
}
|
|
269
|
+
return 0;
|
|
270
|
+
});
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const skip = query.skip ? Number.parseInt(query.skip, 10) : 0;
|
|
275
|
+
const limit = query.limit ? Number.parseInt(query.limit, 10) : 0;
|
|
276
|
+
if (skip > 0 || limit > 0) {
|
|
277
|
+
entries = entries.slice(skip, limit > 0 ? skip + limit : void 0);
|
|
278
|
+
}
|
|
279
|
+
if (query.select) {
|
|
280
|
+
const fields = query.select.split(",");
|
|
281
|
+
entries = entries.map((entry) => {
|
|
282
|
+
const result = {};
|
|
283
|
+
for (const field of fields) {
|
|
284
|
+
setField(result, field, getField(entry, field));
|
|
285
|
+
}
|
|
286
|
+
return result;
|
|
287
|
+
});
|
|
188
288
|
}
|
|
189
289
|
return entries;
|
|
190
290
|
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { resolvePackageFile, resolvePackagePath } from "../../src/public/shared.mjs";
|
|
4
|
+
import { defineFlowModule } from "../../src/runtime/config.mjs";
|
|
5
|
+
export default defineFlowModule({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "netlify-cms",
|
|
8
|
+
configKey: "netlifyCms"
|
|
9
|
+
},
|
|
10
|
+
defaults: {
|
|
11
|
+
base: "/cms",
|
|
12
|
+
dir: "content"
|
|
13
|
+
},
|
|
14
|
+
setup(options, context) {
|
|
15
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
16
|
+
const handlerPath = context.projectRoot === resolvePackagePath() ? resolve(context.projectRoot, "modules/netlify-cms/server/api/local-fs.ts") : resolvePackageFile("modules/netlify-cms/server/api/local-fs.ts", "modules/netlify-cms/server/api/local-fs.mjs", "modules/netlify-cms/server/api/local-fs.js");
|
|
17
|
+
const configHandlerPath = context.projectRoot === resolvePackagePath() ? resolve(context.projectRoot, "modules/netlify-cms/server/api/config.ts") : resolvePackageFile("modules/netlify-cms/server/api/config.ts", "modules/netlify-cms/server/api/config.mjs", "modules/netlify-cms/server/api/config.js");
|
|
18
|
+
const adminHandlerPath = context.projectRoot === resolvePackagePath() ? resolve(context.projectRoot, "modules/netlify-cms/server/api/admin.ts") : resolvePackageFile("modules/netlify-cms/server/api/admin.ts", "modules/netlify-cms/server/api/admin.mjs", "modules/netlify-cms/server/api/admin.js");
|
|
19
|
+
const metaHandlerPath = context.projectRoot === resolvePackagePath() ? resolve(context.projectRoot, "modules/netlify-cms/server/api/meta.ts") : resolvePackageFile("modules/netlify-cms/server/api/meta.ts", "modules/netlify-cms/server/api/meta.mjs", "modules/netlify-cms/server/api/meta.js");
|
|
20
|
+
const locales = context.flowConfig.locale?.locales || ["es"];
|
|
21
|
+
context.nitro.virtual = context.nitro.virtual || {};
|
|
22
|
+
context.nitro.virtual["#cms-meta"] = `export const localeConfig = ${JSON.stringify(context.flowConfig.locale || {})};`;
|
|
23
|
+
context.nitro.handlers.push({
|
|
24
|
+
method: "get",
|
|
25
|
+
route: "/cms/v2/app/**",
|
|
26
|
+
handler: adminHandlerPath
|
|
27
|
+
});
|
|
28
|
+
context.nitro.handlers.push({
|
|
29
|
+
method: "get",
|
|
30
|
+
route: "/cms/v2/app.html",
|
|
31
|
+
handler: adminHandlerPath
|
|
32
|
+
});
|
|
33
|
+
context.nitro.handlers.push({
|
|
34
|
+
method: "get",
|
|
35
|
+
route: "/_flow/cms-meta.json",
|
|
36
|
+
handler: metaHandlerPath
|
|
37
|
+
});
|
|
38
|
+
context.prerenderRoutes?.add("/_flow/cms-meta.json");
|
|
39
|
+
context.prerenderRoutes?.add("/cms/v2/app.html");
|
|
40
|
+
for (const locale of locales) {
|
|
41
|
+
const configRoute = `/cms/${locale}/config.yml`;
|
|
42
|
+
context.nitro.handlers.push({
|
|
43
|
+
method: "get",
|
|
44
|
+
route: configRoute,
|
|
45
|
+
handler: configHandlerPath
|
|
46
|
+
});
|
|
47
|
+
context.prerenderRoutes?.add(configRoute);
|
|
48
|
+
}
|
|
49
|
+
if (isDev) {
|
|
50
|
+
context.nitro.handlers.push({
|
|
51
|
+
method: "post",
|
|
52
|
+
route: "/api/v1",
|
|
53
|
+
handler: handlerPath
|
|
54
|
+
});
|
|
55
|
+
context.nitro.routeRules = context.nitro.routeRules || {};
|
|
56
|
+
context.nitro.routeRules["/api/v1"] = {
|
|
57
|
+
cors: true
|
|
58
|
+
};
|
|
59
|
+
context.nitro.imports = context.nitro.imports || { imports: [] };
|
|
60
|
+
(context.nitro.imports.imports = context.nitro.imports.imports || []).push({
|
|
61
|
+
name: "defineCmsCollection",
|
|
62
|
+
from: resolvePackageFile("modules/netlify-cms/server/lib/composables.ts", "modules/netlify-cms/server/lib/composables.mjs", "modules/netlify-cms/server/lib/composables.js")
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Content Manager</title>
|
|
7
|
+
<link href="configV1.yml" type="text/yaml" rel="cms-config-url">
|
|
8
|
+
|
|
9
|
+
<!-- Include the script that enables Netlify Identity on this page. -->
|
|
10
|
+
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<!-- Include the script that builds the page and powers Netlify CMS -->
|
|
14
|
+
<script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Cms</title>
|
|
8
|
+
<link rel="stylesheet" href="https://cms2-demo.netlify.app/assets/windi.css">
|
|
9
|
+
<link rel="stylesheet" href="https://cms2-demo.netlify.app/assets/cms.css">
|
|
10
|
+
<script type="module" crossorigin src="https://cms2-demo.netlify.app/assets/cms.js"></script>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="app"></div>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineEventHandler, setResponseHeader } from "nitro/h3";
|
|
2
|
+
const adminHtml = `<!DOCTYPE html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
|
+
<title>Cms</title>
|
|
9
|
+
<link rel="stylesheet" href="https://cms2-demo.netlify.app/assets/windi.css">
|
|
10
|
+
<link rel="stylesheet" href="https://cms2-demo.netlify.app/assets/cms.css">
|
|
11
|
+
<script type="module" crossorigin src="https://cms2-demo.netlify.app/assets/cms.js"><\/script>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="app"></div>
|
|
15
|
+
</body>
|
|
16
|
+
</html>`;
|
|
17
|
+
export default defineEventHandler(async (event) => {
|
|
18
|
+
setResponseHeader(event, "Content-Type", "text/html;charset=utf-8");
|
|
19
|
+
return adminHtml;
|
|
20
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import defu from "defu";
|
|
2
|
+
import yml from "js-yaml";
|
|
3
|
+
import { defineEventHandler, setResponseHeader } from "nitro/h3";
|
|
4
|
+
import { collections, defineCms } from "../lib/cms/helpers.mjs";
|
|
5
|
+
import { widgets } from "../lib/cms/widgets.mjs";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { createJiti } from "jiti";
|
|
9
|
+
import process from "node:process";
|
|
10
|
+
import { defineCmsCollection } from "../lib/composables.mjs";
|
|
11
|
+
export default defineEventHandler(async (event) => {
|
|
12
|
+
if (!globalThis.defineCmsCollection) {
|
|
13
|
+
globalThis.defineCmsCollection = defineCmsCollection;
|
|
14
|
+
}
|
|
15
|
+
const urlParts = event.path?.split("?")[0].split("/") || [];
|
|
16
|
+
urlParts.pop();
|
|
17
|
+
const locale = urlParts.pop() || "es";
|
|
18
|
+
const options = {};
|
|
19
|
+
const defaultOptions = {
|
|
20
|
+
config: {
|
|
21
|
+
backend: {
|
|
22
|
+
name: "git-gateway",
|
|
23
|
+
branch: "master"
|
|
24
|
+
},
|
|
25
|
+
media_folder: "public/media",
|
|
26
|
+
public_folder: "/media",
|
|
27
|
+
locale,
|
|
28
|
+
local_backend: {
|
|
29
|
+
url: "/api/v1"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
base: "/"
|
|
33
|
+
};
|
|
34
|
+
const { config, base } = defu(options, defaultOptions);
|
|
35
|
+
config.media_folder = base + config.media_folder;
|
|
36
|
+
const projectRoot = process.cwd();
|
|
37
|
+
const cmsDir = resolve(projectRoot, "cms");
|
|
38
|
+
let loadedCollections = [];
|
|
39
|
+
try {
|
|
40
|
+
if (fs.existsSync(cmsDir)) {
|
|
41
|
+
const jiti = createJiti(resolve(projectRoot, "cms", "dummy.ts"), { interopDefault: true, fsCache: false, requireCache: false });
|
|
42
|
+
const walkSync = (dir, filelist = []) => {
|
|
43
|
+
const files = fs.readdirSync(dir);
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const filepath = resolve(dir, file);
|
|
46
|
+
const stat = fs.statSync(filepath);
|
|
47
|
+
if (stat.isDirectory()) {
|
|
48
|
+
filelist = walkSync(filepath, filelist);
|
|
49
|
+
} else if (file.endsWith(".ts")) {
|
|
50
|
+
filelist.push(filepath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return filelist;
|
|
54
|
+
};
|
|
55
|
+
const cmsFiles = walkSync(cmsDir);
|
|
56
|
+
for (const filePath of cmsFiles) {
|
|
57
|
+
const mod = jiti(filePath);
|
|
58
|
+
const colFn = mod.default || mod;
|
|
59
|
+
if (typeof colFn === "function") {
|
|
60
|
+
loadedCollections.push(colFn);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error("[CMS CONFIG] Error loading collections dynamically:", e);
|
|
66
|
+
}
|
|
67
|
+
const resolvedCollections = loadedCollections.map((c) => c({ widgets, collections }));
|
|
68
|
+
const manifest = defineCms(config, resolvedCollections)(`${base}content/${locale}/`, { rootDir: base });
|
|
69
|
+
const ymlConfig = yml.dump(manifest, { skipInvalid: true });
|
|
70
|
+
setResponseHeader(event, "Content-Type", "text/yml;charset=utf-8");
|
|
71
|
+
return ymlConfig;
|
|
72
|
+
});
|