@blinkk/root-cms 2.4.9 → 2.5.1
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 +17 -3
- package/dist/ai-OZY3JXDH.js +19 -0
- package/dist/altText-RDKJNVGH.js +7 -0
- package/dist/app.js +9 -269
- package/dist/chunk-KTUENARU.js +193 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-NUUABQRN.js +1944 -0
- package/dist/chunk-RIJF2AHU.js +136 -0
- package/dist/chunk-RNDSZKAW.js +171 -0
- package/dist/chunk-RYF3UTHQ.js +302 -0
- package/dist/chunk-T5UK2H24.js +419 -0
- package/dist/cli.js +54 -362
- package/dist/{client-pSzji9ZN.d.ts → client-ROwBDNeR.d.ts} +39 -2
- package/dist/client.d.ts +2 -1
- package/dist/client.js +15 -1509
- package/dist/core.d.ts +3 -3
- package/dist/core.js +33 -1519
- package/dist/edit-XX3LAGK6.js +7 -0
- package/dist/functions.js +8 -1632
- package/dist/generate-types-TQBCE2SG.js +9 -0
- package/dist/plugin.d.ts +2 -1
- package/dist/plugin.js +138 -2519
- package/dist/project.d.ts +1 -1
- package/dist/project.js +6 -76
- package/dist/richtext.js +2 -0
- package/dist/{schema-Bux4PrV2.d.ts → schema-BKfPP_s9.d.ts} +78 -2
- package/dist/ui/ui.css +1 -1
- package/dist/ui/ui.js +234 -154
- package/dist/ui/ui.js.LEGAL.txt +126 -102
- package/package.json +5 -5
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RootCMSClient,
|
|
3
|
+
getCmsPlugin
|
|
4
|
+
} from "./chunk-NUUABQRN.js";
|
|
5
|
+
|
|
6
|
+
// core/versions.ts
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { Timestamp } from "firebase-admin/firestore";
|
|
9
|
+
import glob from "tiny-glob";
|
|
10
|
+
var DOCUMENT_SAVE_OFFSET = 5 * 60 * 1e3;
|
|
11
|
+
var VersionsService = class {
|
|
12
|
+
constructor(rootConfig) {
|
|
13
|
+
this.rootConfig = rootConfig;
|
|
14
|
+
const cmsPlugin = getCmsPlugin(rootConfig);
|
|
15
|
+
const cmsPluginOptions = cmsPlugin.getConfig();
|
|
16
|
+
const projectId = cmsPluginOptions.id || "default";
|
|
17
|
+
this.projectId = projectId;
|
|
18
|
+
this.db = cmsPlugin.getFirestore();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Saves a version of all documents that have been edited since the last run.
|
|
22
|
+
*/
|
|
23
|
+
async saveVersions() {
|
|
24
|
+
const lastRun = await this.getLastRun();
|
|
25
|
+
const lastRunWithOffset = lastRun === 0 ? lastRun : lastRun - DOCUMENT_SAVE_OFFSET;
|
|
26
|
+
const changedDocs = await this.getDocsModifiedAfter(lastRunWithOffset);
|
|
27
|
+
const now = Timestamp.now().toMillis();
|
|
28
|
+
const versions = changedDocs.filter((doc) => {
|
|
29
|
+
const modifiedAt = doc.sys.modifiedAt.toMillis();
|
|
30
|
+
return modifiedAt <= now - DOCUMENT_SAVE_OFFSET;
|
|
31
|
+
});
|
|
32
|
+
if (versions.length > 0) {
|
|
33
|
+
this.saveVersionsToFirestore(versions);
|
|
34
|
+
}
|
|
35
|
+
this.saveLastRun(now);
|
|
36
|
+
}
|
|
37
|
+
async saveVersionsToFirestore(versions) {
|
|
38
|
+
const batch = this.db.batch();
|
|
39
|
+
versions.forEach((version) => {
|
|
40
|
+
if (!version.collection || !version.slug || !version.sys?.modifiedAt) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const modifiedAtMillis = version.sys.modifiedAt.toMillis();
|
|
44
|
+
const versionPath = `Projects/${this.projectId}/Collections/${version.collection}/Drafts/${version.slug}/Versions/${modifiedAtMillis}`;
|
|
45
|
+
console.log(versionPath);
|
|
46
|
+
const versionRef = this.db.doc(versionPath);
|
|
47
|
+
batch.set(versionRef, version);
|
|
48
|
+
});
|
|
49
|
+
await batch.commit();
|
|
50
|
+
console.log(`versions: saved ${versions.length} versions`);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Returns the last time (in millis) saveVersions() was run, or 0 if has
|
|
54
|
+
* never been run.
|
|
55
|
+
*/
|
|
56
|
+
async getLastRun() {
|
|
57
|
+
const projectDocRef = this.db.collection("Projects").doc(this.projectId);
|
|
58
|
+
const projectDoc = await projectDocRef.get();
|
|
59
|
+
if (projectDoc.exists) {
|
|
60
|
+
const data = projectDoc.data() || {};
|
|
61
|
+
const ts = data.versionsLastRun;
|
|
62
|
+
if (ts) {
|
|
63
|
+
return ts.toMillis();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Saves {versionLastRun: <timestamp>} to the Projects/<projectId> doc.
|
|
70
|
+
*/
|
|
71
|
+
async saveLastRun(millis) {
|
|
72
|
+
const ts = Timestamp.fromMillis(millis);
|
|
73
|
+
const projectDocRef = this.db.collection("Projects").doc(this.projectId);
|
|
74
|
+
await projectDocRef.set({ versionsLastRun: ts }, { merge: true });
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Returns a list of all docs that were edited after a certain time.
|
|
78
|
+
*/
|
|
79
|
+
async getDocsModifiedAfter(millis) {
|
|
80
|
+
const ts = Timestamp.fromMillis(millis);
|
|
81
|
+
const results = [];
|
|
82
|
+
const collectionIds = await this.listCollections();
|
|
83
|
+
for (const collectionId of collectionIds) {
|
|
84
|
+
const collectionPath = `Projects/${this.projectId}/Collections/${collectionId}/Drafts`;
|
|
85
|
+
const query = this.db.collection(collectionPath).where("sys.modifiedAt", ">=", ts);
|
|
86
|
+
const querySnapshot = await query.get();
|
|
87
|
+
querySnapshot.forEach((doc) => {
|
|
88
|
+
results.push(doc.data());
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Returns a list of collection ids for the Root project.
|
|
95
|
+
*/
|
|
96
|
+
async listCollections() {
|
|
97
|
+
const collectionIds = [];
|
|
98
|
+
const collectionFileNames = await glob("*.schema.ts", {
|
|
99
|
+
cwd: path.join(this.rootConfig.rootDir, "collections")
|
|
100
|
+
});
|
|
101
|
+
collectionFileNames.forEach((filename) => {
|
|
102
|
+
collectionIds.push(filename.slice(0, -10));
|
|
103
|
+
});
|
|
104
|
+
return collectionIds;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// core/cron.ts
|
|
109
|
+
async function runCronJobs(rootConfig) {
|
|
110
|
+
await Promise.all([
|
|
111
|
+
runCronJob("publishScheduledDocs", runPublishScheduledDocs, rootConfig),
|
|
112
|
+
runCronJob("saveVersions", runSaveVersions, rootConfig)
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
async function runCronJob(name, fn, ...args) {
|
|
116
|
+
try {
|
|
117
|
+
await fn(...args);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.log(`cron failed: ${name}`);
|
|
120
|
+
console.error(String(err.stack || err));
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function runPublishScheduledDocs(rootConfig) {
|
|
125
|
+
const cmsClient = new RootCMSClient(rootConfig);
|
|
126
|
+
await cmsClient.publishScheduledDocs();
|
|
127
|
+
await cmsClient.publishScheduledReleases();
|
|
128
|
+
}
|
|
129
|
+
async function runSaveVersions(rootConfig) {
|
|
130
|
+
const service = new VersionsService(rootConfig);
|
|
131
|
+
await service.saveVersions();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export {
|
|
135
|
+
runCronJobs
|
|
136
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// core/project.ts
|
|
2
|
+
var SCHEMA_MODULES = import.meta.glob(
|
|
3
|
+
[
|
|
4
|
+
"/**/*.schema.ts",
|
|
5
|
+
"!/appengine/**/*.schema.ts",
|
|
6
|
+
"!/functions/**/*.schema.ts",
|
|
7
|
+
"!/gae/**/*.schema.ts"
|
|
8
|
+
],
|
|
9
|
+
{ eager: true }
|
|
10
|
+
);
|
|
11
|
+
async function getProjectSchemas() {
|
|
12
|
+
const schemas = {};
|
|
13
|
+
for (const fileId in SCHEMA_MODULES) {
|
|
14
|
+
const schemaModule = SCHEMA_MODULES[fileId];
|
|
15
|
+
if (schemaModule.default) {
|
|
16
|
+
const resolved = resolveOneOfPatterns(schemaModule.default);
|
|
17
|
+
schemas[fileId] = resolved;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return schemas;
|
|
21
|
+
}
|
|
22
|
+
function resolveOneOfPatterns(schemaObj) {
|
|
23
|
+
const clone = structuredClone(schemaObj);
|
|
24
|
+
function handleField(field) {
|
|
25
|
+
if (field.type === "oneof") {
|
|
26
|
+
const oneOfField = field;
|
|
27
|
+
if (isSchemaPattern(oneOfField.types)) {
|
|
28
|
+
const resolved = resolveSchemaPattern(oneOfField.types);
|
|
29
|
+
oneOfField.types = resolved.names.map((name) => resolved.schemas[name]);
|
|
30
|
+
}
|
|
31
|
+
} else if (field.type === "object" && "fields" in field) {
|
|
32
|
+
(field.fields || []).forEach(handleField);
|
|
33
|
+
} else if (field.type === "array" && "of" in field && field.of) {
|
|
34
|
+
handleField(field.of);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
(clone.fields || []).forEach(handleField);
|
|
38
|
+
return clone;
|
|
39
|
+
}
|
|
40
|
+
async function getCollectionSchema(collectionId) {
|
|
41
|
+
if (!testValidCollectionId(collectionId)) {
|
|
42
|
+
throw new Error(`invalid collection id: ${collectionId}`);
|
|
43
|
+
}
|
|
44
|
+
const fileId = `/collections/${collectionId}.schema.ts`;
|
|
45
|
+
const module = SCHEMA_MODULES[fileId];
|
|
46
|
+
if (!module.default) {
|
|
47
|
+
console.warn(`collection schema not exported in: ${fileId}`);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const collection = module.default;
|
|
51
|
+
collection.id = collectionId;
|
|
52
|
+
return convertOneOfTypes(collection);
|
|
53
|
+
}
|
|
54
|
+
function testValidCollectionId(id) {
|
|
55
|
+
return /^[A-Za-z0-9_-]+$/.test(id);
|
|
56
|
+
}
|
|
57
|
+
function isSchemaPattern(value) {
|
|
58
|
+
return typeof value === "object" && value !== null && "_schemaPattern" in value && value._schemaPattern === true;
|
|
59
|
+
}
|
|
60
|
+
function buildSchemaNameMap() {
|
|
61
|
+
const nameMap = {};
|
|
62
|
+
for (const fileId in SCHEMA_MODULES) {
|
|
63
|
+
const module = SCHEMA_MODULES[fileId];
|
|
64
|
+
if (module.default && module.default.name) {
|
|
65
|
+
nameMap[module.default.name] = module.default;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return nameMap;
|
|
69
|
+
}
|
|
70
|
+
function globToRegex(pattern) {
|
|
71
|
+
const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{DOUBLE_STAR}}").replace(/\*/g, "[^/]*").replace(/\{\{DOUBLE_STAR\}\}/g, ".*");
|
|
72
|
+
return new RegExp(`^${regexStr}$`);
|
|
73
|
+
}
|
|
74
|
+
function resolveSchemaPattern(pattern) {
|
|
75
|
+
const regex = globToRegex(pattern.pattern);
|
|
76
|
+
const excludeSet = new Set(pattern.exclude || []);
|
|
77
|
+
const names = [];
|
|
78
|
+
const schemas = {};
|
|
79
|
+
for (const fileId in SCHEMA_MODULES) {
|
|
80
|
+
if (!regex.test(fileId)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const module = SCHEMA_MODULES[fileId];
|
|
84
|
+
if (!module.default || !module.default.name) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const schemaName = module.default.name;
|
|
88
|
+
if (excludeSet.has(schemaName)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
let schemaObj = module.default;
|
|
92
|
+
if (pattern.omitFields && pattern.omitFields.length > 0) {
|
|
93
|
+
const omitSet = new Set(pattern.omitFields);
|
|
94
|
+
schemaObj = {
|
|
95
|
+
...schemaObj,
|
|
96
|
+
fields: schemaObj.fields.filter(
|
|
97
|
+
(f) => !omitSet.has(f.id || "")
|
|
98
|
+
)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
names.push(schemaName);
|
|
102
|
+
schemas[schemaName] = schemaObj;
|
|
103
|
+
}
|
|
104
|
+
return { names, schemas };
|
|
105
|
+
}
|
|
106
|
+
function convertOneOfTypes(collection) {
|
|
107
|
+
const clone = structuredClone(collection);
|
|
108
|
+
const types = clone.types || {};
|
|
109
|
+
const schemaNameMap = buildSchemaNameMap();
|
|
110
|
+
function handleOneOfField(field) {
|
|
111
|
+
if (isSchemaPattern(field.types)) {
|
|
112
|
+
const resolved = resolveSchemaPattern(field.types);
|
|
113
|
+
for (const [name, schemaObj] of Object.entries(resolved.schemas)) {
|
|
114
|
+
if (!types[name]) {
|
|
115
|
+
types[name] = schemaObj;
|
|
116
|
+
if (schemaObj.fields) {
|
|
117
|
+
walk(schemaObj);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
field.types = resolved.names;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const names = [];
|
|
125
|
+
(field.types || []).forEach((sub) => {
|
|
126
|
+
if (typeof sub === "string") {
|
|
127
|
+
names.push(sub);
|
|
128
|
+
if (!types[sub] && schemaNameMap[sub]) {
|
|
129
|
+
const resolvedSchema = schemaNameMap[sub];
|
|
130
|
+
types[sub] = resolvedSchema;
|
|
131
|
+
if (resolvedSchema.fields) {
|
|
132
|
+
walk(resolvedSchema);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (sub.name) {
|
|
138
|
+
names.push(sub.name);
|
|
139
|
+
if (!types[sub.name]) {
|
|
140
|
+
types[sub.name] = sub;
|
|
141
|
+
if (sub.fields) {
|
|
142
|
+
walk(sub);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
field.types = names;
|
|
148
|
+
}
|
|
149
|
+
function handleField(field) {
|
|
150
|
+
if (field.type === "oneof") {
|
|
151
|
+
handleOneOfField(field);
|
|
152
|
+
} else if (field.type === "object") {
|
|
153
|
+
walk(field);
|
|
154
|
+
} else if (field.type === "array" && field.of) {
|
|
155
|
+
handleField(field.of);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function walk(schema) {
|
|
159
|
+
const fields = schema?.fields || [];
|
|
160
|
+
fields.forEach(handleField);
|
|
161
|
+
}
|
|
162
|
+
walk(clone);
|
|
163
|
+
clone.types = types;
|
|
164
|
+
return clone;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export {
|
|
168
|
+
SCHEMA_MODULES,
|
|
169
|
+
getProjectSchemas,
|
|
170
|
+
getCollectionSchema
|
|
171
|
+
};
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// cli/generate-types.ts
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { loadRootConfig, viteSsrLoadModule } from "@blinkk/root/node";
|
|
6
|
+
import * as dom from "dts-dom";
|
|
7
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
async function generateTypes() {
|
|
9
|
+
const rootDir = process.cwd();
|
|
10
|
+
const rootConfig = await loadRootConfig(rootDir, { command: "root-cms" });
|
|
11
|
+
const modulePath = path.resolve(__dirname, "./project.js");
|
|
12
|
+
const project = await viteSsrLoadModule(
|
|
13
|
+
rootConfig,
|
|
14
|
+
modulePath
|
|
15
|
+
);
|
|
16
|
+
const schemas = await project.getProjectSchemas();
|
|
17
|
+
const outputPath = path.resolve(rootDir, "root-cms.d.ts");
|
|
18
|
+
await generateSchemaDts(outputPath, schemas);
|
|
19
|
+
console.log("saved root-cms.d.ts!");
|
|
20
|
+
}
|
|
21
|
+
var TEMPLATE = `/* eslint-disable */
|
|
22
|
+
/** Root.js CMS types. This file is autogenerated. */
|
|
23
|
+
|
|
24
|
+
export interface RootCMSFile {
|
|
25
|
+
src: string;
|
|
26
|
+
width?: number;
|
|
27
|
+
height?: number;
|
|
28
|
+
alt?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type RootCMSImage = RootCMSFile;
|
|
32
|
+
|
|
33
|
+
export type RootCMSOneOf<T = any> = T;
|
|
34
|
+
|
|
35
|
+
export type RootCMSOneOfOption<T, Base> = Base & {_type: T};
|
|
36
|
+
|
|
37
|
+
export interface RootCMSRichTextBlock {
|
|
38
|
+
type: string;
|
|
39
|
+
data: any;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RootCMSRichText {
|
|
43
|
+
blocks: RootCMSRichTextBlock[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RootCMSReference {
|
|
47
|
+
/** The id of the doc, e.g. "Pages/foo-bar". */
|
|
48
|
+
id: string;
|
|
49
|
+
/** The collection id of the doc, e.g. "Pages". */
|
|
50
|
+
collection: string;
|
|
51
|
+
/** The slug of the doc, e.g. "foo-bar". */
|
|
52
|
+
slug: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RootCMSDoc<Fields extends {}> {
|
|
56
|
+
/** The id of the doc, e.g. "Pages/foo-bar". */
|
|
57
|
+
id: string;
|
|
58
|
+
/** The collection id of the doc, e.g. "Pages". */
|
|
59
|
+
collection: string;
|
|
60
|
+
/** The slug of the doc, e.g. "foo-bar". */
|
|
61
|
+
slug: string;
|
|
62
|
+
/** System-level metadata. */
|
|
63
|
+
sys: {
|
|
64
|
+
createdAt: number;
|
|
65
|
+
createdBy: string;
|
|
66
|
+
modifiedAt: number;
|
|
67
|
+
modifiedBy: string;
|
|
68
|
+
firstPublishedAt?: number;
|
|
69
|
+
firstPublishedBy?: string;
|
|
70
|
+
publishedAt?: number;
|
|
71
|
+
publishedBy?: string;
|
|
72
|
+
locales?: string[];
|
|
73
|
+
};
|
|
74
|
+
/** User-entered field values from the CMS. */
|
|
75
|
+
fields?: Fields;
|
|
76
|
+
}`;
|
|
77
|
+
var DtsFormatter = class {
|
|
78
|
+
constructor() {
|
|
79
|
+
/**
|
|
80
|
+
* Field types defined in either `.schema.ts` files or used by `oneOf()`
|
|
81
|
+
* fields.
|
|
82
|
+
*/
|
|
83
|
+
this.fieldsTypes = {};
|
|
84
|
+
/**
|
|
85
|
+
* For `collections/*.schema.ts` files, output a corresponding "RootCMSDoc"
|
|
86
|
+
* type. For example, `collections/BlogPosts.schema.ts` would output a type
|
|
87
|
+
* called `BlogPostsDoc`.
|
|
88
|
+
*/
|
|
89
|
+
this.docTypes = {};
|
|
90
|
+
}
|
|
91
|
+
/** Adds the types for a `.schema.ts` file to the `.d.ts` file. */
|
|
92
|
+
addSchemaFile(fileId, schema) {
|
|
93
|
+
const jsdoc = `Generated from \`${fileId}\`.`;
|
|
94
|
+
const typeId = alphanumeric(path.parse(fileId).name.split(".")[0]);
|
|
95
|
+
const oneOfTypes = {};
|
|
96
|
+
const fieldsTypeId = `${typeId}Fields`;
|
|
97
|
+
const fieldsType = dom.create.interface(
|
|
98
|
+
fieldsTypeId,
|
|
99
|
+
dom.DeclarationFlags.Export
|
|
100
|
+
);
|
|
101
|
+
fieldsType.jsDocComment = jsdoc;
|
|
102
|
+
for (const field of schema.fields) {
|
|
103
|
+
if (!field.id) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
fieldsType.members.push(fieldProperty(field, { typeId, oneOfTypes }));
|
|
107
|
+
}
|
|
108
|
+
for (const oneOfTypeId in oneOfTypes) {
|
|
109
|
+
if (!this.fieldsTypes[fieldsTypeId]) {
|
|
110
|
+
this.fieldsTypes[oneOfTypeId] = oneOfTypes[oneOfTypeId];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
this.fieldsTypes[fieldsTypeId] = fieldsType;
|
|
114
|
+
if (fileId.startsWith("/collections/")) {
|
|
115
|
+
const docTypeId = `${typeId}Doc`;
|
|
116
|
+
const baseType = dom.create.namedTypeReference("RootCMSDoc");
|
|
117
|
+
baseType.typeArguments.push(dom.create.namedTypeReference(fieldsTypeId));
|
|
118
|
+
const docType = dom.create.alias(
|
|
119
|
+
docTypeId,
|
|
120
|
+
baseType,
|
|
121
|
+
dom.DeclarationFlags.Export
|
|
122
|
+
);
|
|
123
|
+
docType.jsDocComment = jsdoc;
|
|
124
|
+
this.docTypes[docTypeId] = docType;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Generates the `.d.ts` output as a string. */
|
|
128
|
+
toString() {
|
|
129
|
+
const results = [TEMPLATE];
|
|
130
|
+
const sortedFieldsTypes = Object.keys(this.fieldsTypes).sort();
|
|
131
|
+
for (const fieldsTypeId of sortedFieldsTypes) {
|
|
132
|
+
const fieldsType = this.fieldsTypes[fieldsTypeId];
|
|
133
|
+
results.push(this.typeToString(fieldsType));
|
|
134
|
+
const docTypeId = this.fieldsTypeToDocType(fieldsTypeId);
|
|
135
|
+
const docType = this.docTypes[docTypeId];
|
|
136
|
+
if (docType) {
|
|
137
|
+
results.push(this.typeToString(docType));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const output = results.join("\n\n").replaceAll("/** ", "/** ").replaceAll(" */", " */").replace(/\r\n|\r|\n/g, "\n") + "\n";
|
|
141
|
+
return output;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Converts "<Name>Fields" to "<Name>Doc".
|
|
145
|
+
*/
|
|
146
|
+
fieldsTypeToDocType(fieldsTypeId) {
|
|
147
|
+
if (!fieldsTypeId.endsWith("Fields")) {
|
|
148
|
+
throw new Error(`"${fieldsTypeId}" should be suffixed with "Fields"`);
|
|
149
|
+
}
|
|
150
|
+
const name = fieldsTypeId.slice(0, -6);
|
|
151
|
+
return `${name}Doc`;
|
|
152
|
+
}
|
|
153
|
+
typeToString(type2) {
|
|
154
|
+
return this.reformatOutput(dom.emit(type2, { singleLineJsDocComments: true }));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Formats the output to Google style conventions, e.g. 2-space indents and
|
|
158
|
+
* single quote strings.
|
|
159
|
+
*/
|
|
160
|
+
reformatOutput(input) {
|
|
161
|
+
const lines = input.trim().split("\n");
|
|
162
|
+
const results = [];
|
|
163
|
+
for (const line of lines) {
|
|
164
|
+
const convertedLine = line.replace(/ {4}/g, " ").replaceAll('"', "'");
|
|
165
|
+
results.push(convertedLine);
|
|
166
|
+
}
|
|
167
|
+
return results.join("\n");
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
async function generateSchemaDts(outputPath, schemas) {
|
|
171
|
+
const dtsFormatter = new DtsFormatter();
|
|
172
|
+
for (const fileId in schemas) {
|
|
173
|
+
const schema = schemas[fileId];
|
|
174
|
+
dtsFormatter.addSchemaFile(fileId, schema);
|
|
175
|
+
}
|
|
176
|
+
const output = dtsFormatter.toString();
|
|
177
|
+
await fs.writeFile(outputPath, output, "utf-8");
|
|
178
|
+
}
|
|
179
|
+
function fieldProperty(field, options) {
|
|
180
|
+
const prop = dom.create.property(
|
|
181
|
+
field.id,
|
|
182
|
+
fieldType(field, options),
|
|
183
|
+
dom.DeclarationFlags.Optional
|
|
184
|
+
);
|
|
185
|
+
const jsdoc = [];
|
|
186
|
+
if (field.label) {
|
|
187
|
+
if (field.help) {
|
|
188
|
+
jsdoc.push(`${field.label}. ${field.help}`);
|
|
189
|
+
} else {
|
|
190
|
+
jsdoc.push(field.label);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (field.deprecated) {
|
|
194
|
+
jsdoc.push("@deprecated");
|
|
195
|
+
}
|
|
196
|
+
if (jsdoc.length > 0) {
|
|
197
|
+
prop.jsDocComment = jsdoc.join("\n");
|
|
198
|
+
}
|
|
199
|
+
return prop;
|
|
200
|
+
}
|
|
201
|
+
function fieldType(field, options) {
|
|
202
|
+
if (field.type === "array") {
|
|
203
|
+
return dom.type.array(fieldType(field.of, options));
|
|
204
|
+
}
|
|
205
|
+
if (field.type === "boolean") {
|
|
206
|
+
return dom.type.boolean;
|
|
207
|
+
}
|
|
208
|
+
if (field.type === "date") {
|
|
209
|
+
return dom.type.string;
|
|
210
|
+
}
|
|
211
|
+
if (field.type === "datetime") {
|
|
212
|
+
return dom.type.number;
|
|
213
|
+
}
|
|
214
|
+
if (field.type === "file") {
|
|
215
|
+
const fileType = dom.create.namedTypeReference("RootCMSFile");
|
|
216
|
+
return fileType;
|
|
217
|
+
}
|
|
218
|
+
if (field.type === "image") {
|
|
219
|
+
const imageType = dom.create.namedTypeReference("RootCMSImage");
|
|
220
|
+
return imageType;
|
|
221
|
+
}
|
|
222
|
+
if (field.type === "multiselect") {
|
|
223
|
+
return dom.type.array(dom.type.string);
|
|
224
|
+
}
|
|
225
|
+
if (field.type === "oneof") {
|
|
226
|
+
const oneOf = dom.create.namedTypeReference("RootCMSOneOf");
|
|
227
|
+
if (field.types && Array.isArray(field.types)) {
|
|
228
|
+
const unionTypes = [];
|
|
229
|
+
field.types.forEach((schema) => {
|
|
230
|
+
let typeName;
|
|
231
|
+
if (typeof schema === "string") {
|
|
232
|
+
typeName = schema;
|
|
233
|
+
return;
|
|
234
|
+
} else {
|
|
235
|
+
typeName = schema.name;
|
|
236
|
+
}
|
|
237
|
+
if (!typeName) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const cleanName = alphanumeric(typeName);
|
|
241
|
+
const oneOfTypeId = `${cleanName}Fields`;
|
|
242
|
+
if (typeof schema === "object" && !options.oneOfTypes[oneOfTypeId]) {
|
|
243
|
+
const oneOfTypeInterface = dom.create.interface(
|
|
244
|
+
oneOfTypeId,
|
|
245
|
+
dom.DeclarationFlags.Export
|
|
246
|
+
);
|
|
247
|
+
if (schema.description) {
|
|
248
|
+
oneOfTypeInterface.jsDocComment = schema.description;
|
|
249
|
+
}
|
|
250
|
+
const oneOfTypeFields = schema.fields || [];
|
|
251
|
+
oneOfTypeFields.forEach((f) => {
|
|
252
|
+
oneOfTypeInterface.members.push(fieldProperty(f, options));
|
|
253
|
+
});
|
|
254
|
+
options.oneOfTypes[oneOfTypeId] = oneOfTypeInterface;
|
|
255
|
+
}
|
|
256
|
+
const oneOfOption = dom.create.namedTypeReference("RootCMSOneOfOption");
|
|
257
|
+
oneOfOption.typeArguments = [
|
|
258
|
+
dom.type.stringLiteral(typeName),
|
|
259
|
+
dom.create.namedTypeReference(oneOfTypeId)
|
|
260
|
+
];
|
|
261
|
+
unionTypes.push(oneOfOption);
|
|
262
|
+
});
|
|
263
|
+
if (unionTypes.length > 0) {
|
|
264
|
+
oneOf.typeArguments = [dom.create.union(unionTypes)];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return oneOf;
|
|
268
|
+
}
|
|
269
|
+
if (field.type === "reference") {
|
|
270
|
+
const referenceType = dom.create.namedTypeReference("RootCMSReference");
|
|
271
|
+
return referenceType;
|
|
272
|
+
}
|
|
273
|
+
if (field.type === "references") {
|
|
274
|
+
const referenceType = dom.create.namedTypeReference("RootCMSReference");
|
|
275
|
+
return dom.type.array(referenceType);
|
|
276
|
+
}
|
|
277
|
+
if (field.type === "richtext") {
|
|
278
|
+
const richtextType = dom.create.namedTypeReference("RootCMSRichText");
|
|
279
|
+
return richtextType;
|
|
280
|
+
}
|
|
281
|
+
if (field.type === "select") {
|
|
282
|
+
return dom.type.string;
|
|
283
|
+
}
|
|
284
|
+
if (field.type === "string") {
|
|
285
|
+
return dom.type.string;
|
|
286
|
+
}
|
|
287
|
+
if (field.type === "object") {
|
|
288
|
+
const subproperties = (field.fields || []).map(
|
|
289
|
+
(f) => fieldProperty(f, options)
|
|
290
|
+
);
|
|
291
|
+
return dom.create.objectType(subproperties);
|
|
292
|
+
}
|
|
293
|
+
return dom.type.unknown;
|
|
294
|
+
}
|
|
295
|
+
function alphanumeric(input) {
|
|
296
|
+
return input.replace(/[^a-zA-Z0-9]/g, "");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export {
|
|
300
|
+
generateTypes,
|
|
301
|
+
generateSchemaDts
|
|
302
|
+
};
|