@focus-reactive/payload-plugin-seo 1.5.0 → 1.6.0
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 +198 -6
- package/dist/admin.css +97 -1
- package/dist/client-config/registry.d.ts +7 -0
- package/dist/client-config/registry.d.ts.map +1 -0
- package/dist/client-config/registry.js +16 -0
- package/dist/client-config/registry.js.map +1 -0
- package/dist/components/SeoField/Meter.d.ts +10 -0
- package/dist/components/SeoField/Meter.d.ts.map +1 -0
- package/dist/components/SeoField/Meter.js +59 -0
- package/dist/components/SeoField/Meter.js.map +1 -0
- package/dist/components/SeoField/icons.d.ts +5 -0
- package/dist/components/SeoField/icons.d.ts.map +1 -0
- package/dist/components/SeoField/icons.js +12 -0
- package/dist/components/SeoField/icons.js.map +1 -0
- package/dist/components/SeoField/index.d.ts +20 -0
- package/dist/components/SeoField/index.d.ts.map +1 -0
- package/dist/components/SeoField/index.js +118 -0
- package/dist/components/SeoField/index.js.map +1 -0
- package/dist/components/SeoField/useGenerate.d.ts +15 -0
- package/dist/components/SeoField/useGenerate.d.ts.map +1 -0
- package/dist/components/SeoField/useGenerate.js +102 -0
- package/dist/components/SeoField/useGenerate.js.map +1 -0
- package/dist/constants/generation.d.ts +13 -0
- package/dist/constants/generation.d.ts.map +1 -0
- package/dist/constants/generation.js +21 -0
- package/dist/constants/generation.js.map +1 -0
- package/dist/engine/helpers/title-progress.d.ts +2 -1
- package/dist/engine/helpers/title-progress.d.ts.map +1 -1
- package/dist/engine/helpers/title-progress.js +1 -1
- package/dist/engine/helpers/title-progress.js.map +1 -1
- package/dist/fields/index.d.ts +3 -0
- package/dist/fields/index.d.ts.map +1 -0
- package/dist/fields/index.js +5 -0
- package/dist/fields/index.js.map +1 -0
- package/dist/fields/onPublishHook.d.ts +8 -0
- package/dist/fields/onPublishHook.d.ts.map +1 -0
- package/dist/fields/onPublishHook.js +95 -0
- package/dist/fields/onPublishHook.js.map +1 -0
- package/dist/fields/seoTextField.d.ts +23 -0
- package/dist/fields/seoTextField.d.ts.map +1 -0
- package/dist/fields/seoTextField.js +43 -0
- package/dist/fields/seoTextField.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/measure/measure.d.ts +16 -0
- package/dist/measure/measure.d.ts.map +1 -0
- package/dist/measure/measure.js +38 -0
- package/dist/measure/measure.js.map +1 -0
- package/dist/providers/SeoClientConfigProvider.d.ts +8 -0
- package/dist/providers/SeoClientConfigProvider.d.ts.map +1 -0
- package/dist/providers/SeoClientConfigProvider.js +16 -0
- package/dist/providers/SeoClientConfigProvider.js.map +1 -0
- package/dist/server/generate/apiKey.d.ts +3 -0
- package/dist/server/generate/apiKey.d.ts.map +1 -0
- package/dist/server/generate/apiKey.js +11 -0
- package/dist/server/generate/apiKey.js.map +1 -0
- package/dist/server/generate/endpoint.d.ts +3 -0
- package/dist/server/generate/endpoint.d.ts.map +1 -0
- package/dist/server/generate/endpoint.js +47 -0
- package/dist/server/generate/endpoint.js.map +1 -0
- package/dist/server/generate/generateForField.d.ts +18 -0
- package/dist/server/generate/generateForField.d.ts.map +1 -0
- package/dist/server/generate/generateForField.js +41 -0
- package/dist/server/generate/generateForField.js.map +1 -0
- package/dist/server/generate/openai.d.ts +9 -0
- package/dist/server/generate/openai.d.ts.map +1 -0
- package/dist/server/generate/openai.js +43 -0
- package/dist/server/generate/openai.js.map +1 -0
- package/dist/server/generate/prompts.d.ts +19 -0
- package/dist/server/generate/prompts.d.ts.map +1 -0
- package/dist/server/generate/prompts.js +32 -0
- package/dist/server/generate/prompts.js.map +1 -0
- package/dist/server/generate/serverResolveDocs.d.ts +4 -0
- package/dist/server/generate/serverResolveDocs.d.ts.map +1 -0
- package/dist/server/generate/serverResolveDocs.js +38 -0
- package/dist/server/generate/serverResolveDocs.js.map +1 -0
- package/dist/types/config.d.ts +26 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/utils/config/overrideAdmin.d.ts.map +1 -1
- package/dist/utils/config/overrideAdmin.js +27 -1
- package/dist/utils/config/overrideAdmin.js.map +1 -1
- package/package.json +13 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { getPluginConfig } from "../config";
|
|
2
|
+
import { PLUGIN_NAME } from "../constants";
|
|
3
|
+
import { DESCRIPTION_RANGE, TITLE_RANGE } from "../constants/generation";
|
|
4
|
+
import {
|
|
5
|
+
compact,
|
|
6
|
+
heading,
|
|
7
|
+
html,
|
|
8
|
+
image,
|
|
9
|
+
link,
|
|
10
|
+
paragraph,
|
|
11
|
+
richText,
|
|
12
|
+
video
|
|
13
|
+
} from "../content/schema/helpers";
|
|
14
|
+
import { serialize } from "../content/schema/serialize";
|
|
15
|
+
import { resolveApiKey } from "../server/generate/apiKey";
|
|
16
|
+
import { createServerResolveDocs } from "../server/generate/serverResolveDocs";
|
|
17
|
+
import { generateForField } from "../server/generate/generateForField";
|
|
18
|
+
const helpers = {
|
|
19
|
+
heading,
|
|
20
|
+
paragraph,
|
|
21
|
+
link,
|
|
22
|
+
image,
|
|
23
|
+
video,
|
|
24
|
+
html,
|
|
25
|
+
richText,
|
|
26
|
+
compact
|
|
27
|
+
};
|
|
28
|
+
const CONTEXT_KEY = "__fr_seo_content__";
|
|
29
|
+
function isEmpty(v) {
|
|
30
|
+
return typeof v !== "string" || v.trim().length === 0;
|
|
31
|
+
}
|
|
32
|
+
function rangeFor(kind, override) {
|
|
33
|
+
const base = kind === "title" ? TITLE_RANGE : DESCRIPTION_RANGE;
|
|
34
|
+
return {
|
|
35
|
+
min: override?.min ?? base.min,
|
|
36
|
+
max: override?.max ?? base.max,
|
|
37
|
+
unit: kind === "title" ? "px" : "char"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function makeGenerateOnPublishHook(args) {
|
|
41
|
+
return async ({ value, data, collection, req, operation }) => {
|
|
42
|
+
if (!isEmpty(value))
|
|
43
|
+
return value;
|
|
44
|
+
if (operation !== "create" && operation !== "update")
|
|
45
|
+
return value;
|
|
46
|
+
const status = data?._status;
|
|
47
|
+
if (status && status !== "published")
|
|
48
|
+
return value;
|
|
49
|
+
const config = getPluginConfig();
|
|
50
|
+
const apiKey = resolveApiKey(config.generation);
|
|
51
|
+
if (!apiKey)
|
|
52
|
+
return value;
|
|
53
|
+
const slug = collection?.slug;
|
|
54
|
+
const seoCfg = config.collections.find((c) => c.slug === slug);
|
|
55
|
+
const extractor = seoCfg?.serverExtractContent;
|
|
56
|
+
if (!extractor || !slug)
|
|
57
|
+
return value;
|
|
58
|
+
try {
|
|
59
|
+
const localeCode = typeof req.locale === "string" ? req.locale : void 0;
|
|
60
|
+
const bucket = req.context[CONTEXT_KEY] ??= {};
|
|
61
|
+
const cacheKey = `${slug}:${localeCode ?? ""}`;
|
|
62
|
+
let contentHtml = bucket[cacheKey];
|
|
63
|
+
if (contentHtml === void 0) {
|
|
64
|
+
const toolkit = {
|
|
65
|
+
resolveDocs: createServerResolveDocs(req.payload, localeCode),
|
|
66
|
+
helpers
|
|
67
|
+
};
|
|
68
|
+
const ir = await extractor(
|
|
69
|
+
data,
|
|
70
|
+
{ locale: localeCode },
|
|
71
|
+
toolkit
|
|
72
|
+
);
|
|
73
|
+
contentHtml = serialize(ir);
|
|
74
|
+
bucket[cacheKey] = contentHtml;
|
|
75
|
+
}
|
|
76
|
+
return await generateForField({
|
|
77
|
+
kind: args.kind,
|
|
78
|
+
contentHtml,
|
|
79
|
+
range: rangeFor(args.kind, args.range),
|
|
80
|
+
locale: localeCode,
|
|
81
|
+
config: config.generation ?? {},
|
|
82
|
+
apiKey
|
|
83
|
+
});
|
|
84
|
+
} catch (err) {
|
|
85
|
+
req.payload.logger.error(
|
|
86
|
+
`[${PLUGIN_NAME}] on-publish generation failed: ${err.message}`
|
|
87
|
+
);
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
makeGenerateOnPublishHook
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=onPublishHook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/fields/onPublishHook.ts"],"sourcesContent":["import type { FieldHook } from \"payload\";\nimport { getPluginConfig } from \"../config\";\nimport { PLUGIN_NAME } from \"../constants\";\nimport { DESCRIPTION_RANGE, TITLE_RANGE } from \"../constants/generation\";\nimport {\n compact,\n heading,\n html,\n image,\n link,\n paragraph,\n richText,\n video,\n} from \"../content/schema/helpers\";\nimport { serialize } from \"../content/schema/serialize\";\nimport type { ContentHelpers, ExtractToolkit, SeoCollectionConfig } from \"../types/config\";\nimport type { RangeOverride } from \"../measure/measure\";\nimport { resolveApiKey } from \"../server/generate/apiKey\";\nimport { createServerResolveDocs } from \"../server/generate/serverResolveDocs\";\nimport { generateForField } from \"../server/generate/generateForField\";\nimport type { SeoFieldKind } from \"../server/generate/prompts\";\n\nconst helpers: ContentHelpers = {\n heading,\n paragraph,\n link,\n image,\n video,\n html,\n richText,\n compact,\n};\n\nconst CONTEXT_KEY = \"__fr_seo_content__\";\n\nfunction isEmpty(v: unknown): boolean {\n return typeof v !== \"string\" || v.trim().length === 0;\n}\n\nfunction rangeFor(kind: SeoFieldKind, override: RangeOverride | undefined) {\n const base = kind === \"title\" ? TITLE_RANGE : DESCRIPTION_RANGE;\n\n return {\n min: override?.min ?? base.min,\n max: override?.max ?? base.max,\n unit: (kind === \"title\" ? \"px\" : \"char\") as \"px\" | \"char\",\n };\n}\n\nexport function makeGenerateOnPublishHook(args: {\n kind: SeoFieldKind;\n range: RangeOverride | undefined;\n}): FieldHook {\n return async ({ value, data, collection, req, operation }) => {\n if (!isEmpty(value)) return value;\n if (operation !== \"create\" && operation !== \"update\") return value;\n\n const status = (data as { _status?: string } | undefined)?._status;\n if (status && status !== \"published\") return value;\n\n const config = getPluginConfig();\n const apiKey = resolveApiKey(config.generation);\n if (!apiKey) return value;\n\n const slug = collection?.slug;\n const seoCfg = config.collections.find((c: SeoCollectionConfig) => c.slug === slug);\n const extractor = seoCfg?.serverExtractContent;\n if (!extractor || !slug) return value;\n\n try {\n const localeCode = typeof req.locale === \"string\" ? req.locale : undefined;\n\n const bucket = (req.context[CONTEXT_KEY] ??= {}) as Record<string, string>;\n const cacheKey = `${slug}:${localeCode ?? \"\"}`;\n let contentHtml = bucket[cacheKey];\n if (contentHtml === undefined) {\n const toolkit: ExtractToolkit = {\n resolveDocs: createServerResolveDocs(req.payload, localeCode),\n helpers,\n };\n const ir = await extractor(\n data as Record<string, unknown>,\n { locale: localeCode },\n toolkit\n );\n contentHtml = serialize(ir);\n bucket[cacheKey] = contentHtml;\n }\n\n return await generateForField({\n kind: args.kind,\n contentHtml,\n range: rangeFor(args.kind, args.range),\n locale: localeCode,\n config: config.generation ?? {},\n apiKey,\n });\n } catch (err) {\n req.payload.logger.error(\n `[${PLUGIN_NAME}] on-publish generation failed: ${(err as Error).message}`\n );\n\n return value;\n }\n };\n}\n"],"mappings":"AACA,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB,mBAAmB;AAC/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,iBAAiB;AAG1B,SAAS,qBAAqB;AAC9B,SAAS,+BAA+B;AACxC,SAAS,wBAAwB;AAGjC,MAAM,UAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,cAAc;AAEpB,SAAS,QAAQ,GAAqB;AACpC,SAAO,OAAO,MAAM,YAAY,EAAE,KAAK,EAAE,WAAW;AACtD;AAEA,SAAS,SAAS,MAAoB,UAAqC;AACzE,QAAM,OAAO,SAAS,UAAU,cAAc;AAE9C,SAAO;AAAA,IACL,KAAK,UAAU,OAAO,KAAK;AAAA,IAC3B,KAAK,UAAU,OAAO,KAAK;AAAA,IAC3B,MAAO,SAAS,UAAU,OAAO;AAAA,EACnC;AACF;AAEO,SAAS,0BAA0B,MAG5B;AACZ,SAAO,OAAO,EAAE,OAAO,MAAM,YAAY,KAAK,UAAU,MAAM;AAC5D,QAAI,CAAC,QAAQ,KAAK;AAAG,aAAO;AAC5B,QAAI,cAAc,YAAY,cAAc;AAAU,aAAO;AAE7D,UAAM,SAAU,MAA2C;AAC3D,QAAI,UAAU,WAAW;AAAa,aAAO;AAE7C,UAAM,SAAS,gBAAgB;AAC/B,UAAM,SAAS,cAAc,OAAO,UAAU;AAC9C,QAAI,CAAC;AAAQ,aAAO;AAEpB,UAAM,OAAO,YAAY;AACzB,UAAM,SAAS,OAAO,YAAY,KAAK,CAAC,MAA2B,EAAE,SAAS,IAAI;AAClF,UAAM,YAAY,QAAQ;AAC1B,QAAI,CAAC,aAAa,CAAC;AAAM,aAAO;AAEhC,QAAI;AACF,YAAM,aAAa,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS;AAEjE,YAAM,SAAU,IAAI,QAAQ,WAAW,MAAM,CAAC;AAC9C,YAAM,WAAW,GAAG,IAAI,IAAI,cAAc,EAAE;AAC5C,UAAI,cAAc,OAAO,QAAQ;AACjC,UAAI,gBAAgB,QAAW;AAC7B,cAAM,UAA0B;AAAA,UAC9B,aAAa,wBAAwB,IAAI,SAAS,UAAU;AAAA,UAC5D;AAAA,QACF;AACA,cAAM,KAAK,MAAM;AAAA,UACf;AAAA,UACA,EAAE,QAAQ,WAAW;AAAA,UACrB;AAAA,QACF;AACA,sBAAc,UAAU,EAAE;AAC1B,eAAO,QAAQ,IAAI;AAAA,MACrB;AAEA,aAAO,MAAM,iBAAiB;AAAA,QAC5B,MAAM,KAAK;AAAA,QACX;AAAA,QACA,OAAO,SAAS,KAAK,MAAM,KAAK,KAAK;AAAA,QACrC,QAAQ;AAAA,QACR,QAAQ,OAAO,cAAc,CAAC;AAAA,QAC9B;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,UAAI,QAAQ,OAAO;AAAA,QACjB,IAAI,WAAW,mCAAoC,IAAc,OAAO;AAAA,MAC1E;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TextareaField, TextField } from "payload";
|
|
2
|
+
import type { RangeOverride } from "../measure/measure";
|
|
3
|
+
import type { SeoFieldKind } from "../server/generate/prompts";
|
|
4
|
+
export interface SeoTextFieldOptions {
|
|
5
|
+
name: string;
|
|
6
|
+
kind: SeoFieldKind;
|
|
7
|
+
label?: TextField["label"];
|
|
8
|
+
required?: boolean;
|
|
9
|
+
localized?: boolean;
|
|
10
|
+
admin?: TextField["admin"];
|
|
11
|
+
/** Show the manual Generate button.
|
|
12
|
+
* @default false
|
|
13
|
+
*/
|
|
14
|
+
showButton?: boolean;
|
|
15
|
+
/** Generate on publish when the field is empty
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
generateOnPublish?: boolean;
|
|
19
|
+
/** Length range override in the kind's unit (px for title, chars for description). */
|
|
20
|
+
range?: RangeOverride;
|
|
21
|
+
}
|
|
22
|
+
export declare function seoTextField(options: SeoTextFieldOptions): TextField | TextareaField;
|
|
23
|
+
//# sourceMappingURL=seoTextField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seoTextField.d.ts","sourceRoot":"","sources":["../../src/fields/seoTextField.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3B;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,sFAAsF;IACtF,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,aAAa,CA2CpF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getComponentPath } from "../utils/config/getComponentPath";
|
|
2
|
+
import { makeGenerateOnPublishHook } from "./onPublishHook";
|
|
3
|
+
function seoTextField(options) {
|
|
4
|
+
const { name, kind, label, required, localized, admin, range } = options;
|
|
5
|
+
const showButton = options.showButton ?? false;
|
|
6
|
+
const generateOnPublish = options.generateOnPublish ?? false;
|
|
7
|
+
const common = {
|
|
8
|
+
name,
|
|
9
|
+
...label === void 0 ? {} : { label },
|
|
10
|
+
...required === void 0 ? {} : { required },
|
|
11
|
+
...localized === void 0 ? {} : { localized },
|
|
12
|
+
admin: {
|
|
13
|
+
...admin,
|
|
14
|
+
components: {
|
|
15
|
+
...admin?.components,
|
|
16
|
+
Field: {
|
|
17
|
+
path: getComponentPath("components/SeoField", "SeoField"),
|
|
18
|
+
clientProps: {
|
|
19
|
+
kind,
|
|
20
|
+
showButton,
|
|
21
|
+
generateOnPublish,
|
|
22
|
+
range
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const field = kind === "description" ? { ...common, type: "textarea" } : { ...common, type: "text" };
|
|
29
|
+
if (generateOnPublish) {
|
|
30
|
+
field.hooks = {
|
|
31
|
+
...field.hooks,
|
|
32
|
+
beforeChange: [
|
|
33
|
+
...field.hooks?.beforeChange ?? [],
|
|
34
|
+
makeGenerateOnPublishHook({ kind, range })
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return field;
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
seoTextField
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=seoTextField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/fields/seoTextField.ts"],"sourcesContent":["import type { TextareaField, TextField } from \"payload\";\nimport { getComponentPath } from \"../utils/config/getComponentPath\";\nimport { makeGenerateOnPublishHook } from \"./onPublishHook\";\nimport type { RangeOverride } from \"../measure/measure\";\nimport type { SeoFieldKind } from \"../server/generate/prompts\";\n\nexport interface SeoTextFieldOptions {\n name: string;\n kind: SeoFieldKind;\n label?: TextField[\"label\"];\n required?: boolean;\n localized?: boolean;\n admin?: TextField[\"admin\"];\n /** Show the manual Generate button.\n * @default false\n */\n showButton?: boolean;\n /** Generate on publish when the field is empty\n * @default false\n */\n generateOnPublish?: boolean;\n /** Length range override in the kind's unit (px for title, chars for description). */\n range?: RangeOverride;\n}\n\nexport function seoTextField(options: SeoTextFieldOptions): TextField | TextareaField {\n const { name, kind, label, required, localized, admin, range } = options;\n const showButton = options.showButton ?? false;\n const generateOnPublish = options.generateOnPublish ?? false;\n\n const common = {\n name,\n ...(label === undefined ? {} : { label }),\n ...(required === undefined ? {} : { required }),\n ...(localized === undefined ? {} : { localized }),\n admin: {\n ...admin,\n components: {\n ...admin?.components,\n Field: {\n path: getComponentPath(\"components/SeoField\", \"SeoField\"),\n clientProps: {\n kind,\n showButton,\n generateOnPublish,\n range,\n },\n },\n },\n },\n };\n\n const field: TextField | TextareaField =\n kind === \"description\"\n ? ({ ...common, type: \"textarea\" } as TextareaField)\n : ({ ...common, type: \"text\" } as TextField);\n\n if (generateOnPublish) {\n field.hooks = {\n ...field.hooks,\n beforeChange: [\n ...(field.hooks?.beforeChange ?? []),\n makeGenerateOnPublishHook({ kind, range }),\n ],\n };\n }\n\n return field;\n}\n"],"mappings":"AACA,SAAS,wBAAwB;AACjC,SAAS,iCAAiC;AAuBnC,SAAS,aAAa,SAAyD;AACpF,QAAM,EAAE,MAAM,MAAM,OAAO,UAAU,WAAW,OAAO,MAAM,IAAI;AACjE,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,oBAAoB,QAAQ,qBAAqB;AAEvD,QAAM,SAAS;AAAA,IACb;AAAA,IACA,GAAI,UAAU,SAAY,CAAC,IAAI,EAAE,MAAM;AAAA,IACvC,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,SAAS;AAAA,IAC7C,GAAI,cAAc,SAAY,CAAC,IAAI,EAAE,UAAU;AAAA,IAC/C,OAAO;AAAA,MACL,GAAG;AAAA,MACH,YAAY;AAAA,QACV,GAAG,OAAO;AAAA,QACV,OAAO;AAAA,UACL,MAAM,iBAAiB,uBAAuB,UAAU;AAAA,UACxD,aAAa;AAAA,YACX;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QACJ,SAAS,gBACJ,EAAE,GAAG,QAAQ,MAAM,WAAW,IAC9B,EAAE,GAAG,QAAQ,MAAM,OAAO;AAEjC,MAAI,mBAAmB;AACrB,UAAM,QAAQ;AAAA,MACZ,GAAG,MAAM;AAAA,MACT,cAAc;AAAA,QACZ,GAAI,MAAM,OAAO,gBAAgB,CAAC;AAAA,QAClC,0BAA0B,EAAE,MAAM,MAAM,CAAC;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { seoPlugin } from "./plugin";
|
|
2
|
-
export
|
|
2
|
+
export { seoTextField } from "./fields/seoTextField";
|
|
3
|
+
export type { SeoTextFieldOptions } from "./fields/seoTextField";
|
|
4
|
+
export type { ContentExtractor, SeoPluginConfig, SeoCollectionConfig, SeoFieldPaths, SeoSiteConfig, SeoGenerationConfig, } from "./types/config";
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,mBAAmB,GACpB,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { seoPlugin } from \"./plugin\";\nexport type {\n ContentExtractor,\n SeoPluginConfig,\n SeoCollectionConfig,\n SeoFieldPaths,\n SeoSiteConfig,\n} from \"./types/config\";\n"],"mappings":"AAAA,SAAS,iBAAiB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { seoPlugin } from \"./plugin\";\nexport { seoTextField } from \"./fields/seoTextField\";\nexport type { SeoTextFieldOptions } from \"./fields/seoTextField\";\nexport type {\n ContentExtractor,\n SeoPluginConfig,\n SeoCollectionConfig,\n SeoFieldPaths,\n SeoSiteConfig,\n SeoGenerationConfig,\n} from \"./types/config\";\n"],"mappings":"AAAA,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type LengthStatus = "good" | "short" | "long";
|
|
2
|
+
export type LengthUnit = "px" | "char";
|
|
3
|
+
export interface Measurement {
|
|
4
|
+
unit: LengthUnit;
|
|
5
|
+
value: number;
|
|
6
|
+
min: number;
|
|
7
|
+
max: number;
|
|
8
|
+
status: LengthStatus;
|
|
9
|
+
}
|
|
10
|
+
export interface RangeOverride {
|
|
11
|
+
min?: number;
|
|
12
|
+
max?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function measureTitle(text: string, range?: RangeOverride): Measurement;
|
|
15
|
+
export declare function measureDescription(text: string, range?: RangeOverride): Measurement;
|
|
16
|
+
//# sourceMappingURL=measure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure.d.ts","sourceRoot":"","sources":["../../src/measure/measure.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AACrD,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAQD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,WAAW,CAY7E;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,WAAW,CAYnF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getTitleProgressGuarded } from "../engine/helpers/title-progress";
|
|
2
|
+
import { DESCRIPTION_RANGE, TITLE_RANGE } from "../constants/generation";
|
|
3
|
+
function statusFor(value, min, max) {
|
|
4
|
+
if (value > max)
|
|
5
|
+
return "long";
|
|
6
|
+
if (value < min)
|
|
7
|
+
return "short";
|
|
8
|
+
return "good";
|
|
9
|
+
}
|
|
10
|
+
function measureTitle(text, range) {
|
|
11
|
+
const progress = getTitleProgressGuarded(text ?? "");
|
|
12
|
+
const min = range?.min ?? TITLE_RANGE.min;
|
|
13
|
+
const max = range?.max ?? progress.max ?? TITLE_RANGE.max;
|
|
14
|
+
return {
|
|
15
|
+
unit: "px",
|
|
16
|
+
value: progress.actual,
|
|
17
|
+
min,
|
|
18
|
+
max,
|
|
19
|
+
status: statusFor(progress.actual, min, max)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function measureDescription(text, range) {
|
|
23
|
+
const value = (text ?? "").length;
|
|
24
|
+
const min = range?.min ?? DESCRIPTION_RANGE.min;
|
|
25
|
+
const max = range?.max ?? DESCRIPTION_RANGE.max;
|
|
26
|
+
return {
|
|
27
|
+
unit: "char",
|
|
28
|
+
value,
|
|
29
|
+
min,
|
|
30
|
+
max,
|
|
31
|
+
status: statusFor(value, min, max)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
measureDescription,
|
|
36
|
+
measureTitle
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=measure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/measure/measure.ts"],"sourcesContent":["import { getTitleProgressGuarded } from \"../engine/helpers/title-progress\";\nimport { DESCRIPTION_RANGE, TITLE_RANGE } from \"../constants/generation\";\n\nexport type LengthStatus = \"good\" | \"short\" | \"long\";\nexport type LengthUnit = \"px\" | \"char\";\n\nexport interface Measurement {\n unit: LengthUnit;\n value: number;\n min: number;\n max: number;\n status: LengthStatus;\n}\n\nexport interface RangeOverride {\n min?: number;\n max?: number;\n}\n\nfunction statusFor(value: number, min: number, max: number): LengthStatus {\n if (value > max) return \"long\";\n if (value < min) return \"short\";\n return \"good\";\n}\n\nexport function measureTitle(text: string, range?: RangeOverride): Measurement {\n const progress = getTitleProgressGuarded(text ?? \"\");\n const min = range?.min ?? TITLE_RANGE.min;\n const max = range?.max ?? progress.max ?? TITLE_RANGE.max;\n\n return {\n unit: \"px\",\n value: progress.actual,\n min,\n max,\n status: statusFor(progress.actual, min, max),\n };\n}\n\nexport function measureDescription(text: string, range?: RangeOverride): Measurement {\n const value = (text ?? \"\").length;\n const min = range?.min ?? DESCRIPTION_RANGE.min;\n const max = range?.max ?? DESCRIPTION_RANGE.max;\n\n return {\n unit: \"char\",\n value,\n min,\n max,\n status: statusFor(value, min, max),\n };\n}\n"],"mappings":"AAAA,SAAS,+BAA+B;AACxC,SAAS,mBAAmB,mBAAmB;AAkB/C,SAAS,UAAU,OAAe,KAAa,KAA2B;AACxE,MAAI,QAAQ;AAAK,WAAO;AACxB,MAAI,QAAQ;AAAK,WAAO;AACxB,SAAO;AACT;AAEO,SAAS,aAAa,MAAc,OAAoC;AAC7E,QAAM,WAAW,wBAAwB,QAAQ,EAAE;AACnD,QAAM,MAAM,OAAO,OAAO,YAAY;AACtC,QAAM,MAAM,OAAO,OAAO,SAAS,OAAO,YAAY;AAEtD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,SAAS;AAAA,IAChB;AAAA,IACA;AAAA,IACA,QAAQ,UAAU,SAAS,QAAQ,KAAK,GAAG;AAAA,EAC7C;AACF;AAEO,SAAS,mBAAmB,MAAc,OAAoC;AACnF,QAAM,SAAS,QAAQ,IAAI;AAC3B,QAAM,MAAM,OAAO,OAAO,kBAAkB;AAC5C,QAAM,MAAM,OAAO,OAAO,kBAAkB;AAE5C,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,UAAU,OAAO,KAAK,GAAG;AAAA,EACnC;AACF;","names":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { SeoClientConfig } from "../client-config/registry";
|
|
3
|
+
export declare function SeoClientConfigProvider({ config, children, }: {
|
|
4
|
+
config: SeoClientConfig;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export default SeoClientConfigProvider;
|
|
8
|
+
//# sourceMappingURL=SeoClientConfigProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SeoClientConfigProvider.d.ts","sourceRoot":"","sources":["../../src/providers/SeoClientConfigProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,wBAAgB,uBAAuB,CAAC,EACtC,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,SAAS,CAAC;CACrB,2CAIA;AAED,eAAe,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
+
import { registerSeoClientConfig } from "../client-config/registry";
|
|
4
|
+
function SeoClientConfigProvider({
|
|
5
|
+
config,
|
|
6
|
+
children
|
|
7
|
+
}) {
|
|
8
|
+
registerSeoClientConfig(config);
|
|
9
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
10
|
+
}
|
|
11
|
+
var SeoClientConfigProvider_default = SeoClientConfigProvider;
|
|
12
|
+
export {
|
|
13
|
+
SeoClientConfigProvider,
|
|
14
|
+
SeoClientConfigProvider_default as default
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=SeoClientConfigProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/providers/SeoClientConfigProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { registerSeoClientConfig } from \"../client-config/registry\";\nimport type { SeoClientConfig } from \"../client-config/registry\";\n\nexport function SeoClientConfigProvider({\n config,\n children,\n}: {\n config: SeoClientConfig;\n children: ReactNode;\n}) {\n registerSeoClientConfig(config);\n\n return <>{children}</>;\n}\n\nexport default SeoClientConfigProvider;\n"],"mappings":";AAeS;AAZT,SAAS,+BAA+B;AAGjC,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AACF,GAGG;AACD,0BAAwB,MAAM;AAE9B,SAAO,gCAAG,UAAS;AACrB;AAEA,IAAO,kCAAQ;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiKey.d.ts","sourceRoot":"","sources":["../../../src/server/generate/apiKey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAMzF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function resolveApiKey(config) {
|
|
2
|
+
const explicit = config?.apiKey?.trim();
|
|
3
|
+
if (explicit)
|
|
4
|
+
return explicit;
|
|
5
|
+
const env = process.env.OPENAI_API_KEY?.trim();
|
|
6
|
+
return env || void 0;
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
resolveApiKey
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=apiKey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/server/generate/apiKey.ts"],"sourcesContent":["import type { SeoGenerationConfig } from \"../../types/config\";\n\nexport function resolveApiKey(config: SeoGenerationConfig | undefined): string | undefined {\n const explicit = config?.apiKey?.trim();\n if (explicit) return explicit;\n\n const env = process.env.OPENAI_API_KEY?.trim();\n return env || undefined;\n}\n"],"mappings":"AAEO,SAAS,cAAc,QAA6D;AACzF,QAAM,WAAW,QAAQ,QAAQ,KAAK;AACtC,MAAI;AAAU,WAAO;AAErB,QAAM,MAAM,QAAQ,IAAI,gBAAgB,KAAK;AAC7C,SAAO,OAAO;AAChB;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../../../src/server/generate/endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,SAAS,CAAC;AAuBxD,wBAAgB,sBAAsB,IAAI,QAAQ,CAsCjD"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getPluginConfig } from "../../config";
|
|
2
|
+
import { PLUGIN_NAME } from "../../constants";
|
|
3
|
+
import { GENERATE_ENDPOINT_PATH } from "../../constants/generation";
|
|
4
|
+
import { resolveApiKey } from "./apiKey";
|
|
5
|
+
import { generateForField } from "./generateForField";
|
|
6
|
+
function json(data, status) {
|
|
7
|
+
return Response.json(data, {
|
|
8
|
+
status,
|
|
9
|
+
headers: { "Content-Type": "application/json" }
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function createGenerateEndpoint() {
|
|
13
|
+
return {
|
|
14
|
+
path: GENERATE_ENDPOINT_PATH,
|
|
15
|
+
method: "post",
|
|
16
|
+
handler: async (req) => {
|
|
17
|
+
if (!req.user)
|
|
18
|
+
return json({ error: "Unauthorized" }, 401);
|
|
19
|
+
const config = getPluginConfig();
|
|
20
|
+
const apiKey = resolveApiKey(config.generation);
|
|
21
|
+
if (!apiKey)
|
|
22
|
+
return json({ error: "Generation is not configured" }, 503);
|
|
23
|
+
const body = await req.json?.() ?? {};
|
|
24
|
+
if (body.kind !== "title" && body.kind !== "description" || typeof body.contentHtml !== "string" || !body.range) {
|
|
25
|
+
return json({ error: "Invalid request body" }, 400);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const text = await generateForField({
|
|
29
|
+
kind: body.kind,
|
|
30
|
+
contentHtml: body.contentHtml,
|
|
31
|
+
range: body.range,
|
|
32
|
+
locale: body.locale,
|
|
33
|
+
config: config.generation ?? {},
|
|
34
|
+
apiKey
|
|
35
|
+
});
|
|
36
|
+
return json({ text }, 200);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
req.payload.logger.error(`[${PLUGIN_NAME}] generation failed: ${err.message}`);
|
|
39
|
+
return json({ error: "Generation failed" }, 502);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
createGenerateEndpoint
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/server/generate/endpoint.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest } from \"payload\";\nimport { getPluginConfig } from \"../../config\";\nimport { PLUGIN_NAME } from \"../../constants\";\nimport { GENERATE_ENDPOINT_PATH } from \"../../constants/generation\";\nimport type { LengthUnit } from \"../../measure/measure\";\nimport { resolveApiKey } from \"./apiKey\";\nimport { generateForField } from \"./generateForField\";\nimport type { SeoFieldKind } from \"./prompts\";\n\ninterface GenerateBody {\n kind?: SeoFieldKind;\n contentHtml?: string;\n locale?: string;\n range?: { min: number; max: number; unit: LengthUnit };\n}\n\nfunction json(data: unknown, status: number): Response {\n return Response.json(data, {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nexport function createGenerateEndpoint(): Endpoint {\n return {\n path: GENERATE_ENDPOINT_PATH,\n method: \"post\",\n handler: async (req: PayloadRequest): Promise<Response> => {\n if (!req.user) return json({ error: \"Unauthorized\" }, 401);\n\n const config = getPluginConfig();\n const apiKey = resolveApiKey(config.generation);\n if (!apiKey) return json({ error: \"Generation is not configured\" }, 503);\n\n const body = ((await req.json?.()) ?? {}) as GenerateBody;\n if (\n (body.kind !== \"title\" && body.kind !== \"description\") ||\n typeof body.contentHtml !== \"string\" ||\n !body.range\n ) {\n return json({ error: \"Invalid request body\" }, 400);\n }\n\n try {\n const text = await generateForField({\n kind: body.kind,\n contentHtml: body.contentHtml,\n range: body.range,\n locale: body.locale,\n config: config.generation ?? {},\n apiKey,\n });\n\n return json({ text }, 200);\n } catch (err) {\n req.payload.logger.error(`[${PLUGIN_NAME}] generation failed: ${(err as Error).message}`);\n\n return json({ error: \"Generation failed\" }, 502);\n }\n },\n };\n}\n"],"mappings":"AACA,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,8BAA8B;AAEvC,SAAS,qBAAqB;AAC9B,SAAS,wBAAwB;AAUjC,SAAS,KAAK,MAAe,QAA0B;AACrD,SAAO,SAAS,KAAK,MAAM;AAAA,IACzB;AAAA,IACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAEO,SAAS,yBAAmC;AACjD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,OAAO,QAA2C;AACzD,UAAI,CAAC,IAAI;AAAM,eAAO,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG;AAEzD,YAAM,SAAS,gBAAgB;AAC/B,YAAM,SAAS,cAAc,OAAO,UAAU;AAC9C,UAAI,CAAC;AAAQ,eAAO,KAAK,EAAE,OAAO,+BAA+B,GAAG,GAAG;AAEvE,YAAM,OAAS,MAAM,IAAI,OAAO,KAAM,CAAC;AACvC,UACG,KAAK,SAAS,WAAW,KAAK,SAAS,iBACxC,OAAO,KAAK,gBAAgB,YAC5B,CAAC,KAAK,OACN;AACA,eAAO,KAAK,EAAE,OAAO,uBAAuB,GAAG,GAAG;AAAA,MACpD;AAEA,UAAI;AACF,cAAM,OAAO,MAAM,iBAAiB;AAAA,UAClC,MAAM,KAAK;AAAA,UACX,aAAa,KAAK;AAAA,UAClB,OAAO,KAAK;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb,QAAQ,OAAO,cAAc,CAAC;AAAA,UAC9B;AAAA,QACF,CAAC;AAED,eAAO,KAAK,EAAE,KAAK,GAAG,GAAG;AAAA,MAC3B,SAAS,KAAK;AACZ,YAAI,QAAQ,OAAO,MAAM,IAAI,WAAW,wBAAyB,IAAc,OAAO,EAAE;AAExF,eAAO,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SeoGenerationConfig } from "../../types/config";
|
|
2
|
+
import type { LengthUnit } from "../../measure/measure";
|
|
3
|
+
import type { SeoFieldKind } from "./prompts";
|
|
4
|
+
export interface GenerateForFieldArgs {
|
|
5
|
+
kind: SeoFieldKind;
|
|
6
|
+
contentHtml: string;
|
|
7
|
+
range: {
|
|
8
|
+
min: number;
|
|
9
|
+
max: number;
|
|
10
|
+
unit: LengthUnit;
|
|
11
|
+
};
|
|
12
|
+
locale?: string;
|
|
13
|
+
config: SeoGenerationConfig;
|
|
14
|
+
apiKey: string;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}
|
|
17
|
+
export declare function generateForField(args: GenerateForFieldArgs): Promise<string>;
|
|
18
|
+
//# sourceMappingURL=generateForField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateForField.d.ts","sourceRoot":"","sources":["../../../src/server/generate/generateForField.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAG9C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,UAAU,CAAC;KAClB,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAoBD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBlF"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { DEFAULT_MAX_CONTENT_CHARS, DEFAULT_MODEL } from "../../constants/generation";
|
|
2
|
+
import { callOpenAIChat } from "./openai";
|
|
3
|
+
import { buildPrompt } from "./prompts";
|
|
4
|
+
const QUOTE_PAIRS = [
|
|
5
|
+
['"', '"'],
|
|
6
|
+
["'", "'"],
|
|
7
|
+
["\u201C", "\u201D"]
|
|
8
|
+
];
|
|
9
|
+
function stripQuotes(s) {
|
|
10
|
+
const t = s.trim();
|
|
11
|
+
if (t.length < 2)
|
|
12
|
+
return t;
|
|
13
|
+
for (const [open, close] of QUOTE_PAIRS) {
|
|
14
|
+
if (t.startsWith(open) && t.endsWith(close))
|
|
15
|
+
return t.slice(1, -1).trim();
|
|
16
|
+
}
|
|
17
|
+
return t;
|
|
18
|
+
}
|
|
19
|
+
async function generateForField(args) {
|
|
20
|
+
const max = args.config.maxContentChars ?? DEFAULT_MAX_CONTENT_CHARS;
|
|
21
|
+
const contentHtml = args.contentHtml.slice(0, max);
|
|
22
|
+
const { system, user } = buildPrompt({
|
|
23
|
+
kind: args.kind,
|
|
24
|
+
contentHtml,
|
|
25
|
+
range: args.range,
|
|
26
|
+
locale: args.locale,
|
|
27
|
+
config: args.config
|
|
28
|
+
});
|
|
29
|
+
const text = await callOpenAIChat({
|
|
30
|
+
apiKey: args.apiKey,
|
|
31
|
+
model: args.config.model ?? DEFAULT_MODEL,
|
|
32
|
+
system,
|
|
33
|
+
user,
|
|
34
|
+
signal: args.signal
|
|
35
|
+
});
|
|
36
|
+
return stripQuotes(text);
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
generateForField
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=generateForField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/server/generate/generateForField.ts"],"sourcesContent":["import { DEFAULT_MAX_CONTENT_CHARS, DEFAULT_MODEL } from \"../../constants/generation\";\nimport type { SeoGenerationConfig } from \"../../types/config\";\nimport type { LengthUnit } from \"../../measure/measure\";\nimport { callOpenAIChat } from \"./openai\";\nimport type { SeoFieldKind } from \"./prompts\";\nimport { buildPrompt } from \"./prompts\";\n\nexport interface GenerateForFieldArgs {\n kind: SeoFieldKind;\n contentHtml: string;\n range: {\n min: number;\n max: number;\n unit: LengthUnit;\n };\n locale?: string;\n config: SeoGenerationConfig;\n apiKey: string;\n signal?: AbortSignal;\n}\n\nconst QUOTE_PAIRS: ReadonlyArray<readonly [string, string]> = [\n ['\"', '\"'],\n [\"'\", \"'\"],\n [\"“\", \"”\"],\n];\n\nfunction stripQuotes(s: string): string {\n const t = s.trim();\n\n if (t.length < 2) return t;\n\n for (const [open, close] of QUOTE_PAIRS) {\n if (t.startsWith(open) && t.endsWith(close)) return t.slice(1, -1).trim();\n }\n\n return t;\n}\n\nexport async function generateForField(args: GenerateForFieldArgs): Promise<string> {\n const max = args.config.maxContentChars ?? DEFAULT_MAX_CONTENT_CHARS;\n const contentHtml = args.contentHtml.slice(0, max);\n\n const { system, user } = buildPrompt({\n kind: args.kind,\n contentHtml,\n range: args.range,\n locale: args.locale,\n config: args.config,\n });\n\n const text = await callOpenAIChat({\n apiKey: args.apiKey,\n model: args.config.model ?? DEFAULT_MODEL,\n system,\n user,\n signal: args.signal,\n });\n\n return stripQuotes(text);\n}\n"],"mappings":"AAAA,SAAS,2BAA2B,qBAAqB;AAGzD,SAAS,sBAAsB;AAE/B,SAAS,mBAAmB;AAgB5B,MAAM,cAAwD;AAAA,EAC5D,CAAC,KAAK,GAAG;AAAA,EACT,CAAC,KAAK,GAAG;AAAA,EACT,CAAC,UAAK,QAAG;AACX;AAEA,SAAS,YAAY,GAAmB;AACtC,QAAM,IAAI,EAAE,KAAK;AAEjB,MAAI,EAAE,SAAS;AAAG,WAAO;AAEzB,aAAW,CAAC,MAAM,KAAK,KAAK,aAAa;AACvC,QAAI,EAAE,WAAW,IAAI,KAAK,EAAE,SAAS,KAAK;AAAG,aAAO,EAAE,MAAM,GAAG,EAAE,EAAE,KAAK;AAAA,EAC1E;AAEA,SAAO;AACT;AAEA,eAAsB,iBAAiB,MAA6C;AAClF,QAAM,MAAM,KAAK,OAAO,mBAAmB;AAC3C,QAAM,cAAc,KAAK,YAAY,MAAM,GAAG,GAAG;AAEjD,QAAM,EAAE,QAAQ,KAAK,IAAI,YAAY;AAAA,IACnC,MAAM,KAAK;AAAA,IACX;AAAA,IACA,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,QAAQ,KAAK;AAAA,EACf,CAAC;AAED,QAAM,OAAO,MAAM,eAAe;AAAA,IAChC,QAAQ,KAAK;AAAA,IACb,OAAO,KAAK,OAAO,SAAS;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,QAAQ,KAAK;AAAA,EACf,CAAC;AAED,SAAO,YAAY,IAAI;AACzB;","names":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface OpenAIChatArgs {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
model: string;
|
|
4
|
+
system: string;
|
|
5
|
+
user: string;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
export declare function callOpenAIChat({ apiKey, model, system, user, signal, }: OpenAIChatArgs): Promise<string>;
|
|
9
|
+
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../../src/server/generate/openai.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAQD,wBAAsB,cAAc,CAAC,EACnC,MAAM,EACN,KAAK,EACL,MAAM,EACN,IAAI,EACJ,MAAM,GACP,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAqClC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const ENDPOINT = "https://api.openai.com/v1/chat/completions";
|
|
2
|
+
async function callOpenAIChat({
|
|
3
|
+
apiKey,
|
|
4
|
+
model,
|
|
5
|
+
system,
|
|
6
|
+
user,
|
|
7
|
+
signal
|
|
8
|
+
}) {
|
|
9
|
+
const res = await fetch(ENDPOINT, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: {
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
Authorization: `Bearer ${apiKey}`
|
|
14
|
+
},
|
|
15
|
+
body: JSON.stringify({
|
|
16
|
+
model,
|
|
17
|
+
temperature: 0.5,
|
|
18
|
+
messages: [
|
|
19
|
+
{ role: "system", content: system },
|
|
20
|
+
{ role: "user", content: user }
|
|
21
|
+
]
|
|
22
|
+
}),
|
|
23
|
+
signal
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
let kind = "";
|
|
27
|
+
try {
|
|
28
|
+
const errBody = await res.json();
|
|
29
|
+
kind = errBody.error?.code ?? errBody.error?.type ?? "";
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`OpenAI request failed (${res.status}${kind ? `, ${kind}` : ""})`);
|
|
33
|
+
}
|
|
34
|
+
const body = await res.json();
|
|
35
|
+
const text = body.choices?.[0]?.message?.content?.trim();
|
|
36
|
+
if (!text)
|
|
37
|
+
throw new Error("OpenAI returned an empty completion");
|
|
38
|
+
return text;
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
callOpenAIChat
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/server/generate/openai.ts"],"sourcesContent":["export interface OpenAIChatArgs {\n apiKey: string;\n model: string;\n system: string;\n user: string;\n signal?: AbortSignal;\n}\n\ninterface ChatCompletionResponse {\n choices?: Array<{ message?: { content?: string } }>;\n}\n\nconst ENDPOINT = \"https://api.openai.com/v1/chat/completions\";\n\nexport async function callOpenAIChat({\n apiKey,\n model,\n system,\n user,\n signal,\n}: OpenAIChatArgs): Promise<string> {\n const res = await fetch(ENDPOINT, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model,\n temperature: 0.5,\n messages: [\n { role: \"system\", content: system },\n { role: \"user\", content: user },\n ],\n }),\n signal,\n });\n\n if (!res.ok) {\n let kind = \"\";\n\n try {\n const errBody = (await res.json()) as { error?: { type?: string; code?: string } };\n kind = errBody.error?.code ?? errBody.error?.type ?? \"\";\n } catch {\n // non-JSON error body — ignore it; do not surface raw text\n }\n\n throw new Error(`OpenAI request failed (${res.status}${kind ? `, ${kind}` : \"\"})`);\n }\n\n const body = (await res.json()) as ChatCompletionResponse;\n\n const text = body.choices?.[0]?.message?.content?.trim();\n if (!text) throw new Error(\"OpenAI returned an empty completion\");\n\n return text;\n}\n"],"mappings":"AAYA,MAAM,WAAW;AAEjB,eAAsB,eAAe;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAoC;AAClC,QAAM,MAAM,MAAM,MAAM,UAAU;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,UAAU,MAAM;AAAA,IACjC;AAAA,IACA,MAAM,KAAK,UAAU;AAAA,MACnB;AAAA,MACA,aAAa;AAAA,MACb,UAAU;AAAA,QACR,EAAE,MAAM,UAAU,SAAS,OAAO;AAAA,QAClC,EAAE,MAAM,QAAQ,SAAS,KAAK;AAAA,MAChC;AAAA,IACF,CAAC;AAAA,IACD;AAAA,EACF,CAAC;AAED,MAAI,CAAC,IAAI,IAAI;AACX,QAAI,OAAO;AAEX,QAAI;AACF,YAAM,UAAW,MAAM,IAAI,KAAK;AAChC,aAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,QAAQ;AAAA,IACvD,QAAQ;AAAA,IAER;AAEA,UAAM,IAAI,MAAM,0BAA0B,IAAI,MAAM,GAAG,OAAO,KAAK,IAAI,KAAK,EAAE,GAAG;AAAA,EACnF;AAEA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,QAAM,OAAO,KAAK,UAAU,CAAC,GAAG,SAAS,SAAS,KAAK;AACvD,MAAI,CAAC;AAAM,UAAM,IAAI,MAAM,qCAAqC;AAEhE,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SeoGenerationConfig } from "../../types/config";
|
|
2
|
+
import type { LengthUnit } from "../../measure/measure";
|
|
3
|
+
export type SeoFieldKind = "title" | "description";
|
|
4
|
+
export interface PromptArgs {
|
|
5
|
+
kind: SeoFieldKind;
|
|
6
|
+
contentHtml: string;
|
|
7
|
+
range: {
|
|
8
|
+
min: number;
|
|
9
|
+
max: number;
|
|
10
|
+
unit: LengthUnit;
|
|
11
|
+
};
|
|
12
|
+
locale?: string;
|
|
13
|
+
config: SeoGenerationConfig;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildPrompt(args: PromptArgs): {
|
|
16
|
+
system: string;
|
|
17
|
+
user: string;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../src/server/generate/prompts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,aAAa,CAAC;AAEnD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,UAAU,CAAC;KAClB,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AA2BD,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAO9E"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AVG_GLYPH_PX } from "../../constants/generation";
|
|
2
|
+
function charRangeFromPx(minPx, maxPx) {
|
|
3
|
+
return {
|
|
4
|
+
min: Math.ceil(minPx / AVG_GLYPH_PX),
|
|
5
|
+
max: Math.floor(maxPx / AVG_GLYPH_PX)
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
function defaultSystem(kind, range, locale) {
|
|
9
|
+
const { min, max } = range.unit === "px" ? charRangeFromPx(range.min, range.max) : range;
|
|
10
|
+
const what = kind === "title" ? "an SEO meta title for the web page described by the content below" : "an SEO meta description for the web page described by the content below";
|
|
11
|
+
const lines = [
|
|
12
|
+
`You write ${what}.`,
|
|
13
|
+
`The text MUST be strictly between ${min} and ${max} characters long \u2014 never fewer than ${min} and never more than ${max}, including spaces.`,
|
|
14
|
+
"Summarize the page's actual subject; do not invent facts not present in the content.",
|
|
15
|
+
"Return ONLY the text \u2014 no quotes, no markdown, no labels, no trailing punctuation unless natural."
|
|
16
|
+
];
|
|
17
|
+
if (locale)
|
|
18
|
+
lines.push(`Write in the locale "${locale}".`);
|
|
19
|
+
return lines.join(" ");
|
|
20
|
+
}
|
|
21
|
+
function buildPrompt(args) {
|
|
22
|
+
const override = args.kind === "title" ? args.config.titlePrompt : args.config.descriptionPrompt;
|
|
23
|
+
const system = override ?? defaultSystem(args.kind, args.range, args.locale);
|
|
24
|
+
const user = `Page content:
|
|
25
|
+
|
|
26
|
+
${args.contentHtml}`;
|
|
27
|
+
return { system, user };
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
buildPrompt
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/server/generate/prompts.ts"],"sourcesContent":["import { AVG_GLYPH_PX } from \"../../constants/generation\";\nimport type { SeoGenerationConfig } from \"../../types/config\";\nimport type { LengthUnit } from \"../../measure/measure\";\n\nexport type SeoFieldKind = \"title\" | \"description\";\n\nexport interface PromptArgs {\n kind: SeoFieldKind;\n contentHtml: string;\n range: {\n min: number;\n max: number;\n unit: LengthUnit;\n };\n locale?: string;\n config: SeoGenerationConfig;\n}\n\nfunction charRangeFromPx(minPx: number, maxPx: number): { min: number; max: number } {\n return {\n min: Math.ceil(minPx / AVG_GLYPH_PX),\n max: Math.floor(maxPx / AVG_GLYPH_PX),\n };\n}\n\nfunction defaultSystem(kind: SeoFieldKind, range: PromptArgs[\"range\"], locale?: string): string {\n const { min, max } = range.unit === \"px\" ? charRangeFromPx(range.min, range.max) : range;\n const what =\n kind === \"title\"\n ? \"an SEO meta title for the web page described by the content below\"\n : \"an SEO meta description for the web page described by the content below\";\n const lines = [\n `You write ${what}.`,\n `The text MUST be strictly between ${min} and ${max} characters long — never fewer than ${min} and never more than ${max}, including spaces.`,\n \"Summarize the page's actual subject; do not invent facts not present in the content.\",\n \"Return ONLY the text — no quotes, no markdown, no labels, no trailing punctuation unless natural.\",\n ];\n\n if (locale) lines.push(`Write in the locale \"${locale}\".`);\n\n return lines.join(\" \");\n}\n\nexport function buildPrompt(args: PromptArgs): { system: string; user: string } {\n const override = args.kind === \"title\" ? args.config.titlePrompt : args.config.descriptionPrompt;\n\n const system = override ?? defaultSystem(args.kind, args.range, args.locale);\n const user = `Page content:\\n\\n${args.contentHtml}`;\n\n return { system, user };\n}\n"],"mappings":"AAAA,SAAS,oBAAoB;AAkB7B,SAAS,gBAAgB,OAAe,OAA6C;AACnF,SAAO;AAAA,IACL,KAAK,KAAK,KAAK,QAAQ,YAAY;AAAA,IACnC,KAAK,KAAK,MAAM,QAAQ,YAAY;AAAA,EACtC;AACF;AAEA,SAAS,cAAc,MAAoB,OAA4B,QAAyB;AAC9F,QAAM,EAAE,KAAK,IAAI,IAAI,MAAM,SAAS,OAAO,gBAAgB,MAAM,KAAK,MAAM,GAAG,IAAI;AACnF,QAAM,OACJ,SAAS,UACL,sEACA;AACN,QAAM,QAAQ;AAAA,IACZ,aAAa,IAAI;AAAA,IACjB,qCAAqC,GAAG,QAAQ,GAAG,4CAAuC,GAAG,wBAAwB,GAAG;AAAA,IACxH;AAAA,IACA;AAAA,EACF;AAEA,MAAI;AAAQ,UAAM,KAAK,wBAAwB,MAAM,IAAI;AAEzD,SAAO,MAAM,KAAK,GAAG;AACvB;AAEO,SAAS,YAAY,MAAoD;AAC9E,QAAM,WAAW,KAAK,SAAS,UAAU,KAAK,OAAO,cAAc,KAAK,OAAO;AAE/E,QAAM,SAAS,YAAY,cAAc,KAAK,MAAM,KAAK,OAAO,KAAK,MAAM;AAC3E,QAAM,OAAO;AAAA;AAAA,EAAoB,KAAK,WAAW;AAEjD,SAAO,EAAE,QAAQ,KAAK;AACxB;","names":[]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Payload } from "payload";
|
|
2
|
+
import type { DocQuery, DocStore } from "../../types/config";
|
|
3
|
+
export declare function createServerResolveDocs(payload: Payload, locale: string | undefined): (queries: DocQuery[]) => Promise<DocStore>;
|
|
4
|
+
//# sourceMappingURL=serverResolveDocs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverResolveDocs.d.ts","sourceRoot":"","sources":["../../../src/server/generate/serverResolveDocs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAqC7D,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAW5C"}
|