@studiocms/devapps 0.1.0-beta.17 → 0.1.0-beta.19

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.
@@ -0,0 +1,87 @@
1
+ import type { APIContext } from 'astro';
2
+ import { Context, Effect, Layer } from 'studiocms/effect';
3
+ import type { PageData } from './importers.js';
4
+ declare const StringConfig_base: Context.TagClass<StringConfig, "StringConfig", {
5
+ readonly str: string;
6
+ }>;
7
+ export declare class StringConfig extends StringConfig_base {
8
+ static makeLayer: (str: string) => Layer.Layer<StringConfig, never, never>;
9
+ static makeProvide: (str: string) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, StringConfig>>;
10
+ }
11
+ type APISupportedTypes = 'posts' | 'pages' | 'media' | 'categories' | 'tags' | 'settings';
12
+ declare const APIEndpointConfig_base: Context.TagClass<APIEndpointConfig, "APIEndpointConfig", {
13
+ readonly endpoint: string;
14
+ readonly type: APISupportedTypes;
15
+ readonly path?: string | undefined;
16
+ }>;
17
+ export declare class APIEndpointConfig extends APIEndpointConfig_base {
18
+ static makeLayer: (endpoint: string, type: APISupportedTypes, path?: string) => Layer.Layer<APIEndpointConfig, never, never>;
19
+ static makeProvide: (endpoint: string, type: APISupportedTypes, path?: string) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, APIEndpointConfig>>;
20
+ }
21
+ declare const DownloadImageConfig_base: Context.TagClass<DownloadImageConfig, "DownloadImageConfig", {
22
+ readonly imageUrl: string | URL;
23
+ readonly destination: string | URL;
24
+ }>;
25
+ export declare class DownloadImageConfig extends DownloadImageConfig_base {
26
+ static makeLayer: (imageUrl: string | URL, destination: string | URL) => Layer.Layer<DownloadImageConfig, never, never>;
27
+ static makeProvide: (imageUrl: string | URL, destination: string | URL) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, DownloadImageConfig>>;
28
+ }
29
+ declare const DownloadPostImageConfig_base: Context.TagClass<DownloadPostImageConfig, "DownloadPostImageConfig", {
30
+ readonly str: string;
31
+ readonly pathToFolder: string;
32
+ }>;
33
+ export declare class DownloadPostImageConfig extends DownloadPostImageConfig_base {
34
+ static makeLayer: (str: string, pathToFolder: string) => Layer.Layer<DownloadPostImageConfig, never, never>;
35
+ static makeProvide: (str: string, pathToFolder: string) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, DownloadPostImageConfig>>;
36
+ }
37
+ declare const ImportEndpointConfig_base: Context.TagClass<ImportEndpointConfig, "ImportEndpointConfig", {
38
+ readonly endpoint: string;
39
+ }>;
40
+ export declare class ImportEndpointConfig extends ImportEndpointConfig_base {
41
+ static makeLayer: (endpoint: string) => Layer.Layer<ImportEndpointConfig, never, never>;
42
+ static makeProvide: (endpoint: string) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, ImportEndpointConfig>>;
43
+ }
44
+ declare const ImportPostsEndpointConfig_base: Context.TagClass<ImportPostsEndpointConfig, "ImportPostsEndpointConfig", {
45
+ readonly endpoint: string;
46
+ readonly useBlogPkg: boolean;
47
+ }>;
48
+ export declare class ImportPostsEndpointConfig extends ImportPostsEndpointConfig_base {
49
+ static makeLayer: (endpoint: string, useBlogPkg?: boolean) => Layer.Layer<ImportPostsEndpointConfig, never, never>;
50
+ static makeProvide: (endpoint: string, useBlogPkg?: boolean) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, ImportPostsEndpointConfig>>;
51
+ }
52
+ declare const AstroAPIContextProvider_base: Context.TagClass<AstroAPIContextProvider, "AstroAPIContextProvider", {
53
+ context: APIContext;
54
+ }>;
55
+ export declare class AstroAPIContextProvider extends AstroAPIContextProvider_base {
56
+ static makeLayer: (context: APIContext) => Layer.Layer<AstroAPIContextProvider, never, never>;
57
+ static makeProvide: (context: APIContext) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, AstroAPIContextProvider>>;
58
+ }
59
+ declare const RawPageData_base: Context.TagClass<RawPageData, "RawPageData", {
60
+ readonly page: unknown;
61
+ }>;
62
+ export declare class RawPageData extends RawPageData_base {
63
+ static makeLayer: (page: unknown) => Layer.Layer<RawPageData, never, never>;
64
+ static makeProvide: (page: unknown) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, RawPageData>>;
65
+ }
66
+ declare const FullPageData_base: Context.TagClass<FullPageData, "FullPageData", {
67
+ readonly pageData: PageData;
68
+ }>;
69
+ export declare class FullPageData extends FullPageData_base {
70
+ static makeLayer: (pageData: PageData) => Layer.Layer<FullPageData, never, never>;
71
+ static makeProvide: (pageData: PageData) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, FullPageData>>;
72
+ }
73
+ declare const UseBlogPkgConfig_base: Context.TagClass<UseBlogPkgConfig, "UseBlogPkgConfig", {
74
+ readonly useBlogPkg: boolean;
75
+ }>;
76
+ export declare class UseBlogPkgConfig extends UseBlogPkgConfig_base {
77
+ static makeLayer: (useBlogPkg: boolean) => Layer.Layer<UseBlogPkgConfig, never, never>;
78
+ static makeProvide: (useBlogPkg: boolean) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, UseBlogPkgConfig>>;
79
+ }
80
+ declare const CategoryOrTagConfig_base: Context.TagClass<CategoryOrTagConfig, "CategoryOrTagConfig", {
81
+ readonly value: readonly number[];
82
+ }>;
83
+ export declare class CategoryOrTagConfig extends CategoryOrTagConfig_base {
84
+ static makeLayer: (value: readonly number[]) => Layer.Layer<CategoryOrTagConfig, never, never>;
85
+ static makeProvide: (value: readonly number[]) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, CategoryOrTagConfig>>;
86
+ }
87
+ export {};
@@ -0,0 +1,88 @@
1
+ import { Context, Effect, Layer } from "studiocms/effect";
2
+ class StringConfig extends Context.Tag("StringConfig")() {
3
+ static makeLayer = (str) => Layer.succeed(
4
+ this,
5
+ this.of({
6
+ str
7
+ })
8
+ );
9
+ static makeProvide = (str) => Effect.provide(this.makeLayer(str));
10
+ }
11
+ const SUPPORTED_TYPES = ["posts", "pages", "media", "categories", "tags", "settings"];
12
+ class APIEndpointConfig extends Context.Tag("APIEndpointConfig")() {
13
+ static makeLayer = (endpoint, type, path) => {
14
+ if (!SUPPORTED_TYPES.includes(type)) {
15
+ throw new Error(`Invalid API type: ${type}. Supported types: ${SUPPORTED_TYPES.join(", ")}`);
16
+ }
17
+ return Layer.succeed(
18
+ this,
19
+ this.of({
20
+ endpoint,
21
+ type,
22
+ path
23
+ })
24
+ );
25
+ };
26
+ static makeProvide = (endpoint, type, path) => Effect.provide(this.makeLayer(endpoint, type, path));
27
+ }
28
+ class DownloadImageConfig extends Context.Tag("DownloadImageConfig")() {
29
+ static makeLayer = (imageUrl, destination) => Layer.succeed(
30
+ this,
31
+ this.of({
32
+ imageUrl,
33
+ destination
34
+ })
35
+ );
36
+ static makeProvide = (imageUrl, destination) => Effect.provide(this.makeLayer(imageUrl, destination));
37
+ }
38
+ class DownloadPostImageConfig extends Context.Tag("DownloadPostImageConfig")() {
39
+ static makeLayer = (str, pathToFolder) => Layer.succeed(
40
+ this,
41
+ this.of({
42
+ str,
43
+ pathToFolder
44
+ })
45
+ );
46
+ static makeProvide = (str, pathToFolder) => Effect.provide(this.makeLayer(str, pathToFolder));
47
+ }
48
+ class ImportEndpointConfig extends Context.Tag("ImportEndpointConfig")() {
49
+ static makeLayer = (endpoint) => Layer.succeed(this, this.of({ endpoint }));
50
+ static makeProvide = (endpoint) => Effect.provide(this.makeLayer(endpoint));
51
+ }
52
+ class ImportPostsEndpointConfig extends Context.Tag("ImportPostsEndpointConfig")() {
53
+ static makeLayer = (endpoint, useBlogPkg = false) => Layer.succeed(this, this.of({ endpoint, useBlogPkg }));
54
+ static makeProvide = (endpoint, useBlogPkg = false) => Effect.provide(this.makeLayer(endpoint, useBlogPkg));
55
+ }
56
+ class AstroAPIContextProvider extends Context.Tag("AstroAPIContextProvider")() {
57
+ static makeLayer = (context) => Layer.succeed(this, this.of({ context }));
58
+ static makeProvide = (context) => Effect.provide(this.makeLayer(context));
59
+ }
60
+ class RawPageData extends Context.Tag("RawPageData")() {
61
+ static makeLayer = (page) => Layer.succeed(this, this.of({ page }));
62
+ static makeProvide = (page) => Effect.provide(this.makeLayer(page));
63
+ }
64
+ class FullPageData extends Context.Tag("FullPageData")() {
65
+ static makeLayer = (pageData) => Layer.succeed(this, this.of({ pageData }));
66
+ static makeProvide = (pageData) => Effect.provide(this.makeLayer(pageData));
67
+ }
68
+ class UseBlogPkgConfig extends Context.Tag("UseBlogPkgConfig")() {
69
+ static makeLayer = (useBlogPkg) => Layer.succeed(this, this.of({ useBlogPkg }));
70
+ static makeProvide = (useBlogPkg) => Effect.provide(this.makeLayer(useBlogPkg));
71
+ }
72
+ class CategoryOrTagConfig extends Context.Tag("CategoryOrTagConfig")() {
73
+ static makeLayer = (value) => Layer.succeed(this, this.of({ value }));
74
+ static makeProvide = (value) => Effect.provide(this.makeLayer(value));
75
+ }
76
+ export {
77
+ APIEndpointConfig,
78
+ AstroAPIContextProvider,
79
+ CategoryOrTagConfig,
80
+ DownloadImageConfig,
81
+ DownloadPostImageConfig,
82
+ FullPageData,
83
+ ImportEndpointConfig,
84
+ ImportPostsEndpointConfig,
85
+ RawPageData,
86
+ StringConfig,
87
+ UseBlogPkgConfig
88
+ };
@@ -0,0 +1,27 @@
1
+ import { Effect } from 'studiocms/effect';
2
+ import { FullPageData, ImportEndpointConfig, RawPageData, UseBlogPkgConfig } from './configs.js';
3
+ import { WordPressAPIUtils } from './utils.js';
4
+ declare const WordPressAPIConverters_base: Effect.Service.Class<WordPressAPIConverters, "WordPressAPIConverters", {
5
+ readonly dependencies: readonly [import("effect/Layer").Layer<import("studiocms/sdk/sdkCore").SDKCore, never, never>, import("effect/Layer").Layer<WordPressAPIUtils, never, never>];
6
+ readonly effect: Effect.Effect<{
7
+ convertToPageData: Effect.Effect<{
8
+ title: string;
9
+ slug: string;
10
+ description: string;
11
+ }, boolean | Error | import("effect/ParseResult").ParseError, ImportEndpointConfig | RawPageData>;
12
+ convertToPageContent: Effect.Effect<{
13
+ contentId: string;
14
+ }, boolean | Error | import("effect/ParseResult").ParseError, RawPageData | FullPageData>;
15
+ convertToPostData: Effect.Effect<{
16
+ title: string;
17
+ slug: string;
18
+ description: string;
19
+ }, boolean | import("astro/errors").AstroError | import("effect/Cause").UnknownException | import("studiocms/sdk/effect/db").LibSQLDatabaseError | import("effect/ParseResult").ParseError, ImportEndpointConfig | RawPageData | UseBlogPkgConfig>;
20
+ convertToPostContent: Effect.Effect<{
21
+ contentId: string;
22
+ }, boolean | Error | import("effect/ParseResult").ParseError, RawPageData | FullPageData>;
23
+ }, never, WordPressAPIUtils | import("studiocms/sdk/sdkCore").SDKCore>;
24
+ }>;
25
+ export declare class WordPressAPIConverters extends WordPressAPIConverters_base {
26
+ }
27
+ export {};
@@ -0,0 +1,299 @@
1
+ import path from "node:path";
2
+ import { eq } from "astro:db";
3
+ import { SDKCore } from "studiocms:sdk";
4
+ import { userProjectRoot } from "virtual:studiocms-devapps/config";
5
+ import { Console, Effect, Schema, genLogger } from "studiocms/effect";
6
+ import { decode } from "studiocms/runtime";
7
+ import { tsPageDataCategories, tsPageDataTags } from "studiocms/sdk/tables";
8
+ import {
9
+ APIEndpointConfig,
10
+ CategoryOrTagConfig,
11
+ DownloadPostImageConfig,
12
+ FullPageData,
13
+ ImportEndpointConfig,
14
+ RawPageData,
15
+ StringConfig,
16
+ UseBlogPkgConfig
17
+ } from "./configs.js";
18
+ import { Page, Post } from "./schema.js";
19
+ import { WordPressAPIUtils } from "./utils.js";
20
+ const ASTRO_PUBLIC_FOLDER = path.resolve(userProjectRoot, "public");
21
+ const WPImportFolder = path.resolve(ASTRO_PUBLIC_FOLDER, "wp-import");
22
+ const pagesImagesFolder = path.resolve(WPImportFolder, "pages");
23
+ const postsImagesFolder = path.resolve(WPImportFolder, "posts");
24
+ class WordPressAPIConverters extends Effect.Service()(
25
+ "WordPressAPIConverters",
26
+ {
27
+ dependencies: [SDKCore.Default, WordPressAPIUtils.Default],
28
+ effect: genLogger("@studiocms/devapps/effects/WordPressAPI/converters.effect")(function* () {
29
+ const sdk = yield* SDKCore;
30
+ const {
31
+ apiEndpoint,
32
+ cleanUpHtml,
33
+ downloadAndUpdateImages,
34
+ downloadPostImage,
35
+ stripHtml,
36
+ turndown
37
+ } = yield* WordPressAPIUtils;
38
+ const convertToPageData = genLogger(
39
+ "@studiocms/devapps/effects/WordPressAPI/converters.effect.convertToPageData"
40
+ )(function* () {
41
+ const [{ endpoint }, { page }] = yield* Effect.all([ImportEndpointConfig, RawPageData]);
42
+ if (!endpoint) {
43
+ yield* Effect.fail(new Error("Missing endpoint configuration"));
44
+ }
45
+ if (!page) {
46
+ yield* Effect.fail(new Error("Missing page data"));
47
+ }
48
+ const data = yield* Schema.decodeUnknown(Page)(page);
49
+ const cleanHTML = yield* stripHtml.pipe(StringConfig.makeProvide(data.excerpt.rendered));
50
+ const titleImageId = data.featured_media;
51
+ if (!titleImageId || titleImageId === 0) {
52
+ yield* Console.log("No featured media for:", data.title.rendered);
53
+ const pageData2 = {
54
+ // @ts-expect-error - Drizzle broke this
55
+ id: crypto.randomUUID(),
56
+ title: data.title.rendered,
57
+ description: decode(cleanHTML),
58
+ slug: data.slug,
59
+ publishedAt: new Date(data.date_gmt),
60
+ updatedAt: new Date(data.modified_gmt),
61
+ showOnNav: false,
62
+ contentLang: "default",
63
+ package: "studiocms"
64
+ };
65
+ return pageData2;
66
+ }
67
+ const titleImageURL = yield* apiEndpoint.pipe(
68
+ APIEndpointConfig.makeProvide(endpoint, "media", String(titleImageId))
69
+ );
70
+ const titleImageResponse = yield* Effect.tryPromise(() => fetch(titleImageURL));
71
+ const titleImageJson = yield* Effect.tryPromise(() => titleImageResponse.json());
72
+ const titleImage = yield* downloadPostImage.pipe(
73
+ DownloadPostImageConfig.makeProvide(titleImageJson.source_url, pagesImagesFolder)
74
+ );
75
+ const pageData = {
76
+ // @ts-expect-error - Drizzle broke this
77
+ id: crypto.randomUUID(),
78
+ title: data.title.rendered,
79
+ description: decode(cleanHTML),
80
+ slug: data.slug,
81
+ publishedAt: new Date(data.date_gmt),
82
+ updatedAt: new Date(data.modified_gmt),
83
+ showOnNav: false,
84
+ contentLang: "default",
85
+ package: "studiocms",
86
+ heroImage: titleImage
87
+ };
88
+ return pageData;
89
+ });
90
+ const convertToPageContent = genLogger(
91
+ "@studiocms/devapps/effects/WordPressAPI/converters.effect.convertToPageContent"
92
+ )(function* () {
93
+ const [{ page }, { pageData }] = yield* Effect.all([RawPageData, FullPageData]);
94
+ const data = yield* Schema.decodeUnknown(Page)(page);
95
+ if (pageData.id === void 0) {
96
+ yield* Effect.fail(new Error("pageData is missing id"));
97
+ }
98
+ const cleanUpContent = yield* cleanUpHtml.pipe(
99
+ StringConfig.makeProvide(data.content.rendered)
100
+ );
101
+ const htmlWithImages = yield* downloadAndUpdateImages.pipe(
102
+ DownloadPostImageConfig.makeProvide(cleanUpContent, pagesImagesFolder)
103
+ );
104
+ const content = yield* turndown.pipe(StringConfig.makeProvide(htmlWithImages));
105
+ const pageContent = {
106
+ // @ts-expect-error - Drizzle broke this
107
+ id: crypto.randomUUID(),
108
+ // @ts-expect-error - Drizzle broke this
109
+ contentId: pageData.id,
110
+ contentLang: "default",
111
+ content
112
+ };
113
+ return pageContent;
114
+ });
115
+ const generateCategoriesOrTags = (type) => genLogger(
116
+ "@studiocms/devapps/effects/WordPressAPI/converters.effect.generateCategoriesOrTags"
117
+ )(function* () {
118
+ const [{ endpoint }, { value }] = yield* Effect.all([
119
+ ImportEndpointConfig,
120
+ CategoryOrTagConfig
121
+ ]);
122
+ const TableMap = {
123
+ categories: tsPageDataCategories,
124
+ tags: tsPageDataTags
125
+ };
126
+ const table = TableMap[type];
127
+ const newItems = [];
128
+ const idChecks = yield* Effect.all(
129
+ value.map(
130
+ (val) => sdk.dbService.execute((client) => client.select().from(table).where(eq(table.id, val)).get()).pipe(Effect.map((exists) => ({ val, exists: !!exists })))
131
+ ),
132
+ { concurrency: 10 }
133
+ );
134
+ const missingIds = idChecks.filter(({ exists }) => !exists).map(({ val }) => val);
135
+ const fetchedIds = yield* Effect.all(
136
+ missingIds.map(
137
+ (id) => apiEndpoint.pipe(
138
+ APIEndpointConfig.makeProvide(endpoint, type, String(id)),
139
+ Effect.flatMap((url) => Effect.tryPromise(() => fetch(url))),
140
+ Effect.flatMap((response) => Effect.tryPromise(() => response.json()))
141
+ )
142
+ ),
143
+ { concurrency: 5 }
144
+ // Limit concurrent API calls
145
+ );
146
+ newItems.push(...fetchedIds);
147
+ if (newItems.length > 0) {
148
+ switch (type) {
149
+ case "categories": {
150
+ const data = newItems.map((category) => {
151
+ const data2 = {
152
+ // @ts-expect-error - Drizzle broke this
153
+ id: category.id,
154
+ name: category.name,
155
+ slug: category.slug,
156
+ description: category.description,
157
+ meta: JSON.stringify(category.meta)
158
+ };
159
+ if (category.parent) {
160
+ data2.parent = category.parent;
161
+ }
162
+ return data2;
163
+ });
164
+ yield* Console.log(
165
+ "Inserting new Categories into the database:",
166
+ data.map((d) => `${d.id}: ${d.name}`).join(", ")
167
+ );
168
+ yield* sdk.dbService.execute(
169
+ (client) => client.insert(tsPageDataCategories).values(data)
170
+ );
171
+ yield* Console.log("Categories inserted!");
172
+ break;
173
+ }
174
+ case "tags": {
175
+ const tagData = newItems.map((tag) => {
176
+ const data = {
177
+ // @ts-expect-error - Drizzle broke this
178
+ id: tag.id,
179
+ name: tag.name,
180
+ slug: tag.slug,
181
+ description: tag.description,
182
+ meta: JSON.stringify(tag.meta)
183
+ };
184
+ return data;
185
+ });
186
+ yield* Console.log(
187
+ "Inserting new Tags into the database:",
188
+ tagData.map((data) => `${data.id}: ${data.name}`).join(", ")
189
+ );
190
+ yield* sdk.dbService.execute(
191
+ (client) => client.insert(tsPageDataTags).values(tagData)
192
+ );
193
+ yield* Console.log("Tags inserted!");
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ });
199
+ const convertToPostData = genLogger(
200
+ "@studiocms/devapps/effects/WordPressAPI/converters.effect.convertToPostData"
201
+ )(function* () {
202
+ const [{ endpoint }, { page: post }, { useBlogPkg }] = yield* Effect.all([
203
+ ImportEndpointConfig,
204
+ RawPageData,
205
+ UseBlogPkgConfig
206
+ ]);
207
+ const data = yield* Schema.decodeUnknown(Post)(post);
208
+ const pkg = useBlogPkg ? "@studiocms/blog" : "studiocms/markdown";
209
+ const cleanedHTML = yield* stripHtml.pipe(StringConfig.makeProvide(data.excerpt.rendered));
210
+ const titleImageId = data.featured_media;
211
+ if (!titleImageId || titleImageId === 0) {
212
+ yield* Console.log("No featured media for:", data.title.rendered);
213
+ const pageData2 = {
214
+ // @ts-expect-error - Drizzle broke this
215
+ id: crypto.randomUUID(),
216
+ title: data.title.rendered,
217
+ description: decode(cleanedHTML),
218
+ slug: data.slug,
219
+ publishedAt: new Date(data.date_gmt),
220
+ updatedAt: new Date(data.modified_gmt),
221
+ showOnNav: false,
222
+ contentLang: "default",
223
+ package: pkg,
224
+ categories: JSON.stringify(data.categories),
225
+ tags: JSON.stringify(data.tags)
226
+ };
227
+ return pageData2;
228
+ }
229
+ const titleImageURL = yield* apiEndpoint.pipe(
230
+ APIEndpointConfig.makeProvide(endpoint, "media", String(titleImageId))
231
+ );
232
+ const titleImageResponse = yield* Effect.tryPromise(() => fetch(titleImageURL));
233
+ const titleImageJson = yield* Effect.tryPromise(() => titleImageResponse.json());
234
+ const titleImage = yield* downloadPostImage.pipe(
235
+ DownloadPostImageConfig.makeProvide(titleImageJson.source_url, postsImagesFolder)
236
+ );
237
+ yield* generateCategoriesOrTags("categories").pipe(
238
+ ImportEndpointConfig.makeProvide(endpoint),
239
+ CategoryOrTagConfig.makeProvide(data.categories)
240
+ );
241
+ yield* generateCategoriesOrTags("tags").pipe(
242
+ ImportEndpointConfig.makeProvide(endpoint),
243
+ CategoryOrTagConfig.makeProvide(data.tags)
244
+ );
245
+ const pageData = {
246
+ // @ts-expect-error - Drizzle broke this
247
+ id: crypto.randomUUID(),
248
+ title: data.title.rendered,
249
+ description: decode(cleanedHTML),
250
+ slug: data.slug,
251
+ publishedAt: new Date(data.date_gmt),
252
+ updatedAt: new Date(data.modified_gmt),
253
+ showOnNav: false,
254
+ contentLang: "default",
255
+ package: pkg,
256
+ categories: JSON.stringify(data.categories),
257
+ tags: JSON.stringify(data.tags),
258
+ heroImage: titleImage
259
+ };
260
+ return pageData;
261
+ });
262
+ const convertToPostContent = genLogger(
263
+ "@studiocms/devapps/effects/WordPressAPI/converters.effect.convertToPostContent"
264
+ )(function* () {
265
+ const [{ pageData }, { page: post }] = yield* Effect.all([FullPageData, RawPageData]);
266
+ const data = yield* Schema.decodeUnknown(Post)(post);
267
+ if (pageData.id === void 0) {
268
+ yield* Effect.fail(new Error("pageData is missing id"));
269
+ }
270
+ const cleanupContent = yield* cleanUpHtml.pipe(
271
+ StringConfig.makeProvide(data.content.rendered)
272
+ );
273
+ const htmlWithImages = yield* downloadAndUpdateImages.pipe(
274
+ DownloadPostImageConfig.makeProvide(cleanupContent, postsImagesFolder)
275
+ );
276
+ const content = yield* turndown.pipe(StringConfig.makeProvide(htmlWithImages));
277
+ const pageContent = {
278
+ // @ts-expect-error - Drizzle broke this
279
+ id: crypto.randomUUID(),
280
+ // @ts-expect-error - Drizzle broke this
281
+ contentId: pageData.id,
282
+ contentLang: "default",
283
+ content
284
+ };
285
+ return pageContent;
286
+ });
287
+ return {
288
+ convertToPageData,
289
+ convertToPageContent,
290
+ convertToPostData,
291
+ convertToPostContent
292
+ };
293
+ })
294
+ }
295
+ ) {
296
+ }
297
+ export {
298
+ WordPressAPIConverters
299
+ };
@@ -0,0 +1,19 @@
1
+ import { Effect } from 'studiocms/effect';
2
+ import type { tsPageContent, tsPageData } from 'studiocms/sdk/tables';
3
+ import { ImportEndpointConfig, ImportPostsEndpointConfig } from './configs.js';
4
+ import { WordPressAPIConverters } from './converters.js';
5
+ import { WordPressAPIUtils } from './utils.js';
6
+ export type PageData = typeof tsPageData.$inferInsert;
7
+ export type PageContent = typeof tsPageContent.$inferInsert;
8
+ declare const WordPressAPI_base: Effect.Service.Class<WordPressAPI, "WordPressAPI", {
9
+ readonly dependencies: readonly [import("effect/Layer").Layer<import("studiocms/sdk/sdkCore").SDKCore, never, never>, import("effect/Layer").Layer<WordPressAPIUtils, never, never>, import("effect/Layer").Layer<WordPressAPIConverters, never, never>];
10
+ readonly effect: Effect.Effect<{
11
+ importSettingsFromWPAPI: Effect.Effect<void, boolean | import("astro/errors").AstroError | import("effect/Cause").UnknownException | import("studiocms/sdk/errors").SDKCoreError | import("effect/ParseResult").ParseError, ImportEndpointConfig>;
12
+ importPagesFromWPAPI: Effect.Effect<void, boolean | Error | import("studiocms/sdk/errors").SDKCoreError | import("effect/ParseResult").ParseError, ImportEndpointConfig>;
13
+ importPostsFromWPAPI: Effect.Effect<void, boolean | Error | import("studiocms/sdk/effect/db").LibSQLDatabaseError | import("studiocms/sdk/errors").SDKCoreError | import("effect/ParseResult").ParseError, ImportPostsEndpointConfig>;
14
+ }, never, WordPressAPIUtils | WordPressAPIConverters | import("studiocms/sdk/sdkCore").SDKCore>;
15
+ }>;
16
+ export declare class WordPressAPI extends WordPressAPI_base {
17
+ static Provide: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, WordPressAPI>>;
18
+ }
19
+ export {};
@@ -0,0 +1,122 @@
1
+ import path from "node:path";
2
+ import { SDKCore } from "studiocms:sdk";
3
+ import { userProjectRoot } from "virtual:studiocms-devapps/config";
4
+ import { Console, Effect, Schema, genLogger } from "studiocms/effect";
5
+ import {
6
+ APIEndpointConfig,
7
+ DownloadPostImageConfig,
8
+ FullPageData,
9
+ ImportEndpointConfig,
10
+ ImportPostsEndpointConfig,
11
+ RawPageData,
12
+ UseBlogPkgConfig
13
+ } from "./configs.js";
14
+ import { WordPressAPIConverters } from "./converters.js";
15
+ import { PagesSchema, PostsSchema, SiteSettings } from "./schema.js";
16
+ import { WordPressAPIUtils } from "./utils.js";
17
+ const ASTRO_PUBLIC_FOLDER = path.resolve(userProjectRoot, "public");
18
+ class WordPressAPI extends Effect.Service()("WordPressAPI", {
19
+ dependencies: [SDKCore.Default, WordPressAPIUtils.Default, WordPressAPIConverters.Default],
20
+ effect: genLogger("@studiocms/devapps/effects/WordPressAPI.effect")(function* () {
21
+ const sdk = yield* SDKCore;
22
+ const { apiEndpoint, downloadPostImage, fetchAll } = yield* WordPressAPIUtils;
23
+ const { convertToPageContent, convertToPageData, convertToPostContent, convertToPostData } = yield* WordPressAPIConverters;
24
+ const importSettingsFromWPAPI = genLogger(
25
+ "@studiocms/devapps/effects/WordPressAPI.effect.importSettingsFromWPAPI"
26
+ )(function* () {
27
+ const { endpoint } = yield* ImportEndpointConfig;
28
+ const url = yield* apiEndpoint.pipe(APIEndpointConfig.makeProvide(endpoint, "settings"));
29
+ yield* Console.log("Fetching site settings from: ", url.origin);
30
+ const response = yield* Effect.tryPromise(() => fetch(url));
31
+ const rawSettings = yield* Effect.tryPromise(() => response.json());
32
+ const settings = yield* Schema.decodeUnknown(SiteSettings)(rawSettings);
33
+ yield* Console.log("Importing site settings: ", settings);
34
+ let siteIcon = void 0;
35
+ if (settings.site_icon_url) {
36
+ siteIcon = yield* downloadPostImage.pipe(
37
+ DownloadPostImageConfig.makeProvide(settings.site_icon_url, ASTRO_PUBLIC_FOLDER)
38
+ );
39
+ }
40
+ if (!settings.site_icon_url && settings.site_logo) {
41
+ const siteLogoUrl = yield* apiEndpoint.pipe(
42
+ APIEndpointConfig.makeProvide(endpoint, "media", String(settings.site_logo))
43
+ );
44
+ const siteLogoResponse = yield* Effect.tryPromise(() => fetch(siteLogoUrl));
45
+ const siteLogoData = yield* Effect.tryPromise(() => siteLogoResponse.json());
46
+ siteIcon = yield* downloadPostImage.pipe(
47
+ DownloadPostImageConfig.makeProvide(siteLogoData.source_url, ASTRO_PUBLIC_FOLDER)
48
+ );
49
+ }
50
+ const siteConfig = {
51
+ title: settings.name,
52
+ description: settings.description
53
+ };
54
+ if (siteIcon) {
55
+ siteConfig.siteIcon = siteIcon;
56
+ }
57
+ const insert = yield* sdk.UPDATE.siteConfig(siteConfig);
58
+ if (insert.lastCacheUpdate) {
59
+ yield* Console.log("Updated site settings");
60
+ } else {
61
+ yield* Console.error("Failed to update site settings");
62
+ }
63
+ });
64
+ const importPagesFromWPAPI = genLogger(
65
+ "@studiocms/devapps/effects/WordPressAPI.effect.importPagesFromWPAPI"
66
+ )(function* () {
67
+ const { endpoint } = yield* ImportEndpointConfig;
68
+ const url = yield* apiEndpoint.pipe(APIEndpointConfig.makeProvide(endpoint, "pages"));
69
+ yield* Console.log("fetching pages from: ", url.origin);
70
+ const rawPages = yield* fetchAll(url);
71
+ const { pages } = yield* Schema.decodeUnknown(PagesSchema)({ pages: rawPages });
72
+ yield* Console.log("Total pages: ", pages.length);
73
+ for (const page of pages) {
74
+ yield* Console.log("importing page:", page.title.rendered);
75
+ const pageData = yield* convertToPageData.pipe(
76
+ ImportEndpointConfig.makeProvide(endpoint),
77
+ RawPageData.makeProvide(page)
78
+ );
79
+ const pageContent = yield* convertToPageContent.pipe(
80
+ RawPageData.makeProvide(page),
81
+ FullPageData.makeProvide(pageData)
82
+ );
83
+ yield* sdk.POST.databaseEntry.pages(pageData, pageContent);
84
+ yield* Console.log("- Imported new page from WP-API: ", page.title.rendered);
85
+ }
86
+ });
87
+ const importPostsFromWPAPI = genLogger(
88
+ "@studiocms/devapps/effects/WordPressAPI.effect.importPostsFromWPAPI"
89
+ )(function* () {
90
+ const { endpoint, useBlogPkg } = yield* ImportPostsEndpointConfig;
91
+ const url = yield* apiEndpoint.pipe(APIEndpointConfig.makeProvide(endpoint, "posts"));
92
+ yield* Console.log("fetching posts from: ", url.origin);
93
+ const rawPages = yield* fetchAll(url);
94
+ const { posts: pages } = yield* Schema.decodeUnknown(PostsSchema)({ posts: rawPages });
95
+ yield* Console.log("Total posts: ", pages.length);
96
+ for (const page of pages) {
97
+ yield* Console.log("importing post:", page.title.rendered);
98
+ const pageData = yield* convertToPostData.pipe(
99
+ ImportEndpointConfig.makeProvide(endpoint),
100
+ RawPageData.makeProvide(page),
101
+ UseBlogPkgConfig.makeProvide(useBlogPkg)
102
+ );
103
+ const pageContent = yield* convertToPostContent.pipe(
104
+ RawPageData.makeProvide(page),
105
+ FullPageData.makeProvide(pageData)
106
+ );
107
+ yield* sdk.POST.databaseEntry.pages(pageData, pageContent);
108
+ yield* Console.log("- Imported new post from WP-API: ", page.title.rendered);
109
+ }
110
+ });
111
+ return {
112
+ importSettingsFromWPAPI,
113
+ importPagesFromWPAPI,
114
+ importPostsFromWPAPI
115
+ };
116
+ })
117
+ }) {
118
+ static Provide = Effect.provide(this.Default);
119
+ }
120
+ export {
121
+ WordPressAPI
122
+ };