@isardsat/editorial-server 6.9.0 → 6.11.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/dist/lib/schema.d.ts +1 -0
- package/dist/lib/storage copy.d.ts +46 -0
- package/dist/lib/storage copy.js +137 -0
- package/dist/lib/storage.d.ts +1 -0
- package/dist/routes/data.js +2 -2
- package/package.json +3 -3
- package/dist/lib/logger.d.ts +0 -7
- package/dist/lib/logger.js +0 -26
package/dist/lib/schema.d.ts
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { EditorialData, EditorialDataObjectWithType } from "@isardsat/editorial-common";
|
|
2
|
+
export declare function createStorage(dataDirectory: string): {
|
|
3
|
+
getSchema: () => Promise<Record<string, {
|
|
4
|
+
displayName: string;
|
|
5
|
+
fields: Record<string, {
|
|
6
|
+
[x: string]: unknown;
|
|
7
|
+
type: "string" | "number" | "boolean" | "url" | "date" | "datetime" | "markdown" | "color" | "select";
|
|
8
|
+
displayName: string;
|
|
9
|
+
optional: boolean;
|
|
10
|
+
displayExtra?: string | undefined;
|
|
11
|
+
placeholder?: string | undefined;
|
|
12
|
+
showInSummary?: boolean | undefined;
|
|
13
|
+
}>;
|
|
14
|
+
singleton?: boolean | undefined;
|
|
15
|
+
}>>;
|
|
16
|
+
getContent: ({ production, }: {
|
|
17
|
+
production?: boolean;
|
|
18
|
+
}) => Promise<EditorialData>;
|
|
19
|
+
getLocalisationMessages: (langCode: string) => Promise<any>;
|
|
20
|
+
saveLocalisationMessages: (messages: any) => Promise<boolean>;
|
|
21
|
+
createItem: (item: EditorialDataObjectWithType) => Promise<{
|
|
22
|
+
[x: string]: unknown;
|
|
23
|
+
id: string;
|
|
24
|
+
isDraft: boolean;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
}>;
|
|
28
|
+
updateItem: (item: EditorialDataObjectWithType) => Promise<{
|
|
29
|
+
[x: string]: unknown;
|
|
30
|
+
id: string;
|
|
31
|
+
isDraft: boolean;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
updatedAt: string;
|
|
34
|
+
}>;
|
|
35
|
+
deleteItem: (item: EditorialDataObjectWithType) => Promise<Record<string, Record<string, {
|
|
36
|
+
[x: string]: unknown;
|
|
37
|
+
id: string;
|
|
38
|
+
isDraft: boolean;
|
|
39
|
+
createdAt: string;
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
}>>>;
|
|
42
|
+
saveContent: ({ production }: {
|
|
43
|
+
production?: boolean;
|
|
44
|
+
}) => Promise<boolean>;
|
|
45
|
+
};
|
|
46
|
+
export type Storage = ReturnType<typeof createStorage>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { EditorialDataItemSchema, EditorialDataSchema, EditorialSchemaSchema, } from "@isardsat/editorial-common";
|
|
2
|
+
import { readFile, readdir } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { parse } from "yaml";
|
|
5
|
+
import { writeFileSafe } from "./utils/fs.js";
|
|
6
|
+
export function createStorage(dataDirectory) {
|
|
7
|
+
const schemaPath = join(dataDirectory, "schema.yaml");
|
|
8
|
+
const dataPath = join(dataDirectory, "data.json");
|
|
9
|
+
const dataProdPath = join(dataDirectory, "data.prod.json");
|
|
10
|
+
const dataExtractedPath = join(dataDirectory, "data.messages.json");
|
|
11
|
+
async function getSchema() {
|
|
12
|
+
const schemaFile = await readFile(schemaPath, "utf-8").then((value) => parse(value));
|
|
13
|
+
const schema = EditorialSchemaSchema.parse(schemaFile);
|
|
14
|
+
return schema;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* TODO: This should ideally cache the result of reading the file until an update occurs.
|
|
18
|
+
*/
|
|
19
|
+
async function getContent({ production, }) {
|
|
20
|
+
const content = await readFile(production ? dataProdPath : dataPath, "utf-8").then((value) => EditorialDataSchema.parse(JSON.parse(value)));
|
|
21
|
+
return content;
|
|
22
|
+
}
|
|
23
|
+
async function getLocalisationMessages(langCode) {
|
|
24
|
+
return await readFile(join(dataDirectory, "locales", "messages", `${langCode}.json`), "utf-8").then((value) => JSON.parse(value));
|
|
25
|
+
}
|
|
26
|
+
async function saveContent({ production }) {
|
|
27
|
+
const content = await getContent({ production: false });
|
|
28
|
+
/** Do not save any items that have `isDraft` */
|
|
29
|
+
if (production) {
|
|
30
|
+
const filteredContent = {};
|
|
31
|
+
for (const [itemType, items] of Object.entries(content)) {
|
|
32
|
+
filteredContent[itemType] = {};
|
|
33
|
+
const typedItems = items;
|
|
34
|
+
for (const [itemId, item] of Object.entries(typedItems)) {
|
|
35
|
+
if (!item.isDraft) {
|
|
36
|
+
filteredContent[itemType][itemId] = item;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
await writeFileSafe(dataProdPath, JSON.stringify(EditorialDataSchema.parse(filteredContent), null, 2));
|
|
41
|
+
const usedKeys = collectUsedMessageKeys(filteredContent);
|
|
42
|
+
console.log("🚀 ~ saveContent ~ usedKeys:", usedKeys);
|
|
43
|
+
const localesDir = join(dataDirectory, "locales", "messages");
|
|
44
|
+
const locales = await getAllLocalesMessages(localesDir);
|
|
45
|
+
for (const locale of locales) {
|
|
46
|
+
const pruned = pruneMessages(locale.messages, usedKeys);
|
|
47
|
+
await writeFileSafe(locale.path, JSON.stringify(pruned, null, 2));
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
await writeFileSafe(dataPath, JSON.stringify(EditorialDataSchema.parse(content), null, 2));
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
async function saveLocalisationMessages(messages) {
|
|
55
|
+
await writeFileSafe(dataExtractedPath, JSON.stringify(messages, null, 2));
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
async function createItem(item) {
|
|
59
|
+
const content = await getContent({ production: false });
|
|
60
|
+
content[item.type] = content[item.type] ?? {};
|
|
61
|
+
const parsedItem = EditorialDataItemSchema.parse(item);
|
|
62
|
+
content[item.type][item.id] = parsedItem;
|
|
63
|
+
// TODO: Use superjson to safely encode different types.
|
|
64
|
+
await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
|
|
65
|
+
return parsedItem;
|
|
66
|
+
}
|
|
67
|
+
async function updateItem(item) {
|
|
68
|
+
const content = await getContent({ production: false });
|
|
69
|
+
content[item.type] = content[item.type] ?? {};
|
|
70
|
+
const oldItem = content[item.type][item.id];
|
|
71
|
+
const newItem = EditorialDataItemSchema.parse({
|
|
72
|
+
...oldItem,
|
|
73
|
+
...item,
|
|
74
|
+
updatedAt: new Date().toISOString(),
|
|
75
|
+
});
|
|
76
|
+
content[item.type][item.id] = newItem;
|
|
77
|
+
// TODO: Use superjson to safely encode different types.
|
|
78
|
+
await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
|
|
79
|
+
return newItem;
|
|
80
|
+
}
|
|
81
|
+
async function deleteItem(item) {
|
|
82
|
+
const content = await getContent({ production: false });
|
|
83
|
+
delete content[item.type][item.id];
|
|
84
|
+
// TODO: Use superjson to safely encode different types.
|
|
85
|
+
await writeFileSafe(dataPath, JSON.stringify(content, null, 2));
|
|
86
|
+
return content;
|
|
87
|
+
}
|
|
88
|
+
function collectFromItem(value, keys) {
|
|
89
|
+
console.log("🚀 ~ collectFromItem ~ typeof value:", typeof value);
|
|
90
|
+
if (!value)
|
|
91
|
+
return;
|
|
92
|
+
if (typeof value === "object") {
|
|
93
|
+
if (typeof value.i18nKey === "string") {
|
|
94
|
+
keys.add(value.i18nKey);
|
|
95
|
+
}
|
|
96
|
+
for (const v of Object.values(value)) {
|
|
97
|
+
collectFromItem(v, keys);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function collectUsedMessageKeys(content) {
|
|
102
|
+
const keys = new Set();
|
|
103
|
+
for (const items of Object.values(content)) {
|
|
104
|
+
for (const item of Object.values(items)) {
|
|
105
|
+
collectFromItem(item, keys);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return keys;
|
|
109
|
+
}
|
|
110
|
+
async function getAllLocalesMessages(dir) {
|
|
111
|
+
const files = await readdir(dir);
|
|
112
|
+
return Promise.all(files.map(async (file) => ({
|
|
113
|
+
file,
|
|
114
|
+
path: join(dir, file),
|
|
115
|
+
messages: JSON.parse(await readFile(join(dir, file), "utf-8")),
|
|
116
|
+
})));
|
|
117
|
+
}
|
|
118
|
+
function pruneMessages(messages, usedKeys) {
|
|
119
|
+
const pruned = {};
|
|
120
|
+
for (const key of usedKeys) {
|
|
121
|
+
if (messages[key]) {
|
|
122
|
+
pruned[key] = messages[key];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return pruned;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
getSchema,
|
|
129
|
+
getContent,
|
|
130
|
+
getLocalisationMessages,
|
|
131
|
+
saveLocalisationMessages,
|
|
132
|
+
createItem,
|
|
133
|
+
updateItem,
|
|
134
|
+
deleteItem,
|
|
135
|
+
saveContent,
|
|
136
|
+
};
|
|
137
|
+
}
|
package/dist/lib/storage.d.ts
CHANGED
package/dist/routes/data.js
CHANGED
|
@@ -253,8 +253,8 @@ export function createDataRoutes(config, storage) {
|
|
|
253
253
|
if (lang) {
|
|
254
254
|
const messages = await storage.getLocalisationMessages(lang);
|
|
255
255
|
for (const [key, message] of Object.entries(messages)) {
|
|
256
|
-
const [, typeKey, fieldKey] = key.split(".");
|
|
257
|
-
if (typeKey === id) {
|
|
256
|
+
const [contentKey, typeKey, fieldKey] = key.split(".");
|
|
257
|
+
if (contentKey === itemType && typeKey === id) {
|
|
258
258
|
item[fieldKey] = message.defaultMessage;
|
|
259
259
|
}
|
|
260
260
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isardsat/editorial-server",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"hono": "^4.9.8",
|
|
15
15
|
"yaml": "^2.8.1",
|
|
16
16
|
"zod": "^4.1.11",
|
|
17
|
-
"@isardsat/editorial-
|
|
18
|
-
"@isardsat/editorial-
|
|
17
|
+
"@isardsat/editorial-common": "^6.11.0",
|
|
18
|
+
"@isardsat/editorial-admin": "^6.11.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@tsconfig/node22": "^22.0.0",
|
package/dist/lib/logger.d.ts
DELETED
package/dist/lib/logger.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import pino, {} from "pino";
|
|
2
|
-
export function createLogger(config = {}) {
|
|
3
|
-
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
4
|
-
const level = config.level ?? (isDevelopment ? "debug" : "info");
|
|
5
|
-
const pretty = config.pretty ?? isDevelopment;
|
|
6
|
-
const transport = pretty
|
|
7
|
-
? {
|
|
8
|
-
target: "pino-pretty",
|
|
9
|
-
options: {
|
|
10
|
-
colorize: true,
|
|
11
|
-
translateTime: "HH:MM:ss Z",
|
|
12
|
-
ignore: "pid,hostname",
|
|
13
|
-
},
|
|
14
|
-
}
|
|
15
|
-
: undefined;
|
|
16
|
-
return pino({
|
|
17
|
-
name: config.name ?? "editorial-server",
|
|
18
|
-
level,
|
|
19
|
-
transport,
|
|
20
|
-
serializers: {
|
|
21
|
-
err: pino.stdSerializers.err,
|
|
22
|
-
req: pino.stdSerializers.req,
|
|
23
|
-
res: pino.stdSerializers.res,
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
}
|