nuxt-studio 1.0.0-alpha.0 → 1.0.0-alpha.2
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 +11 -9
- package/dist/app/en-CeGQDKH5.js +7 -0
- package/dist/app/en-D0jd_r21.js +7 -0
- package/dist/app/en-XbfkDcMn.js +7 -0
- package/dist/app/fr-CbcFrmHX.js +7 -0
- package/dist/app/fr-Ct4HO5e1.js +7 -0
- package/dist/app/fr-ILHmVnG9.js +7 -0
- package/dist/app/main.d.ts +83 -33
- package/dist/app/main.js +34187 -30512
- package/dist/app/service-worker.js +29 -5
- package/dist/app/shared-B6H2zqDG.js +8 -0
- package/dist/app/{shared-D04791PA.js → shared-DLETHbvn.js} +2311 -2307
- package/dist/app/{shared-DI6F-ifc.js → shared-Dvm_mWyG.js} +3120 -3132
- package/dist/app/shared.d.ts +1 -20
- package/dist/app/shared.js +2 -4
- package/dist/module/module.d.mts +70 -27
- package/dist/module/module.json +1 -1
- package/dist/module/module.mjs +101 -58
- package/dist/module/runtime/host.dev.js +19 -12
- package/dist/module/runtime/host.js +124 -61
- package/dist/module/runtime/plugins/studio.client.js +1 -1
- package/dist/module/runtime/server/routes/admin.js +11 -2
- package/dist/module/runtime/server/routes/auth/github.get.d.ts +4 -4
- package/dist/module/runtime/server/routes/auth/github.get.js +17 -9
- package/dist/module/runtime/server/routes/auth/gitlab.get.d.ts +55 -0
- package/dist/module/runtime/server/routes/auth/gitlab.get.js +157 -0
- package/dist/module/runtime/server/routes/auth/session.delete.js +2 -1
- package/dist/module/runtime/server/routes/auth/session.get.js +4 -1
- package/dist/module/runtime/server/routes/dev/content/[...path].js +0 -11
- package/dist/module/runtime/server/routes/dev/public/[...path].d.ts +1 -0
- package/dist/module/runtime/server/routes/dev/public/[...path].js +2 -0
- package/dist/module/runtime/types/content.d.ts +6 -0
- package/dist/module/runtime/types/content.js +7 -0
- package/dist/module/runtime/utils/activation.js +16 -5
- package/dist/module/runtime/utils/collection.d.ts +10 -17
- package/dist/module/runtime/utils/collection.js +32 -92
- package/dist/module/runtime/utils/document.d.ts +15 -0
- package/dist/module/runtime/utils/document.js +288 -0
- package/dist/module/runtime/utils/media.d.ts +1 -0
- package/dist/module/runtime/utils/media.js +5 -0
- package/dist/module/runtime/utils/object.d.ts +4 -0
- package/dist/module/runtime/utils/object.js +27 -0
- package/dist/module/runtime/utils/source.d.ts +11 -0
- package/dist/module/runtime/utils/source.js +36 -0
- package/license.md +9 -0
- package/package.json +7 -6
- package/dist/app/shared-R5zYJ3Dl.js +0 -25803
- package/dist/app/utils-DI6F-ifc.js +0 -25774
- package/dist/app/utils.d.ts +0 -22
- package/dist/app/utils.js +0 -6
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export var ContentFileExtension = /* @__PURE__ */ ((ContentFileExtension2) => {
|
|
2
|
+
ContentFileExtension2["Markdown"] = "md";
|
|
3
|
+
ContentFileExtension2["YAML"] = "yaml";
|
|
4
|
+
ContentFileExtension2["YML"] = "yml";
|
|
5
|
+
ContentFileExtension2["JSON"] = "json";
|
|
6
|
+
return ContentFileExtension2;
|
|
7
|
+
})(ContentFileExtension || {});
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import { getAppManifest, useState, useRuntimeConfig } from "#imports";
|
|
1
|
+
import { getAppManifest, useState, useRuntimeConfig, useCookie } from "#imports";
|
|
2
2
|
export async function defineStudioActivationPlugin(onStudioActivation) {
|
|
3
3
|
const user = useState("studio-session", () => null);
|
|
4
4
|
const config = useRuntimeConfig().public.studio;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const cookie = useCookie("studio-session-check");
|
|
6
|
+
if (config.dev) {
|
|
7
|
+
return await onStudioActivation({
|
|
8
|
+
provider: "github",
|
|
9
|
+
email: "dev@nuxt.com",
|
|
10
|
+
name: "Dev",
|
|
11
|
+
accessToken: "",
|
|
12
|
+
providerId: "",
|
|
13
|
+
avatar: ""
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
user.value = String(cookie.value) === "true" ? await $fetch("/__nuxt_studio/auth/session").then((session) => session?.user ?? null) : null;
|
|
8
17
|
let mounted = false;
|
|
9
18
|
if (user.value?.email) {
|
|
10
19
|
const manifest = await getAppManifest();
|
|
@@ -16,7 +25,9 @@ export async function defineStudioActivationPlugin(onStudioActivation) {
|
|
|
16
25
|
} else {
|
|
17
26
|
document.addEventListener("keydown", (event) => {
|
|
18
27
|
if (event.metaKey && event.key === ".") {
|
|
19
|
-
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
window.location.href = config.route + "?redirect=" + encodeURIComponent(window.location.pathname);
|
|
30
|
+
});
|
|
20
31
|
}
|
|
21
32
|
});
|
|
22
33
|
}
|
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
import type { CollectionInfo,
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { CollectionInfo, Draft07, ResolvedCollectionSource } from '@nuxt/content';
|
|
2
|
+
/**
|
|
3
|
+
* Generation methods
|
|
4
|
+
*/
|
|
4
5
|
export declare function generateStemFromFsPath(path: string): string;
|
|
5
6
|
export declare function generateIdFromFsPath(path: string, collectionInfo: CollectionInfo): string;
|
|
7
|
+
export declare function generateFsPathFromId(id: string, source: ResolvedCollectionSource): string;
|
|
8
|
+
/**
|
|
9
|
+
* Utils methods
|
|
10
|
+
*/
|
|
6
11
|
export declare function getOrderedSchemaKeys(schema: Draft07): string[];
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function
|
|
9
|
-
export declare function generateFsPathFromId(id: string, source: CollectionInfo['source'][0]): string;
|
|
10
|
-
export declare function getCollectionInfo(id: string, collections: Record<string, CollectionInfo>): {
|
|
11
|
-
collection: CollectionInfo;
|
|
12
|
-
source: ResolvedCollectionSource | undefined;
|
|
13
|
-
fsPath: string;
|
|
14
|
-
};
|
|
15
|
-
export declare function parseSourceBase(source: CollectionSource): {
|
|
16
|
-
fixed: string;
|
|
17
|
-
dynamic: string;
|
|
18
|
-
};
|
|
19
|
-
export declare function createCollectionDocument(collection: CollectionInfo, id: string, document: CollectionItemBase): DatabaseItem;
|
|
20
|
-
export declare function normalizeDocument(document: DatabaseItem): DatabaseItem;
|
|
12
|
+
export declare function getCollectionByFilePath(path: string, collections: Record<string, CollectionInfo>): CollectionInfo | undefined;
|
|
13
|
+
export declare function getCollectionById(id: string, collections: Record<string, CollectionInfo>): CollectionInfo;
|
|
21
14
|
export declare function generateRecordInsert(collection: CollectionInfo, data: Record<string, unknown>): string;
|
|
22
15
|
export declare function generateRecordDeletion(collection: CollectionInfo, id: string): string;
|
|
@@ -1,26 +1,8 @@
|
|
|
1
1
|
import { hash } from "ohash";
|
|
2
|
-
import { pathMetaTransform } from "./path-meta.js";
|
|
3
2
|
import { minimatch } from "minimatch";
|
|
4
3
|
import { join, dirname, parse } from "pathe";
|
|
5
|
-
import { withoutLeadingSlash } from "ufo";
|
|
6
|
-
|
|
7
|
-
let matchedSource;
|
|
8
|
-
const collection = Object.values(collections).find((collection2) => {
|
|
9
|
-
if (!collection2.source || collection2.source.length === 0) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
const paths = path === "/" ? ["index.yml", "index.yaml", "index.md", "index.json"] : [path];
|
|
13
|
-
return paths.some((p) => {
|
|
14
|
-
matchedSource = collection2.source.find((source) => {
|
|
15
|
-
const include = minimatch(p, source.include, { dot: true });
|
|
16
|
-
const exclude = source.exclude?.some((exclude2) => minimatch(p, exclude2));
|
|
17
|
-
return include && !exclude;
|
|
18
|
-
});
|
|
19
|
-
return matchedSource;
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
return collection;
|
|
23
|
-
};
|
|
4
|
+
import { withoutLeadingSlash, withoutTrailingSlash } from "ufo";
|
|
5
|
+
import { parseSourceBase } from "./source.js";
|
|
24
6
|
export function generateStemFromFsPath(path) {
|
|
25
7
|
return withoutLeadingSlash(join(dirname(path), parse(path).name));
|
|
26
8
|
}
|
|
@@ -29,6 +11,16 @@ export function generateIdFromFsPath(path, collectionInfo) {
|
|
|
29
11
|
const pathWithoutFixed = path.substring(fixed.length);
|
|
30
12
|
return join(collectionInfo.name, collectionInfo.source[0]?.prefix || "", pathWithoutFixed);
|
|
31
13
|
}
|
|
14
|
+
export function generateFsPathFromId(id, source) {
|
|
15
|
+
const [_, ...rest] = id.split(/[/:]/);
|
|
16
|
+
const path = rest.join("/");
|
|
17
|
+
const { fixed } = parseSourceBase(source);
|
|
18
|
+
const normalizedFixed = withoutTrailingSlash(fixed);
|
|
19
|
+
if (normalizedFixed && path.startsWith(normalizedFixed)) {
|
|
20
|
+
return path;
|
|
21
|
+
}
|
|
22
|
+
return join(fixed, path);
|
|
23
|
+
}
|
|
32
24
|
export function getOrderedSchemaKeys(schema) {
|
|
33
25
|
const shape = Object.values(schema.definitions)[0]?.properties || {};
|
|
34
26
|
const keys = new Set([
|
|
@@ -38,84 +30,32 @@ export function getOrderedSchemaKeys(schema) {
|
|
|
38
30
|
].filter(Boolean));
|
|
39
31
|
return Array.from(keys);
|
|
40
32
|
}
|
|
41
|
-
export function
|
|
33
|
+
export function getCollectionByFilePath(path, collections) {
|
|
34
|
+
let matchedSource;
|
|
35
|
+
const collection = Object.values(collections).find((collection2) => {
|
|
36
|
+
if (!collection2.source || collection2.source.length === 0) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const paths = path === "/" ? ["index.yml", "index.yaml", "index.md", "index.json"] : [path];
|
|
40
|
+
return paths.some((p) => {
|
|
41
|
+
matchedSource = collection2.source.find((source) => {
|
|
42
|
+
const include = minimatch(p, source.include, { dot: true });
|
|
43
|
+
const exclude = source.exclude?.some((exclude2) => minimatch(p, exclude2));
|
|
44
|
+
return include && !exclude;
|
|
45
|
+
});
|
|
46
|
+
return matchedSource;
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return collection;
|
|
50
|
+
}
|
|
51
|
+
export function getCollectionById(id, collections) {
|
|
52
|
+
const collectionName = id.split(/[/:]/)[0];
|
|
42
53
|
const collection = collections[collectionName];
|
|
43
54
|
if (!collection) {
|
|
44
55
|
throw new Error(`Collection ${collectionName} not found`);
|
|
45
56
|
}
|
|
46
57
|
return collection;
|
|
47
58
|
}
|
|
48
|
-
export function getCollectionSource(id, collection) {
|
|
49
|
-
const [_, ...rest] = id.split(/[/:]/);
|
|
50
|
-
const path = rest.join("/");
|
|
51
|
-
const matchedSource = collection.source.find((source) => {
|
|
52
|
-
const include = minimatch(path, source.include, { dot: true });
|
|
53
|
-
const exclude = source.exclude?.some((exclude2) => minimatch(path, exclude2));
|
|
54
|
-
return include && !exclude;
|
|
55
|
-
});
|
|
56
|
-
return matchedSource;
|
|
57
|
-
}
|
|
58
|
-
export function generateFsPathFromId(id, source) {
|
|
59
|
-
const [_, ...rest] = id.split(/[/:]/);
|
|
60
|
-
const path = rest.join("/");
|
|
61
|
-
const { fixed } = parseSourceBase(source);
|
|
62
|
-
const pathWithoutFixed = path.substring(fixed.length);
|
|
63
|
-
return join(fixed, pathWithoutFixed);
|
|
64
|
-
}
|
|
65
|
-
export function getCollectionInfo(id, collections) {
|
|
66
|
-
const collection = getCollection(id.split(/[/:]/)[0], collections);
|
|
67
|
-
const source = getCollectionSource(id, collection);
|
|
68
|
-
const fsPath = generateFsPathFromId(id, source);
|
|
69
|
-
return {
|
|
70
|
-
collection,
|
|
71
|
-
source,
|
|
72
|
-
fsPath
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
export function parseSourceBase(source) {
|
|
76
|
-
const [fixPart, ...rest] = source.include.includes("*") ? source.include.split("*") : ["", source.include];
|
|
77
|
-
return {
|
|
78
|
-
fixed: fixPart || "",
|
|
79
|
-
dynamic: "*" + rest.join("*")
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
export function createCollectionDocument(collection, id, document) {
|
|
83
|
-
const parsedContent = [
|
|
84
|
-
pathMetaTransform
|
|
85
|
-
].reduce((acc, fn) => collection.type === "page" ? fn(acc) : acc, { ...document, id });
|
|
86
|
-
const result = { id };
|
|
87
|
-
const meta = parsedContent.meta;
|
|
88
|
-
const collectionKeys = getOrderedSchemaKeys(collection.schema);
|
|
89
|
-
for (const key of Object.keys(parsedContent)) {
|
|
90
|
-
if (collectionKeys.includes(key)) {
|
|
91
|
-
result[key] = parsedContent[key];
|
|
92
|
-
} else {
|
|
93
|
-
meta[key] = parsedContent[key];
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
result.meta = meta;
|
|
97
|
-
if (collectionKeys.includes("seo")) {
|
|
98
|
-
const seo = result.seo = result.seo || {};
|
|
99
|
-
seo.title = seo.title || result.title;
|
|
100
|
-
seo.description = seo.description || result.description;
|
|
101
|
-
}
|
|
102
|
-
return result;
|
|
103
|
-
}
|
|
104
|
-
export function normalizeDocument(document) {
|
|
105
|
-
if (document?.seo) {
|
|
106
|
-
const seo = document.seo;
|
|
107
|
-
if (!seo.title || seo.title === document.title) {
|
|
108
|
-
Reflect.deleteProperty(document.seo, "title");
|
|
109
|
-
}
|
|
110
|
-
if (!seo.description || seo.description === document.description) {
|
|
111
|
-
Reflect.deleteProperty(document.seo, "description");
|
|
112
|
-
}
|
|
113
|
-
if (Object.keys(seo).length === 0) {
|
|
114
|
-
Reflect.deleteProperty(document, "seo");
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return document;
|
|
118
|
-
}
|
|
119
59
|
function computeValuesBasedOnCollectionSchema(collection, data) {
|
|
120
60
|
const fields = [];
|
|
121
61
|
const values = [];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CollectionInfo, CollectionItemBase } from '@nuxt/content';
|
|
2
|
+
import type { DatabaseItem, DatabasePageItem } from 'nuxt-studio/app';
|
|
3
|
+
export declare function normalizeDocument(id: string, collectionInfo: CollectionInfo, document: CollectionItemBase): DatabaseItem;
|
|
4
|
+
export declare function pickReservedKeysFromDocument(document: DatabaseItem): DatabaseItem;
|
|
5
|
+
export declare function removeReservedKeysFromDocument(document: DatabaseItem): DatabaseItem;
|
|
6
|
+
export declare function isDocumentMatchingContent(content: string, document: DatabaseItem): Promise<boolean>;
|
|
7
|
+
export declare function areDocumentsEqual(document1: Record<string, unknown>, document2: Record<string, unknown>): boolean;
|
|
8
|
+
export declare function generateDocumentFromContent(id: string, content: string): Promise<DatabaseItem | null>;
|
|
9
|
+
export declare function generateDocumentFromYAMLContent(id: string, content: string): Promise<DatabaseItem>;
|
|
10
|
+
export declare function generateDocumentFromJSONContent(id: string, content: string): Promise<DatabaseItem>;
|
|
11
|
+
export declare function generateDocumentFromMarkdownContent(id: string, content: string): Promise<DatabaseItem>;
|
|
12
|
+
export declare function generateContentFromDocument(document: DatabaseItem): Promise<string | null>;
|
|
13
|
+
export declare function generateContentFromYAMLDocument(document: DatabaseItem): Promise<string | null>;
|
|
14
|
+
export declare function generateContentFromJSONDocument(document: DatabaseItem): Promise<string | null>;
|
|
15
|
+
export declare function generateContentFromMarkdownDocument(document: DatabasePageItem): Promise<string | null>;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { getOrderedSchemaKeys } from "./collection.js";
|
|
2
|
+
import { pathMetaTransform } from "./path-meta.js";
|
|
3
|
+
import { doObjectsMatch, omit, pick } from "./object.js";
|
|
4
|
+
import { ContentFileExtension } from "../types/content.js";
|
|
5
|
+
import { parseMarkdown } from "@nuxtjs/mdc/runtime/parser/index";
|
|
6
|
+
import { visit } from "unist-util-visit";
|
|
7
|
+
import { compressTree, decompressTree } from "@nuxt/content/runtime";
|
|
8
|
+
import destr from "destr";
|
|
9
|
+
import { parseFrontMatter, stringifyFrontMatter } from "remark-mdc";
|
|
10
|
+
import { stringify } from "minimark/stringify";
|
|
11
|
+
import { stringifyMarkdown } from "@nuxtjs/mdc/runtime";
|
|
12
|
+
const reservedKeys = ["id", "fsPath", "stem", "extension", "__hash__", "path", "body", "meta", "rawbody"];
|
|
13
|
+
export function normalizeDocument(id, collectionInfo, document) {
|
|
14
|
+
const parsedContent = [
|
|
15
|
+
pathMetaTransform
|
|
16
|
+
].reduce((acc, fn) => collectionInfo.type === "page" ? fn(acc) : acc, { ...document, id });
|
|
17
|
+
const result = { id };
|
|
18
|
+
const meta = parsedContent.meta;
|
|
19
|
+
const collectionKeys = getOrderedSchemaKeys(collectionInfo.schema);
|
|
20
|
+
for (const key of Object.keys(parsedContent)) {
|
|
21
|
+
if (collectionKeys.includes(key)) {
|
|
22
|
+
result[key] = parsedContent[key];
|
|
23
|
+
} else {
|
|
24
|
+
meta[key] = parsedContent[key];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (meta.fsPath) {
|
|
28
|
+
Reflect.deleteProperty(meta, "fsPath");
|
|
29
|
+
}
|
|
30
|
+
result.meta = meta;
|
|
31
|
+
if (collectionKeys.includes("seo")) {
|
|
32
|
+
const seo = result.seo = result.seo || {};
|
|
33
|
+
seo.title = seo.title || result.title;
|
|
34
|
+
seo.description = seo.description || result.description;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
export function pickReservedKeysFromDocument(document) {
|
|
39
|
+
return pick(document, reservedKeys);
|
|
40
|
+
}
|
|
41
|
+
export function removeReservedKeysFromDocument(document) {
|
|
42
|
+
const result = omit(document, reservedKeys);
|
|
43
|
+
if (result.navigation === true) {
|
|
44
|
+
Reflect.deleteProperty(result, "navigation");
|
|
45
|
+
}
|
|
46
|
+
if (document.seo) {
|
|
47
|
+
const seo = document.seo;
|
|
48
|
+
if ((!seo.title || seo.title === document.title) && (!seo.description || seo.description === document.description)) {
|
|
49
|
+
Reflect.deleteProperty(result, "seo");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!document.title) {
|
|
53
|
+
Reflect.deleteProperty(result, "title");
|
|
54
|
+
}
|
|
55
|
+
if (!document.description) {
|
|
56
|
+
Reflect.deleteProperty(result, "description");
|
|
57
|
+
}
|
|
58
|
+
for (const key in document.meta || {}) {
|
|
59
|
+
if (key !== "__hash__") {
|
|
60
|
+
result[key] = document.meta[key];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const key in result || {}) {
|
|
64
|
+
if (result[key] === null) {
|
|
65
|
+
Reflect.deleteProperty(result, key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
export async function isDocumentMatchingContent(content, document) {
|
|
71
|
+
const generatedDocument = await generateDocumentFromContent(document.id, content);
|
|
72
|
+
if (generatedDocument.extension === ContentFileExtension.Markdown) {
|
|
73
|
+
const { body: generatedBody, ...generatedDocumentData } = generatedDocument;
|
|
74
|
+
const { body: documentBody, ...documentData } = document;
|
|
75
|
+
if (stringify(withoutLastStyles(generatedBody)) !== stringify(withoutLastStyles(documentBody))) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return doObjectsMatch(generatedDocumentData, documentData);
|
|
79
|
+
}
|
|
80
|
+
return doObjectsMatch(generatedDocument, document);
|
|
81
|
+
}
|
|
82
|
+
export function areDocumentsEqual(document1, document2) {
|
|
83
|
+
const { body: body1, meta: meta1, ...documentData1 } = document1;
|
|
84
|
+
const { body: body2, meta: meta2, ...documentData2 } = document2;
|
|
85
|
+
if (document1.extension === ContentFileExtension.Markdown) {
|
|
86
|
+
const minifiedBody1 = withoutLastStyles(
|
|
87
|
+
document1.body.type === "minimark" ? document1.body : compressTree(document1.body)
|
|
88
|
+
);
|
|
89
|
+
const minifiedBody2 = withoutLastStyles(
|
|
90
|
+
document2.body.type === "minimark" ? document2.body : compressTree(document2.body)
|
|
91
|
+
);
|
|
92
|
+
if (stringify(minifiedBody1) !== stringify(minifiedBody2)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
} else if (typeof body1 === "object" && typeof body2 === "object") {
|
|
96
|
+
if (!doObjectsMatch(body1, body2)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
if (JSON.stringify(body1) !== JSON.stringify(body2)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function refineDocumentData(doc) {
|
|
105
|
+
if (doc.seo) {
|
|
106
|
+
const seo = doc.seo;
|
|
107
|
+
doc.seo = {
|
|
108
|
+
...seo,
|
|
109
|
+
title: seo.title || doc.title,
|
|
110
|
+
description: seo.description || doc.description
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
Reflect.deleteProperty(doc, "__hash__");
|
|
114
|
+
Reflect.deleteProperty(doc, "path");
|
|
115
|
+
if (typeof doc.navigation === "undefined") {
|
|
116
|
+
doc.navigation = true;
|
|
117
|
+
}
|
|
118
|
+
for (const key in doc) {
|
|
119
|
+
const value = doc[key];
|
|
120
|
+
if (typeof value === "string" && !Number.isNaN(Date.parse(value))) {
|
|
121
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
|
|
122
|
+
doc[key] = new Date(value).toISOString().split("T")[0];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function removeNullAndUndefined(obj) {
|
|
127
|
+
const result = {};
|
|
128
|
+
for (const key in obj) {
|
|
129
|
+
const value = obj[key];
|
|
130
|
+
if (value === null || value === void 0) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date)) {
|
|
134
|
+
result[key] = removeNullAndUndefined(value);
|
|
135
|
+
} else {
|
|
136
|
+
result[key] = value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
return removeNullAndUndefined(doc);
|
|
142
|
+
}
|
|
143
|
+
const data1 = refineDocumentData({ ...documentData1, ...meta1 || {} });
|
|
144
|
+
const data2 = refineDocumentData({ ...documentData2, ...meta2 || {} });
|
|
145
|
+
if (!doObjectsMatch(data1, data2)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
export async function generateDocumentFromContent(id, content) {
|
|
151
|
+
const [_id, _hash] = id.split("#");
|
|
152
|
+
const extension = getFileExtension(id);
|
|
153
|
+
if (extension === ContentFileExtension.Markdown) {
|
|
154
|
+
return await generateDocumentFromMarkdownContent(id, content);
|
|
155
|
+
}
|
|
156
|
+
if (extension === ContentFileExtension.YAML || extension === ContentFileExtension.YML) {
|
|
157
|
+
return await generateDocumentFromYAMLContent(id, content);
|
|
158
|
+
}
|
|
159
|
+
if (extension === ContentFileExtension.JSON) {
|
|
160
|
+
return await generateDocumentFromJSONContent(id, content);
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
export async function generateDocumentFromYAMLContent(id, content) {
|
|
165
|
+
const { data } = parseFrontMatter(`---
|
|
166
|
+
${content}
|
|
167
|
+
---`);
|
|
168
|
+
let parsed = data;
|
|
169
|
+
if (Array.isArray(data)) {
|
|
170
|
+
console.warn(`YAML array is not supported in ${id}, moving the array into the \`body\` key`);
|
|
171
|
+
parsed = { body: data };
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
id,
|
|
175
|
+
extension: getFileExtension(id),
|
|
176
|
+
stem: generateStemFromId(id),
|
|
177
|
+
meta: {},
|
|
178
|
+
...parsed,
|
|
179
|
+
body: parsed.body || parsed
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
export async function generateDocumentFromJSONContent(id, content) {
|
|
183
|
+
let parsed = destr(content);
|
|
184
|
+
if (Array.isArray(parsed)) {
|
|
185
|
+
console.warn(`JSON array is not supported in ${id}, moving the array into the \`body\` key`);
|
|
186
|
+
parsed = {
|
|
187
|
+
body: parsed
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
id,
|
|
192
|
+
extension: ContentFileExtension.JSON,
|
|
193
|
+
stem: generateStemFromId(id),
|
|
194
|
+
meta: {},
|
|
195
|
+
...parsed,
|
|
196
|
+
body: parsed.body || parsed
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
export async function generateDocumentFromMarkdownContent(id, content) {
|
|
200
|
+
const document = await parseMarkdown(content, {
|
|
201
|
+
remark: {
|
|
202
|
+
plugins: {
|
|
203
|
+
"remark-mdc": {
|
|
204
|
+
options: {
|
|
205
|
+
autoUnwrap: true
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
visit(document.body, (node) => node.type === "element" && node.tag === "a", (node) => {
|
|
212
|
+
if (node.props?.rel?.join(" ") === "nofollow") {
|
|
213
|
+
Reflect.deleteProperty(node.props, "rel");
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
const body = document.body.type === "root" ? compressTree(document.body) : document.body;
|
|
217
|
+
const result = {
|
|
218
|
+
id,
|
|
219
|
+
meta: {},
|
|
220
|
+
extension: "md",
|
|
221
|
+
stem: id.split("/").slice(1).join("/").split(".").slice(0, -1).join("."),
|
|
222
|
+
body: {
|
|
223
|
+
...body,
|
|
224
|
+
toc: document.toc
|
|
225
|
+
},
|
|
226
|
+
...document.data
|
|
227
|
+
};
|
|
228
|
+
return pathMetaTransform(result);
|
|
229
|
+
}
|
|
230
|
+
export async function generateContentFromDocument(document) {
|
|
231
|
+
const [id, _hash] = document.id.split("#");
|
|
232
|
+
const extension = getFileExtension(id);
|
|
233
|
+
if (extension === ContentFileExtension.Markdown) {
|
|
234
|
+
return await generateContentFromMarkdownDocument(document);
|
|
235
|
+
}
|
|
236
|
+
if (extension === ContentFileExtension.YAML || extension === ContentFileExtension.YML) {
|
|
237
|
+
return await generateContentFromYAMLDocument(document);
|
|
238
|
+
}
|
|
239
|
+
if (extension === ContentFileExtension.JSON) {
|
|
240
|
+
return await generateContentFromJSONDocument(document);
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
export async function generateContentFromYAMLDocument(document) {
|
|
245
|
+
return await stringifyFrontMatter(removeReservedKeysFromDocument(document), "", {
|
|
246
|
+
prefix: "",
|
|
247
|
+
suffix: "",
|
|
248
|
+
lineWidth: 0
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
export async function generateContentFromJSONDocument(document) {
|
|
252
|
+
return JSON.stringify(removeReservedKeysFromDocument(document), null, 2);
|
|
253
|
+
}
|
|
254
|
+
export async function generateContentFromMarkdownDocument(document) {
|
|
255
|
+
const body = document.body.type === "minimark" ? decompressTree(document.body) : document.body;
|
|
256
|
+
visit(body, (node) => node.type === "element" && node.tag === "a", (node) => {
|
|
257
|
+
if (node.props?.rel?.join(" ") === "nofollow") {
|
|
258
|
+
Reflect.deleteProperty(node.props, "rel");
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
const markdown = await stringifyMarkdown(body, removeReservedKeysFromDocument(document), {
|
|
262
|
+
frontMatter: {
|
|
263
|
+
options: {
|
|
264
|
+
lineWidth: 0
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
plugins: {
|
|
268
|
+
remarkMDC: {
|
|
269
|
+
options: {
|
|
270
|
+
autoUnwrap: true
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
return typeof markdown === "string" ? markdown.replace(/*/g, "*") : markdown;
|
|
276
|
+
}
|
|
277
|
+
function generateStemFromId(id) {
|
|
278
|
+
return id.split("/").slice(1).join("/").split(".").slice(0, -1).join(".");
|
|
279
|
+
}
|
|
280
|
+
function getFileExtension(id) {
|
|
281
|
+
return id.split("#")[0]?.split(".").pop().toLowerCase();
|
|
282
|
+
}
|
|
283
|
+
function withoutLastStyles(body) {
|
|
284
|
+
if (body.value[body.value.length - 1]?.[0] === "style") {
|
|
285
|
+
return { ...body, value: body.value.slice(0, -1) };
|
|
286
|
+
}
|
|
287
|
+
return body;
|
|
288
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateIdFromFsPath(fsPath: string): string;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export declare const omit: (obj: Record<string, unknown>, keys: string | string[]) => {
|
|
2
2
|
[k: string]: unknown;
|
|
3
3
|
};
|
|
4
|
+
export declare const pick: (obj: Record<string, unknown>, keys: string | string[]) => {
|
|
5
|
+
[k: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
export declare function doObjectsMatch(base: Record<string, unknown>, target: Record<string, unknown>): boolean;
|
|
@@ -1,3 +1,30 @@
|
|
|
1
1
|
export const omit = (obj, keys) => {
|
|
2
2
|
return Object.fromEntries(Object.entries(obj).filter(([key]) => !keys.includes(key)));
|
|
3
3
|
};
|
|
4
|
+
export const pick = (obj, keys) => {
|
|
5
|
+
return Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key)));
|
|
6
|
+
};
|
|
7
|
+
export function doObjectsMatch(base, target) {
|
|
8
|
+
if (typeof base !== "object" || typeof target !== "object") {
|
|
9
|
+
return base === target;
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(base) && Array.isArray(target)) {
|
|
12
|
+
if (base.length !== target.length) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
for (let index = 0; index < base.length; index++) {
|
|
16
|
+
const item = base[index];
|
|
17
|
+
const targetItem = target[index];
|
|
18
|
+
if (!doObjectsMatch(item, targetItem)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
for (const key in base) {
|
|
25
|
+
if (!doObjectsMatch(base[key], target[key])) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CollectionSource, ResolvedCollectionSource } from '@nuxt/content';
|
|
2
|
+
export declare function parseSourceBase(source: CollectionSource): {
|
|
3
|
+
fixed: string;
|
|
4
|
+
dynamic: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* On Nuxt Content, Id is built like this: {collection.name}/{source.prefix}/{path}
|
|
8
|
+
* But 'source.prefix' can be different from the fixed part of 'source.include'
|
|
9
|
+
* We need to remove the 'source.prefix' from the path and add the fixed part of the 'source.include' to get the fsPath (used to match the source)
|
|
10
|
+
*/
|
|
11
|
+
export declare function getCollectionSourceById(id: string, sources: ResolvedCollectionSource[]): ResolvedCollectionSource | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { withLeadingSlash, withoutLeadingSlash, withoutTrailingSlash } from "ufo";
|
|
2
|
+
import { join } from "pathe";
|
|
3
|
+
import { minimatch } from "minimatch";
|
|
4
|
+
export function parseSourceBase(source) {
|
|
5
|
+
const [fixPart, ...rest] = source.include.includes("*") ? source.include.split("*") : ["", source.include];
|
|
6
|
+
return {
|
|
7
|
+
fixed: fixPart || "",
|
|
8
|
+
dynamic: "*" + rest.join("*")
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function getCollectionSourceById(id, sources) {
|
|
12
|
+
const [_, ...rest] = id.split(/[/:]/);
|
|
13
|
+
const prefixAndPath = rest.join("/");
|
|
14
|
+
const matchedSource = sources.find((source) => {
|
|
15
|
+
const prefix = source.prefix;
|
|
16
|
+
if (!prefix) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (!withLeadingSlash(prefixAndPath).startsWith(prefix)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
let fsPath;
|
|
23
|
+
const [fixPart] = source.include.includes("*") ? source.include.split("*") : ["", source.include];
|
|
24
|
+
const fixed = withoutTrailingSlash(fixPart || "/");
|
|
25
|
+
if (withoutLeadingSlash(fixed) === withoutLeadingSlash(prefix)) {
|
|
26
|
+
fsPath = prefixAndPath;
|
|
27
|
+
} else {
|
|
28
|
+
const path = prefixAndPath.replace(prefix, "");
|
|
29
|
+
fsPath = join(fixed, path);
|
|
30
|
+
}
|
|
31
|
+
const include = minimatch(fsPath, source.include, { dot: true });
|
|
32
|
+
const exclude = source.exclude?.some((exclude2) => minimatch(fsPath, exclude2));
|
|
33
|
+
return include && !exclude;
|
|
34
|
+
});
|
|
35
|
+
return matchedSource;
|
|
36
|
+
}
|
package/license.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vercel, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|