@studiocms/devapps 0.1.0-beta.10
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/LICENSE +21 -0
- package/README.md +92 -0
- package/assets/preview-embeddedapp.png +0 -0
- package/assets/preview-page.png +0 -0
- package/assets/preview-toolbar.png +0 -0
- package/assets/wp-importer.png +0 -0
- package/dist/apps/libsql-viewer.d.ts +46 -0
- package/dist/apps/libsql-viewer.js +95 -0
- package/dist/apps/wp-importer.d.ts +42 -0
- package/dist/apps/wp-importer.js +214 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +66 -0
- package/dist/routes/wp-importer.d.ts +27 -0
- package/dist/routes/wp-importer.js +57 -0
- package/dist/schema/index.d.ts +152 -0
- package/dist/schema/index.js +45 -0
- package/dist/schema/wp-api.d.ts +439 -0
- package/dist/schema/wp-api.js +72 -0
- package/dist/utils/app-utils.d.ts +21 -0
- package/dist/utils/app-utils.js +37 -0
- package/dist/utils/pathGenerator.d.ts +10 -0
- package/dist/utils/pathGenerator.js +37 -0
- package/dist/utils/wp-api/converters.d.ts +74 -0
- package/dist/utils/wp-api/converters.js +181 -0
- package/dist/utils/wp-api/index.d.ts +6 -0
- package/dist/utils/wp-api/index.js +3 -0
- package/dist/utils/wp-api/pages.d.ts +13 -0
- package/dist/utils/wp-api/pages.js +38 -0
- package/dist/utils/wp-api/posts.d.ts +10 -0
- package/dist/utils/wp-api/posts.js +38 -0
- package/dist/utils/wp-api/settings.d.ts +18 -0
- package/dist/utils/wp-api/settings.js +45 -0
- package/dist/utils/wp-api/utils.d.ts +66 -0
- package/dist/utils/wp-api/utils.js +138 -0
- package/dist/virt.d.js +0 -0
- package/dist/virt.d.ts +13 -0
- package/package.json +83 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "astro/zod";
|
|
2
|
+
const OpenClosedSchema = z.enum(["open", "closed", ""]);
|
|
3
|
+
const PageSchema = z.object({
|
|
4
|
+
id: z.number(),
|
|
5
|
+
date: z.coerce.date(),
|
|
6
|
+
date_gmt: z.coerce.date(),
|
|
7
|
+
guid: z.object({ rendered: z.string() }),
|
|
8
|
+
modified: z.coerce.date(),
|
|
9
|
+
modified_gmt: z.coerce.date(),
|
|
10
|
+
slug: z.string(),
|
|
11
|
+
status: z.enum(["publish", "future", "draft", "pending", "private"]),
|
|
12
|
+
type: z.string(),
|
|
13
|
+
title: z.object({ rendered: z.string() }),
|
|
14
|
+
content: z.object({ rendered: z.string(), protected: z.boolean() }),
|
|
15
|
+
excerpt: z.object({ rendered: z.string(), protected: z.boolean() }),
|
|
16
|
+
author: z.number(),
|
|
17
|
+
featured_media: z.number(),
|
|
18
|
+
parent: z.number(),
|
|
19
|
+
menu_order: z.number(),
|
|
20
|
+
comment_status: OpenClosedSchema,
|
|
21
|
+
ping_status: OpenClosedSchema,
|
|
22
|
+
template: z.string(),
|
|
23
|
+
meta: z.array(z.any().or(z.record(z.any())))
|
|
24
|
+
});
|
|
25
|
+
const PostSchema = PageSchema.extend({
|
|
26
|
+
format: z.enum([
|
|
27
|
+
"standard",
|
|
28
|
+
"aside",
|
|
29
|
+
"chat",
|
|
30
|
+
"gallery",
|
|
31
|
+
"link",
|
|
32
|
+
"image",
|
|
33
|
+
"quote",
|
|
34
|
+
"status",
|
|
35
|
+
"video",
|
|
36
|
+
"audio",
|
|
37
|
+
""
|
|
38
|
+
]),
|
|
39
|
+
categories: z.array(z.number()),
|
|
40
|
+
tags: z.array(z.number())
|
|
41
|
+
});
|
|
42
|
+
const TagSchema = z.object({
|
|
43
|
+
id: z.number(),
|
|
44
|
+
count: z.number(),
|
|
45
|
+
description: z.string(),
|
|
46
|
+
link: z.string().url(),
|
|
47
|
+
name: z.string(),
|
|
48
|
+
slug: z.string(),
|
|
49
|
+
taxonomy: z.string(),
|
|
50
|
+
meta: z.array(z.any()).or(z.record(z.any()))
|
|
51
|
+
});
|
|
52
|
+
const CategorySchema = TagSchema.extend({
|
|
53
|
+
parent: z.number()
|
|
54
|
+
});
|
|
55
|
+
const SiteSettingsSchema = z.object({
|
|
56
|
+
name: z.string(),
|
|
57
|
+
description: z.string(),
|
|
58
|
+
url: z.string(),
|
|
59
|
+
home: z.string(),
|
|
60
|
+
gmt_offset: z.coerce.number(),
|
|
61
|
+
timezone_string: z.string(),
|
|
62
|
+
site_logo: z.number().optional(),
|
|
63
|
+
site_icon: z.number().optional(),
|
|
64
|
+
site_icon_url: z.string().optional()
|
|
65
|
+
});
|
|
66
|
+
export {
|
|
67
|
+
CategorySchema,
|
|
68
|
+
PageSchema,
|
|
69
|
+
PostSchema,
|
|
70
|
+
SiteSettingsSchema,
|
|
71
|
+
TagSchema
|
|
72
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a new window element with the specified content.
|
|
3
|
+
*
|
|
4
|
+
* @param content - The HTML content to be inserted into the window element.
|
|
5
|
+
* @returns The newly created window element with the specified content.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createWindowElement(content: string): import("astro/runtime/client/dev-toolbar/ui-library/window.js").DevToolbarWindow;
|
|
8
|
+
/**
|
|
9
|
+
* Attaches an event listener to the specified event target that listens for the 'app-toggled' event.
|
|
10
|
+
* When the 'app-toggled' event is triggered, it adds or removes a click event listener on the document
|
|
11
|
+
* based on the state provided in the event's detail.
|
|
12
|
+
*
|
|
13
|
+
* The click event listener will dispatch a 'toggle-app' event with `state: false` if the click occurs
|
|
14
|
+
* outside of the specified elements or does not pass the additional check.
|
|
15
|
+
*
|
|
16
|
+
* @param eventTarget - The target to which the 'app-toggled' event listener is attached.
|
|
17
|
+
* @param additionalCheck - An optional function that takes an Element and returns a boolean. If provided,
|
|
18
|
+
* the click event listener will not dispatch the 'toggle-app' event if this function
|
|
19
|
+
* returns true for the clicked target.
|
|
20
|
+
*/
|
|
21
|
+
export declare function closeOnOutsideClick(eventTarget: EventTarget, additionalCheck?: (target: Element) => boolean): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function createWindowElement(content) {
|
|
2
|
+
const windowElement = document.createElement("astro-dev-toolbar-window");
|
|
3
|
+
windowElement.innerHTML = content;
|
|
4
|
+
windowElement.placement = "bottom-center";
|
|
5
|
+
return windowElement;
|
|
6
|
+
}
|
|
7
|
+
function closeOnOutsideClick(eventTarget, additionalCheck) {
|
|
8
|
+
const isCustomEvent = (event) => {
|
|
9
|
+
return "detail" in event;
|
|
10
|
+
};
|
|
11
|
+
function onPageClick(event) {
|
|
12
|
+
const target = event.target;
|
|
13
|
+
if (!target) return;
|
|
14
|
+
if (!target.closest) return;
|
|
15
|
+
if (target.closest("astro-dev-toolbar")) return;
|
|
16
|
+
if (additionalCheck?.(target)) return;
|
|
17
|
+
eventTarget.dispatchEvent(
|
|
18
|
+
new CustomEvent("toggle-app", {
|
|
19
|
+
detail: {
|
|
20
|
+
state: false
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
eventTarget.addEventListener("app-toggled", (event) => {
|
|
26
|
+
if (!isCustomEvent(event)) return;
|
|
27
|
+
if (event.detail.state === true) {
|
|
28
|
+
document.addEventListener("click", onPageClick, true);
|
|
29
|
+
} else {
|
|
30
|
+
document.removeEventListener("click", onPageClick, true);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
closeOnOutsideClick,
|
|
36
|
+
createWindowElement
|
|
37
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Get the a root-relative URL path with the site’s `base` prefixed. */
|
|
2
|
+
export declare function pathWithBase(path: string, _base: string): string;
|
|
3
|
+
/** Ensure the passed path does not start with a leading slash. */
|
|
4
|
+
export declare function stripLeadingSlash(href: string): string;
|
|
5
|
+
/** Ensure the passed path does not end with a trailing slash. */
|
|
6
|
+
export declare function stripTrailingSlash(href: string): string;
|
|
7
|
+
/** Ensure the passed path starts with a leading slash. */
|
|
8
|
+
export declare function ensureLeadingSlash(href: string): string;
|
|
9
|
+
/** Endpoint path generator */
|
|
10
|
+
export declare const pathGenerator: (endpointPath: string, _base: string) => (path: string) => string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function base(url) {
|
|
2
|
+
return stripTrailingSlash(url);
|
|
3
|
+
}
|
|
4
|
+
function pathWithBase(path, _base) {
|
|
5
|
+
let newPath = path;
|
|
6
|
+
newPath = stripLeadingSlash(newPath);
|
|
7
|
+
return newPath ? `${base(_base)}/${newPath}` : `${base(_base)}/`;
|
|
8
|
+
}
|
|
9
|
+
function stripLeadingSlash(href) {
|
|
10
|
+
let newHref = href;
|
|
11
|
+
if (newHref[0] === "/") newHref = newHref.slice(1);
|
|
12
|
+
return newHref;
|
|
13
|
+
}
|
|
14
|
+
function stripTrailingSlash(href) {
|
|
15
|
+
let newHref = href;
|
|
16
|
+
if (newHref[newHref.length - 1] === "/") newHref = newHref.slice(0, -1);
|
|
17
|
+
return newHref;
|
|
18
|
+
}
|
|
19
|
+
function ensureLeadingSlash(href) {
|
|
20
|
+
let newHref = href;
|
|
21
|
+
if (newHref[0] !== "/") newHref = `/${newHref}`;
|
|
22
|
+
return newHref;
|
|
23
|
+
}
|
|
24
|
+
const pathGenerator = (endpointPath, _base) => {
|
|
25
|
+
const newEndpointPath = stripTrailingSlash(endpointPath);
|
|
26
|
+
return function pathBuilder(path) {
|
|
27
|
+
const newPath = stripLeadingSlash(path);
|
|
28
|
+
return `${pathWithBase(newEndpointPath, _base)}${ensureLeadingSlash(newPath)}`;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export {
|
|
32
|
+
ensureLeadingSlash,
|
|
33
|
+
pathGenerator,
|
|
34
|
+
pathWithBase,
|
|
35
|
+
stripLeadingSlash,
|
|
36
|
+
stripTrailingSlash
|
|
37
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { PageContent, PageData } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Converts a given page object to a PageData object.
|
|
4
|
+
*
|
|
5
|
+
* @param page - The page object to convert. This is expected to be of an unknown type.
|
|
6
|
+
* @param endpoint - The API endpoint to fetch additional data, such as media.
|
|
7
|
+
* @returns A promise that resolves to a PageData object containing the converted page data.
|
|
8
|
+
*
|
|
9
|
+
* @throws Will throw an error if fetching the title image fails.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ConvertToPageData: (page: unknown, endpoint: string) => Promise<PageData>;
|
|
12
|
+
/**
|
|
13
|
+
* Converts the provided page data and page content into a PageContent object.
|
|
14
|
+
*
|
|
15
|
+
* @param pageData - The data of the page to be converted.
|
|
16
|
+
* @param page - The raw page content to be converted.
|
|
17
|
+
* @returns A promise that resolves to a PageContent object.
|
|
18
|
+
* @throws Will throw an error if the pageData is missing an id.
|
|
19
|
+
*/
|
|
20
|
+
export declare const ConvertToPageContent: (pageData: PageData, page: unknown) => Promise<PageContent>;
|
|
21
|
+
/**
|
|
22
|
+
* Generates and inserts categories into the database if they do not already exist.
|
|
23
|
+
*
|
|
24
|
+
* @param categories - An array of category IDs to be processed.
|
|
25
|
+
* @param endpoint - The API endpoint to fetch category data from.
|
|
26
|
+
* @returns A promise that resolves when the categories have been processed and inserted into the database.
|
|
27
|
+
*
|
|
28
|
+
* This function performs the following steps:
|
|
29
|
+
* 1. Iterates over the provided category IDs.
|
|
30
|
+
* 2. Checks if each category already exists in the database.
|
|
31
|
+
* 3. If a category does not exist, fetches the category data from the specified API endpoint.
|
|
32
|
+
* 4. Collects the new category data.
|
|
33
|
+
* 5. Maps the new category data to the database schema.
|
|
34
|
+
* 6. Inserts the new categories into the database.
|
|
35
|
+
*/
|
|
36
|
+
export declare const generateCategories: (categories: number[], endpoint: string) => Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Generates and inserts tags into the database if they do not already exist.
|
|
39
|
+
*
|
|
40
|
+
* @param {number[]} tags - An array of tag IDs to be processed.
|
|
41
|
+
* @param {string} endpoint - The API endpoint to fetch tag data from.
|
|
42
|
+
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* const tags = [1, 2, 3];
|
|
46
|
+
* const endpoint = 'https://example.com/wp-json/wp/v2';
|
|
47
|
+
* await generateTags(tags, endpoint);
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* This function checks if each tag ID already exists in the database. If a tag does not exist,
|
|
51
|
+
* it fetches the tag data from the specified API endpoint and inserts it into the database.
|
|
52
|
+
* The function logs messages to the console for each tag that is processed.
|
|
53
|
+
*/
|
|
54
|
+
export declare const generateTags: (tags: number[], endpoint: string) => Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Converts a given post object to PageData format.
|
|
57
|
+
*
|
|
58
|
+
* @param post - The post object to be converted.
|
|
59
|
+
* @param useBlogPkg - A boolean indicating whether to use the blog package.
|
|
60
|
+
* @param endpoint - The API endpoint to fetch additional data.
|
|
61
|
+
* @returns A promise that resolves to the converted PageData object.
|
|
62
|
+
*
|
|
63
|
+
* @throws Will throw an error if fetching the title image or downloading the post image fails.
|
|
64
|
+
*/
|
|
65
|
+
export declare const ConvertToPostData: (post: unknown, useBlogPkg: boolean, endpoint: string) => Promise<PageData>;
|
|
66
|
+
/**
|
|
67
|
+
* Converts the given post data to a PageContent object.
|
|
68
|
+
*
|
|
69
|
+
* @param pageData - The data of the page to which the post content belongs.
|
|
70
|
+
* @param post - The post data to be converted.
|
|
71
|
+
* @returns A promise that resolves to a PageContent object.
|
|
72
|
+
* @throws Will throw an error if the pageData is missing an id.
|
|
73
|
+
*/
|
|
74
|
+
export declare const ConvertToPostContent: (pageData: PageData, post: unknown) => Promise<PageContent>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { db, eq } from "astro:db";
|
|
3
|
+
import { userProjectRoot } from "virtual:studiocms-devapps/config";
|
|
4
|
+
import { decode } from "html-entities";
|
|
5
|
+
import { tsPageDataCategories, tsPageDataTags } from "studiocms/sdk/tables";
|
|
6
|
+
import TurndownService from "turndown";
|
|
7
|
+
import {
|
|
8
|
+
apiEndpoint,
|
|
9
|
+
cleanUpHtml,
|
|
10
|
+
downloadAndUpdateImages,
|
|
11
|
+
downloadPostImage,
|
|
12
|
+
stripHtml
|
|
13
|
+
} from "./utils";
|
|
14
|
+
const ASTROPUBLICFOLDER = path.resolve(userProjectRoot, "public");
|
|
15
|
+
const WPImportFolder = path.resolve(ASTROPUBLICFOLDER, "wp-import");
|
|
16
|
+
const pagesImagesFolder = path.resolve(WPImportFolder, "pages");
|
|
17
|
+
const postsImagesFolder = path.resolve(WPImportFolder, "posts");
|
|
18
|
+
const ConvertToPageData = async (page, endpoint) => {
|
|
19
|
+
const data = page;
|
|
20
|
+
const titleImageId = data.featured_media;
|
|
21
|
+
const titleImageURL = apiEndpoint(endpoint, "media", `${titleImageId}`);
|
|
22
|
+
const titleImageResponse = await fetch(titleImageURL);
|
|
23
|
+
const titleImageJson = await titleImageResponse.json();
|
|
24
|
+
const titleImage = await downloadPostImage(titleImageJson.source_url, pagesImagesFolder);
|
|
25
|
+
const pageData = {
|
|
26
|
+
id: crypto.randomUUID(),
|
|
27
|
+
title: data.title.rendered,
|
|
28
|
+
description: decode(stripHtml(data.excerpt.rendered)),
|
|
29
|
+
slug: data.slug,
|
|
30
|
+
publishedAt: new Date(data.date_gmt),
|
|
31
|
+
updatedAt: new Date(data.modified_gmt),
|
|
32
|
+
showOnNav: false,
|
|
33
|
+
contentLang: "default",
|
|
34
|
+
package: "studiocms"
|
|
35
|
+
};
|
|
36
|
+
if (titleImage) {
|
|
37
|
+
pageData.heroImage = titleImage;
|
|
38
|
+
}
|
|
39
|
+
return pageData;
|
|
40
|
+
};
|
|
41
|
+
const ConvertToPageContent = async (pageData, page) => {
|
|
42
|
+
const data = page;
|
|
43
|
+
if (pageData.id === void 0) {
|
|
44
|
+
throw new Error("pageData is missing id");
|
|
45
|
+
}
|
|
46
|
+
const cleanupContent = cleanUpHtml(data.content.rendered);
|
|
47
|
+
const htmlWithImages = await downloadAndUpdateImages(cleanupContent, pagesImagesFolder);
|
|
48
|
+
const turndownService = new TurndownService({
|
|
49
|
+
bulletListMarker: "-",
|
|
50
|
+
codeBlockStyle: "fenced",
|
|
51
|
+
emDelimiter: "*"
|
|
52
|
+
});
|
|
53
|
+
const content = turndownService.turndown(htmlWithImages);
|
|
54
|
+
const pageContent = {
|
|
55
|
+
id: crypto.randomUUID(),
|
|
56
|
+
contentId: pageData.id,
|
|
57
|
+
contentLang: "default",
|
|
58
|
+
content
|
|
59
|
+
};
|
|
60
|
+
return pageContent;
|
|
61
|
+
};
|
|
62
|
+
const generateCategories = async (categories, endpoint) => {
|
|
63
|
+
const newCategories = [];
|
|
64
|
+
for (const categoryId of categories) {
|
|
65
|
+
const categoryExists = await db.select().from(tsPageDataCategories).where(eq(tsPageDataCategories.id, categoryId)).get();
|
|
66
|
+
if (categoryExists) {
|
|
67
|
+
console.log(`Category with id ${categoryId} already exists in the database`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const categoryURL = apiEndpoint(endpoint, "categories", `${categoryId}`);
|
|
71
|
+
const response = await fetch(categoryURL);
|
|
72
|
+
const json = await response.json();
|
|
73
|
+
newCategories.push(json);
|
|
74
|
+
}
|
|
75
|
+
if (newCategories.length > 0) {
|
|
76
|
+
const categoryData = newCategories.map((category) => {
|
|
77
|
+
const data = {
|
|
78
|
+
id: category.id,
|
|
79
|
+
name: category.name,
|
|
80
|
+
slug: category.slug,
|
|
81
|
+
description: category.description,
|
|
82
|
+
meta: JSON.stringify(category.meta)
|
|
83
|
+
};
|
|
84
|
+
if (category.parent) {
|
|
85
|
+
data.parent = category.parent;
|
|
86
|
+
}
|
|
87
|
+
return data;
|
|
88
|
+
});
|
|
89
|
+
for (const category of categoryData) {
|
|
90
|
+
console.log(`Inserting category with id ${category.id} into the database`);
|
|
91
|
+
await db.insert(tsPageDataCategories).values(category);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const generateTags = async (tags, endpoint) => {
|
|
96
|
+
const newTags = [];
|
|
97
|
+
for (const tagId of tags) {
|
|
98
|
+
const tagExists = await db.select().from(tsPageDataTags).where(eq(tsPageDataTags.id, tagId)).get();
|
|
99
|
+
if (tagExists) {
|
|
100
|
+
console.log(`Tag with id ${tagId} already exists in the database`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const tagURL = apiEndpoint(endpoint, "tags", `${tagId}`);
|
|
104
|
+
const response = await fetch(tagURL);
|
|
105
|
+
const json = await response.json();
|
|
106
|
+
newTags.push(json);
|
|
107
|
+
}
|
|
108
|
+
if (newTags.length > 0) {
|
|
109
|
+
const tagData = newTags.map((tag) => {
|
|
110
|
+
const data = {
|
|
111
|
+
id: tag.id,
|
|
112
|
+
name: tag.name,
|
|
113
|
+
slug: tag.slug,
|
|
114
|
+
description: tag.description,
|
|
115
|
+
meta: JSON.stringify(tag.meta)
|
|
116
|
+
};
|
|
117
|
+
return data;
|
|
118
|
+
});
|
|
119
|
+
for (const tag of tagData) {
|
|
120
|
+
console.log(`Inserting tag with id ${tag.id} into the database`);
|
|
121
|
+
await db.insert(tsPageDataTags).values(tag);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const ConvertToPostData = async (post, useBlogPkg, endpoint) => {
|
|
126
|
+
const data = post;
|
|
127
|
+
const titleImageId = data.featured_media;
|
|
128
|
+
const titleImageURL = apiEndpoint(endpoint, "media", `${titleImageId}`);
|
|
129
|
+
const titleImageResponse = await fetch(titleImageURL);
|
|
130
|
+
const titleImageJson = await titleImageResponse.json();
|
|
131
|
+
const titleImage = await downloadPostImage(titleImageJson.source_url, pagesImagesFolder);
|
|
132
|
+
const pkg = useBlogPkg ? "@studiocms/blog" : "studiocms/markdown";
|
|
133
|
+
await generateCategories(data.categories, endpoint);
|
|
134
|
+
await generateTags(data.tags, endpoint);
|
|
135
|
+
const pageData = {
|
|
136
|
+
id: crypto.randomUUID(),
|
|
137
|
+
title: data.title.rendered,
|
|
138
|
+
description: decode(stripHtml(data.excerpt.rendered)),
|
|
139
|
+
slug: data.slug,
|
|
140
|
+
publishedAt: new Date(data.date_gmt),
|
|
141
|
+
updatedAt: new Date(data.modified_gmt),
|
|
142
|
+
showOnNav: false,
|
|
143
|
+
contentLang: "default",
|
|
144
|
+
package: pkg,
|
|
145
|
+
categories: JSON.stringify(data.categories),
|
|
146
|
+
tags: JSON.stringify(data.tags)
|
|
147
|
+
};
|
|
148
|
+
if (titleImage) {
|
|
149
|
+
pageData.heroImage = titleImage;
|
|
150
|
+
}
|
|
151
|
+
return pageData;
|
|
152
|
+
};
|
|
153
|
+
const ConvertToPostContent = async (pageData, post) => {
|
|
154
|
+
const data = post;
|
|
155
|
+
if (pageData.id === void 0) {
|
|
156
|
+
throw new Error("pageData is missing id");
|
|
157
|
+
}
|
|
158
|
+
const cleanupContent = cleanUpHtml(data.content.rendered);
|
|
159
|
+
const htmlWithImages = await downloadAndUpdateImages(cleanupContent, postsImagesFolder);
|
|
160
|
+
const turndownService = new TurndownService({
|
|
161
|
+
bulletListMarker: "-",
|
|
162
|
+
codeBlockStyle: "fenced",
|
|
163
|
+
emDelimiter: "*"
|
|
164
|
+
});
|
|
165
|
+
const content = turndownService.turndown(htmlWithImages);
|
|
166
|
+
const pageContent = {
|
|
167
|
+
id: crypto.randomUUID(),
|
|
168
|
+
contentId: pageData.id,
|
|
169
|
+
contentLang: "default",
|
|
170
|
+
content
|
|
171
|
+
};
|
|
172
|
+
return pageContent;
|
|
173
|
+
};
|
|
174
|
+
export {
|
|
175
|
+
ConvertToPageContent,
|
|
176
|
+
ConvertToPageData,
|
|
177
|
+
ConvertToPostContent,
|
|
178
|
+
ConvertToPostData,
|
|
179
|
+
generateCategories,
|
|
180
|
+
generateTags
|
|
181
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { tsPageContent, tsPageData } from 'studiocms/sdk/tables';
|
|
2
|
+
export type PageData = typeof tsPageData.$inferInsert;
|
|
3
|
+
export type PageContent = typeof tsPageContent.$inferInsert;
|
|
4
|
+
export * from './pages.js';
|
|
5
|
+
export * from './posts.js';
|
|
6
|
+
export * from './settings.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imports pages from a WordPress API endpoint.
|
|
3
|
+
*
|
|
4
|
+
* This function fetches all pages from the specified WordPress API endpoint
|
|
5
|
+
* and imports each page individually.
|
|
6
|
+
*
|
|
7
|
+
* @param endpoint - The WordPress API endpoint to fetch pages from.
|
|
8
|
+
*
|
|
9
|
+
* @returns A promise that resolves when all pages have been imported.
|
|
10
|
+
*
|
|
11
|
+
* @throws Will throw an error if the pages cannot be imported.
|
|
12
|
+
*/
|
|
13
|
+
export declare const importPagesFromWPAPI: (endpoint: string) => Promise<void>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { db } from "astro:db";
|
|
2
|
+
import { tsPageContent, tsPageData } from "studiocms/sdk/tables";
|
|
3
|
+
import { ConvertToPageContent, ConvertToPageData } from "./converters.js";
|
|
4
|
+
import { apiEndpoint, fetchAll } from "./utils.js";
|
|
5
|
+
const generatePageFromData = async (page, endpoint) => {
|
|
6
|
+
const pageData = await ConvertToPageData(page, endpoint);
|
|
7
|
+
const pageContent = await ConvertToPageContent(pageData, page);
|
|
8
|
+
return { pageData, pageContent };
|
|
9
|
+
};
|
|
10
|
+
const importPage = async (page, endpoint) => {
|
|
11
|
+
const { pageData, pageContent } = await generatePageFromData(page, endpoint);
|
|
12
|
+
const pageDataResult = await db.insert(tsPageData).values(pageData).returning({ id: tsPageData.id, title: tsPageData.title }).get();
|
|
13
|
+
if (pageDataResult === void 0) {
|
|
14
|
+
throw new Error("Failed to insert page data");
|
|
15
|
+
}
|
|
16
|
+
const pageContentResult = await db.insert(tsPageContent).values(pageContent).returning({ id: tsPageContent.id }).get();
|
|
17
|
+
if (pageContentResult === void 0) {
|
|
18
|
+
throw new Error("Failed to insert page content");
|
|
19
|
+
}
|
|
20
|
+
console.log("- Imported new page from WP-API: ", pageDataResult.title);
|
|
21
|
+
};
|
|
22
|
+
const importPagesFromWPAPI = async (endpoint) => {
|
|
23
|
+
const url = apiEndpoint(endpoint, "pages");
|
|
24
|
+
console.log("fetching pages from: ", url.origin);
|
|
25
|
+
const pages = await fetchAll(url);
|
|
26
|
+
console.log("Total pages: ", pages.length);
|
|
27
|
+
try {
|
|
28
|
+
for (const page of pages) {
|
|
29
|
+
console.log("importing page:", page.title.rendered);
|
|
30
|
+
await importPage(page, endpoint);
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error("Failed to import pages from WP-API:", error);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export {
|
|
37
|
+
importPagesFromWPAPI
|
|
38
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imports posts from a WordPress API endpoint.
|
|
3
|
+
*
|
|
4
|
+
* @param endpoint - The API endpoint to fetch posts from.
|
|
5
|
+
* @param useBlogPkg - A boolean indicating whether to use the blog package.
|
|
6
|
+
* @returns A promise that resolves when all posts have been imported.
|
|
7
|
+
*
|
|
8
|
+
* @throws Will throw an error if the import process fails.
|
|
9
|
+
*/
|
|
10
|
+
export declare const importPostsFromWPAPI: (endpoint: string, useBlogPkg: boolean) => Promise<void>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { db } from "astro:db";
|
|
2
|
+
import { tsPageContent, tsPageData } from "studiocms/sdk/tables";
|
|
3
|
+
import { ConvertToPostContent, ConvertToPostData } from "./converters.js";
|
|
4
|
+
import { apiEndpoint, fetchAll } from "./utils.js";
|
|
5
|
+
const generatePostFromData = async (post, useBlogPkg, endpoint) => {
|
|
6
|
+
const pageData = await ConvertToPostData(post, useBlogPkg, endpoint);
|
|
7
|
+
const pageContent = await ConvertToPostContent(pageData, post);
|
|
8
|
+
return { pageData, pageContent };
|
|
9
|
+
};
|
|
10
|
+
const importPost = async (post, useBlogPkg, endpoint) => {
|
|
11
|
+
const { pageData, pageContent } = await generatePostFromData(post, useBlogPkg, endpoint);
|
|
12
|
+
const pageDataResult = await db.insert(tsPageData).values(pageData).returning({ id: tsPageData.id, title: tsPageData.title }).get();
|
|
13
|
+
if (pageDataResult === void 0) {
|
|
14
|
+
throw new Error("Failed to insert post data");
|
|
15
|
+
}
|
|
16
|
+
const pageContentResult = await db.insert(tsPageContent).values(pageContent).returning({ id: tsPageContent.id }).get();
|
|
17
|
+
if (pageContentResult === void 0) {
|
|
18
|
+
throw new Error("Failed to insert post content");
|
|
19
|
+
}
|
|
20
|
+
console.log("- Imported new post from WP-API:", pageDataResult.title);
|
|
21
|
+
};
|
|
22
|
+
const importPostsFromWPAPI = async (endpoint, useBlogPkg) => {
|
|
23
|
+
const url = apiEndpoint(endpoint, "posts");
|
|
24
|
+
console.log("Fetching posts from: ", url.origin);
|
|
25
|
+
const posts = await fetchAll(url);
|
|
26
|
+
console.log("Total posts: ", posts.length);
|
|
27
|
+
try {
|
|
28
|
+
for (const post of posts) {
|
|
29
|
+
console.log("importing post: ", post.title.rendered);
|
|
30
|
+
await importPost(post, useBlogPkg, endpoint);
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error("Failed to import posts from WP-API: ", error);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export {
|
|
37
|
+
importPostsFromWPAPI
|
|
38
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imports site settings from a WordPress API endpoint and updates the local database.
|
|
3
|
+
*
|
|
4
|
+
* @param endpoint - The WordPress API endpoint to fetch settings from.
|
|
5
|
+
*
|
|
6
|
+
* This function performs the following steps:
|
|
7
|
+
* 1. Constructs the URL for the settings endpoint.
|
|
8
|
+
* 2. Fetches the site settings from the constructed URL.
|
|
9
|
+
* 3. Logs the fetched settings.
|
|
10
|
+
* 4. Downloads the site icon if available.
|
|
11
|
+
* 5. If the site icon is not available, attempts to download the site logo.
|
|
12
|
+
* 6. Constructs the site configuration object.
|
|
13
|
+
* 7. Updates the local database with the fetched settings.
|
|
14
|
+
* 8. Logs the success or failure of the database update.
|
|
15
|
+
*
|
|
16
|
+
* @throws Will log an error message if the fetch or database update fails.
|
|
17
|
+
*/
|
|
18
|
+
export declare const importSettingsFromWPAPI: (endpoint: string) => Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { db, eq } from "astro:db";
|
|
3
|
+
import { userProjectRoot } from "virtual:studiocms-devapps/config";
|
|
4
|
+
import { CMSSiteConfigId } from "studiocms/consts";
|
|
5
|
+
import { tsSiteConfig } from "studiocms/sdk/tables";
|
|
6
|
+
import { apiEndpoint, downloadPostImage } from "./utils.js";
|
|
7
|
+
const ASTROPUBLICFOLDER = path.resolve(userProjectRoot, "public");
|
|
8
|
+
const importSettingsFromWPAPI = async (endpoint) => {
|
|
9
|
+
const url = apiEndpoint(endpoint, "settings");
|
|
10
|
+
console.log("Fetching site settings from: ", url.origin);
|
|
11
|
+
const response = await fetch(url);
|
|
12
|
+
const settings = await response.json();
|
|
13
|
+
console.log("Importing site settings: ", settings);
|
|
14
|
+
let siteIcon = void 0;
|
|
15
|
+
if (settings.site_icon_url) {
|
|
16
|
+
siteIcon = await downloadPostImage(settings.site_icon_url, ASTROPUBLICFOLDER);
|
|
17
|
+
}
|
|
18
|
+
if (!settings.site_icon_url && settings.site_logo) {
|
|
19
|
+
const siteLogoURL = apiEndpoint(endpoint, "media", `${settings.site_logo}`);
|
|
20
|
+
const siteLogoResponse = await fetch(siteLogoURL);
|
|
21
|
+
const siteLogoJson = await siteLogoResponse.json();
|
|
22
|
+
siteIcon = await downloadPostImage(siteLogoJson.source_url, ASTROPUBLICFOLDER);
|
|
23
|
+
}
|
|
24
|
+
const siteConfig = {
|
|
25
|
+
id: CMSSiteConfigId,
|
|
26
|
+
title: settings.name,
|
|
27
|
+
description: settings.description
|
|
28
|
+
};
|
|
29
|
+
if (siteIcon) {
|
|
30
|
+
siteConfig.siteIcon = siteIcon;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const insert = await db.update(tsSiteConfig).set(siteConfig).where(eq(tsSiteConfig.id, CMSSiteConfigId)).returning({ id: tsSiteConfig.id }).get();
|
|
34
|
+
if (insert) {
|
|
35
|
+
console.log("Updated site settings");
|
|
36
|
+
} else {
|
|
37
|
+
console.error("Failed to update site settings");
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("Failed to import site settings from WP-API: ", error);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export {
|
|
44
|
+
importSettingsFromWPAPI
|
|
45
|
+
};
|