@momentumcms/core 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/generators/generator.cjs +813 -0
- package/generators/generator.js +782 -0
- package/package.json +2 -2
- package/src/generators/field-to-typescript.d.ts +44 -0
- package/src/generators/generator.d.ts +185 -0
- package/src/lib/config.d.ts +34 -1
- package/src/lib/plugins.d.ts +23 -0
- package/generators/types/generator.cjs +0 -292
- package/generators/types/generator.js +0 -271
- package/src/generators/types/generator.d.ts +0 -27
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// libs/core/src/generators/generator.ts
|
|
21
|
+
var generator_exports = {};
|
|
22
|
+
__export(generator_exports, {
|
|
23
|
+
computeRelativeImport: () => computeRelativeImport,
|
|
24
|
+
default: () => runGenerator,
|
|
25
|
+
generateAdminConfig: () => generateAdminConfig,
|
|
26
|
+
generateTypes: () => generateTypes,
|
|
27
|
+
serializeCollection: () => serializeCollection,
|
|
28
|
+
serializeField: () => serializeField,
|
|
29
|
+
serializeGlobal: () => serializeGlobal,
|
|
30
|
+
serializeValue: () => serializeValue
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(generator_exports);
|
|
33
|
+
var import_node_fs = require("node:fs");
|
|
34
|
+
var import_node_child_process = require("node:child_process");
|
|
35
|
+
var import_node_path = require("node:path");
|
|
36
|
+
var import_node_url = require("node:url");
|
|
37
|
+
|
|
38
|
+
// libs/core/src/lib/fields/field.types.ts
|
|
39
|
+
function flattenDataFields(fields) {
|
|
40
|
+
const result = [];
|
|
41
|
+
for (const field of fields) {
|
|
42
|
+
if (field.type === "tabs") {
|
|
43
|
+
for (const tab of field.tabs) {
|
|
44
|
+
result.push(...flattenDataFields(tab.fields));
|
|
45
|
+
}
|
|
46
|
+
} else if (field.type === "collapsible" || field.type === "row") {
|
|
47
|
+
result.push(...flattenDataFields(field.fields));
|
|
48
|
+
} else {
|
|
49
|
+
result.push(field);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// libs/core/src/generators/field-to-typescript.ts
|
|
56
|
+
function slugToPascalCase(slug) {
|
|
57
|
+
return slug.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("").replace(/[^a-zA-Z0-9_$]/g, "");
|
|
58
|
+
}
|
|
59
|
+
function needsQuoting(name) {
|
|
60
|
+
return /[^a-zA-Z0-9_$]/.test(name) || /^\d/.test(name);
|
|
61
|
+
}
|
|
62
|
+
function safeQuote(value) {
|
|
63
|
+
return JSON.stringify(String(value));
|
|
64
|
+
}
|
|
65
|
+
function formatPropName(name) {
|
|
66
|
+
return needsQuoting(name) ? safeQuote(name) : name;
|
|
67
|
+
}
|
|
68
|
+
function fieldTypeToTS(field) {
|
|
69
|
+
switch (field.type) {
|
|
70
|
+
case "text":
|
|
71
|
+
case "textarea":
|
|
72
|
+
case "richText":
|
|
73
|
+
case "email":
|
|
74
|
+
case "password":
|
|
75
|
+
case "slug":
|
|
76
|
+
return "string";
|
|
77
|
+
case "number":
|
|
78
|
+
return "number";
|
|
79
|
+
case "checkbox":
|
|
80
|
+
return "boolean";
|
|
81
|
+
case "date":
|
|
82
|
+
return "string";
|
|
83
|
+
case "select": {
|
|
84
|
+
if (field.options && field.options.length > 0) {
|
|
85
|
+
const union = field.options.map((opt) => safeQuote(opt.value)).join(" | ");
|
|
86
|
+
return field.hasMany ? `(${union})[]` : union;
|
|
87
|
+
}
|
|
88
|
+
return field.hasMany ? "string[]" : "string";
|
|
89
|
+
}
|
|
90
|
+
case "radio": {
|
|
91
|
+
if (field.options && field.options.length > 0) {
|
|
92
|
+
return field.options.map((opt) => safeQuote(opt.value)).join(" | ");
|
|
93
|
+
}
|
|
94
|
+
return "string";
|
|
95
|
+
}
|
|
96
|
+
case "relationship": {
|
|
97
|
+
return field.hasMany ? "string[]" : "string";
|
|
98
|
+
}
|
|
99
|
+
case "upload":
|
|
100
|
+
return field.hasMany ? "string[]" : "string";
|
|
101
|
+
case "array": {
|
|
102
|
+
if (field.fields && field.fields.length > 0) {
|
|
103
|
+
const arrayItemType = generateFieldsInterface(field.fields, " ");
|
|
104
|
+
return `Array<{
|
|
105
|
+
${arrayItemType}
|
|
106
|
+
}>`;
|
|
107
|
+
}
|
|
108
|
+
return "unknown[]";
|
|
109
|
+
}
|
|
110
|
+
case "group": {
|
|
111
|
+
if (field.fields && field.fields.length > 0) {
|
|
112
|
+
const groupType = generateFieldsInterface(field.fields, " ");
|
|
113
|
+
return `{
|
|
114
|
+
${groupType}
|
|
115
|
+
}`;
|
|
116
|
+
}
|
|
117
|
+
return "Record<string, unknown>";
|
|
118
|
+
}
|
|
119
|
+
case "blocks":
|
|
120
|
+
return "unknown[]";
|
|
121
|
+
case "json":
|
|
122
|
+
return "Record<string, unknown>";
|
|
123
|
+
case "point":
|
|
124
|
+
return "[number, number]";
|
|
125
|
+
default:
|
|
126
|
+
return "unknown";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function generateFieldsInterface(fields, indent = " ") {
|
|
130
|
+
const dataFields = flattenDataFields(fields);
|
|
131
|
+
return dataFields.map((field) => {
|
|
132
|
+
const tsType = fieldTypeToTS(field);
|
|
133
|
+
const optional = field.required ? "" : "?";
|
|
134
|
+
const propName = formatPropName(field.name);
|
|
135
|
+
return `${indent}${propName}${optional}: ${tsType};`;
|
|
136
|
+
}).join("\n");
|
|
137
|
+
}
|
|
138
|
+
function generateBlockTypes(collectionName, fieldName, blocksField) {
|
|
139
|
+
const collectionPascal = slugToPascalCase(collectionName);
|
|
140
|
+
const fieldPascal = slugToPascalCase(fieldName);
|
|
141
|
+
const prefix = `${collectionPascal}${fieldPascal}`;
|
|
142
|
+
const blockTypeNames = [];
|
|
143
|
+
const declarations = [];
|
|
144
|
+
for (const block of blocksField.blocks) {
|
|
145
|
+
const blockPascal = slugToPascalCase(block.slug);
|
|
146
|
+
const typeName = `${prefix}${blockPascal}Block`;
|
|
147
|
+
blockTypeNames.push(typeName);
|
|
148
|
+
const fieldsCode = generateFieldsInterface(block.fields);
|
|
149
|
+
declarations.push(`export interface ${typeName} {`);
|
|
150
|
+
declarations.push(` blockType: ${safeQuote(block.slug)};`);
|
|
151
|
+
if (fieldsCode) {
|
|
152
|
+
declarations.push(fieldsCode);
|
|
153
|
+
}
|
|
154
|
+
declarations.push(`}`);
|
|
155
|
+
declarations.push("");
|
|
156
|
+
}
|
|
157
|
+
const unionTypeName = `${prefix}Block`;
|
|
158
|
+
if (blockTypeNames.length > 0) {
|
|
159
|
+
declarations.push(`export type ${unionTypeName} =`);
|
|
160
|
+
declarations.push(blockTypeNames.map((name) => ` | ${name}`).join("\n") + ";");
|
|
161
|
+
declarations.push("");
|
|
162
|
+
}
|
|
163
|
+
return { declarations: declarations.join("\n"), unionTypeName };
|
|
164
|
+
}
|
|
165
|
+
function getFieldWhereType(field) {
|
|
166
|
+
switch (field.type) {
|
|
167
|
+
case "text":
|
|
168
|
+
case "textarea":
|
|
169
|
+
case "richText":
|
|
170
|
+
case "email":
|
|
171
|
+
case "slug":
|
|
172
|
+
return `string | { equals?: string; not?: string; contains?: string; in?: string[] }`;
|
|
173
|
+
case "number":
|
|
174
|
+
return `number | { equals?: number; not?: number; gt?: number; gte?: number; lt?: number; lte?: number; in?: number[] }`;
|
|
175
|
+
case "checkbox":
|
|
176
|
+
return `boolean | { equals?: boolean }`;
|
|
177
|
+
case "date":
|
|
178
|
+
return `string | { equals?: string; not?: string; gt?: string; gte?: string; lt?: string; lte?: string }`;
|
|
179
|
+
case "select":
|
|
180
|
+
case "radio": {
|
|
181
|
+
if (field.options && field.options.length > 0) {
|
|
182
|
+
const options = field.options.map((opt) => safeQuote(opt.value)).join(" | ");
|
|
183
|
+
return `${options} | { equals?: ${options}; not?: ${options}; in?: (${options})[] }`;
|
|
184
|
+
}
|
|
185
|
+
return `string | { equals?: string; in?: string[] }`;
|
|
186
|
+
}
|
|
187
|
+
case "relationship":
|
|
188
|
+
case "upload":
|
|
189
|
+
return `string | { equals?: string; not?: string; in?: string[] }`;
|
|
190
|
+
default:
|
|
191
|
+
return "unknown";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function generateWhereClauseInterface(slug, fields, hasTimestamps) {
|
|
195
|
+
const interfaceName = slugToPascalCase(slug);
|
|
196
|
+
const lines = [];
|
|
197
|
+
const dataFields = flattenDataFields(fields);
|
|
198
|
+
lines.push(`export interface ${interfaceName}WhereClause {`);
|
|
199
|
+
lines.push(` id?: string | { equals?: string; not?: string; in?: string[] };`);
|
|
200
|
+
for (const field of dataFields) {
|
|
201
|
+
const whereType = getFieldWhereType(field);
|
|
202
|
+
if (whereType !== "unknown") {
|
|
203
|
+
const propName = formatPropName(field.name);
|
|
204
|
+
lines.push(` ${propName}?: ${whereType};`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (hasTimestamps) {
|
|
208
|
+
lines.push(
|
|
209
|
+
` createdAt?: string | { equals?: string; gt?: string; gte?: string; lt?: string; lte?: string };`
|
|
210
|
+
);
|
|
211
|
+
lines.push(
|
|
212
|
+
` updatedAt?: string | { equals?: string; gt?: string; gte?: string; lt?: string; lte?: string };`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
lines.push(`}`);
|
|
216
|
+
return lines.join("\n");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// libs/core/src/generators/generator.ts
|
|
220
|
+
async function loadConfig(configPath) {
|
|
221
|
+
try {
|
|
222
|
+
const configUrl = (0, import_node_url.pathToFileURL)(configPath).href;
|
|
223
|
+
const configModule = await import(configUrl);
|
|
224
|
+
return configModule.default || configModule;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
throw new Error(`Failed to load config from ${configPath}: ${error}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function resolveAllCollections(config) {
|
|
230
|
+
const allCollections = [...config.collections];
|
|
231
|
+
for (const plugin of config.plugins ?? []) {
|
|
232
|
+
if (plugin.collections) {
|
|
233
|
+
for (const col of plugin.collections) {
|
|
234
|
+
if (!allCollections.some((c) => c.slug === col.slug)) {
|
|
235
|
+
allCollections.push({ ...col, fields: col.fields ?? [] });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
for (const plugin of config.plugins ?? []) {
|
|
241
|
+
if (plugin.modifyCollections) {
|
|
242
|
+
plugin.modifyCollections(allCollections);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return allCollections;
|
|
246
|
+
}
|
|
247
|
+
function generateTypes(config) {
|
|
248
|
+
const allCollections = resolveAllCollections(config);
|
|
249
|
+
const globals = config.globals ?? [];
|
|
250
|
+
const lines = [];
|
|
251
|
+
lines.push("/**");
|
|
252
|
+
lines.push(" * AUTO-GENERATED by @momentumcms/core");
|
|
253
|
+
lines.push(" * DO NOT EDIT - regenerate with: nx run <app>:generate");
|
|
254
|
+
lines.push(" */");
|
|
255
|
+
lines.push("");
|
|
256
|
+
const blockDeclarations = [];
|
|
257
|
+
for (const collection of allCollections) {
|
|
258
|
+
const interfaceName = slugToPascalCase(collection.slug);
|
|
259
|
+
const dataFields = flattenDataFields(collection.fields);
|
|
260
|
+
const hasVersionsDrafts = hasVersionsWithDrafts(collection);
|
|
261
|
+
const hasSoftDelete = !!collection.softDelete;
|
|
262
|
+
const hasTimestamps = collection.timestamps !== false;
|
|
263
|
+
for (const field of dataFields) {
|
|
264
|
+
if (field.type === "blocks") {
|
|
265
|
+
const blockResult = generateBlockTypes(
|
|
266
|
+
collection.slug,
|
|
267
|
+
field.name,
|
|
268
|
+
field
|
|
269
|
+
);
|
|
270
|
+
blockDeclarations.push(blockResult.declarations);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (blockDeclarations.length > 0) {
|
|
274
|
+
lines.push(blockDeclarations.join("\n"));
|
|
275
|
+
blockDeclarations.length = 0;
|
|
276
|
+
}
|
|
277
|
+
lines.push(`export interface ${interfaceName} {`);
|
|
278
|
+
lines.push(` id: string;`);
|
|
279
|
+
for (const field of dataFields) {
|
|
280
|
+
const optional = field.required ? "" : "?";
|
|
281
|
+
const propName = needsQuoting2(field.name) ? safeQuote(field.name) : field.name;
|
|
282
|
+
if (field.type === "blocks") {
|
|
283
|
+
const blockResult = generateBlockTypes(
|
|
284
|
+
collection.slug,
|
|
285
|
+
field.name,
|
|
286
|
+
field
|
|
287
|
+
);
|
|
288
|
+
lines.push(` ${propName}${optional}: ${blockResult.unionTypeName}[];`);
|
|
289
|
+
} else {
|
|
290
|
+
const tsType = fieldTypeToTS(field);
|
|
291
|
+
lines.push(` ${propName}${optional}: ${tsType};`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (hasVersionsDrafts) {
|
|
295
|
+
lines.push(` _status?: 'draft' | 'published';`);
|
|
296
|
+
}
|
|
297
|
+
if (hasSoftDelete) {
|
|
298
|
+
const fieldName = typeof collection.softDelete === "object" && collection.softDelete.field ? collection.softDelete.field : "deletedAt";
|
|
299
|
+
lines.push(` ${fieldName}?: string | null;`);
|
|
300
|
+
}
|
|
301
|
+
if (hasTimestamps) {
|
|
302
|
+
lines.push(` createdAt: string;`);
|
|
303
|
+
lines.push(` updatedAt: string;`);
|
|
304
|
+
}
|
|
305
|
+
lines.push(`}`);
|
|
306
|
+
lines.push("");
|
|
307
|
+
}
|
|
308
|
+
if (globals.length > 0) {
|
|
309
|
+
lines.push("// \u2500\u2500 Global Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
310
|
+
lines.push("");
|
|
311
|
+
for (const global of globals) {
|
|
312
|
+
const interfaceName = slugToPascalCase(global.slug) + "Global";
|
|
313
|
+
const fieldsCode = generateFieldsInterface(global.fields);
|
|
314
|
+
lines.push(`export interface ${interfaceName} {`);
|
|
315
|
+
if (fieldsCode) {
|
|
316
|
+
lines.push(fieldsCode);
|
|
317
|
+
}
|
|
318
|
+
lines.push(` updatedAt: string;`);
|
|
319
|
+
lines.push(`}`);
|
|
320
|
+
lines.push("");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
lines.push("// \u2500\u2500 Where Clauses \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
324
|
+
lines.push("");
|
|
325
|
+
for (const collection of allCollections) {
|
|
326
|
+
const hasTimestamps = collection.timestamps !== false;
|
|
327
|
+
lines.push(
|
|
328
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- boundary: local FieldDefinition[] → library Field[]
|
|
329
|
+
generateWhereClauseInterface(collection.slug, collection.fields, hasTimestamps)
|
|
330
|
+
);
|
|
331
|
+
lines.push("");
|
|
332
|
+
}
|
|
333
|
+
const slugs = allCollections.map((c) => safeQuote(c.slug)).join(" | ");
|
|
334
|
+
lines.push(`export type CollectionSlug = ${slugs || "never"};`);
|
|
335
|
+
lines.push("");
|
|
336
|
+
if (globals.length > 0) {
|
|
337
|
+
const globalSlugs = globals.map((g) => safeQuote(g.slug)).join(" | ");
|
|
338
|
+
lines.push(`export type GlobalSlug = ${globalSlugs};`);
|
|
339
|
+
lines.push("");
|
|
340
|
+
}
|
|
341
|
+
lines.push(`export interface MomentumCollections {`);
|
|
342
|
+
for (const collection of allCollections) {
|
|
343
|
+
const interfaceName = slugToPascalCase(collection.slug);
|
|
344
|
+
lines.push(` ${safeQuote(collection.slug)}: ${interfaceName};`);
|
|
345
|
+
}
|
|
346
|
+
lines.push(`}`);
|
|
347
|
+
lines.push("");
|
|
348
|
+
if (globals.length > 0) {
|
|
349
|
+
lines.push(`export interface MomentumGlobals {`);
|
|
350
|
+
for (const global of globals) {
|
|
351
|
+
const interfaceName = slugToPascalCase(global.slug) + "Global";
|
|
352
|
+
lines.push(` ${safeQuote(global.slug)}: ${interfaceName};`);
|
|
353
|
+
}
|
|
354
|
+
lines.push(`}`);
|
|
355
|
+
lines.push("");
|
|
356
|
+
}
|
|
357
|
+
lines.push(`export type TypedMomentumCollections = {`);
|
|
358
|
+
for (const collection of allCollections) {
|
|
359
|
+
const interfaceName = slugToPascalCase(collection.slug);
|
|
360
|
+
lines.push(
|
|
361
|
+
` ${safeQuote(collection.slug)}: { doc: ${interfaceName}; where: ${interfaceName}WhereClause };`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
lines.push(`};`);
|
|
365
|
+
lines.push("");
|
|
366
|
+
lines.push(`export type DocumentType<S extends CollectionSlug> = MomentumCollections[S];`);
|
|
367
|
+
lines.push("");
|
|
368
|
+
lines.push(
|
|
369
|
+
`export type WhereClauseType<S extends CollectionSlug> = TypedMomentumCollections[S]['where'];`
|
|
370
|
+
);
|
|
371
|
+
lines.push("");
|
|
372
|
+
return lines.join("\n");
|
|
373
|
+
}
|
|
374
|
+
function hasVersionsWithDrafts(collection) {
|
|
375
|
+
if (!collection.versions)
|
|
376
|
+
return false;
|
|
377
|
+
if (collection.versions === true)
|
|
378
|
+
return false;
|
|
379
|
+
return !!collection.versions.drafts;
|
|
380
|
+
}
|
|
381
|
+
function needsQuoting2(name) {
|
|
382
|
+
return /[^a-zA-Z0-9_$]/.test(name) || /^\d/.test(name);
|
|
383
|
+
}
|
|
384
|
+
var FIELD_STRIP_KEYS = /* @__PURE__ */ new Set(["access", "hooks", "validate", "filterOptions"]);
|
|
385
|
+
var FIELD_ADMIN_STRIP_KEYS = /* @__PURE__ */ new Set(["condition"]);
|
|
386
|
+
function isRecord(value) {
|
|
387
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
388
|
+
}
|
|
389
|
+
function serializeValue(value, indent = " ") {
|
|
390
|
+
if (value === null)
|
|
391
|
+
return "null";
|
|
392
|
+
if (value === void 0)
|
|
393
|
+
return "undefined";
|
|
394
|
+
if (typeof value === "string")
|
|
395
|
+
return JSON.stringify(value);
|
|
396
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
397
|
+
return String(value);
|
|
398
|
+
if (typeof value === "function")
|
|
399
|
+
return "undefined";
|
|
400
|
+
if (Array.isArray(value)) {
|
|
401
|
+
if (value.length === 0)
|
|
402
|
+
return "[]";
|
|
403
|
+
const items = value.map((item) => `${indent} ${serializeValue(item, indent + " ")}`).join(",\n");
|
|
404
|
+
return `[
|
|
405
|
+
${items},
|
|
406
|
+
${indent}]`;
|
|
407
|
+
}
|
|
408
|
+
if (typeof value === "object") {
|
|
409
|
+
const entries = Object.entries(value).filter(
|
|
410
|
+
([, v]) => v !== void 0 && typeof v !== "function"
|
|
411
|
+
);
|
|
412
|
+
if (entries.length === 0)
|
|
413
|
+
return "{}";
|
|
414
|
+
const props = entries.map(([k, v]) => {
|
|
415
|
+
const key = needsQuoting2(k) ? safeQuote(k) : k;
|
|
416
|
+
return `${indent} ${key}: ${serializeValue(v, indent + " ")}`;
|
|
417
|
+
}).join(",\n");
|
|
418
|
+
return `{
|
|
419
|
+
${props},
|
|
420
|
+
${indent}}`;
|
|
421
|
+
}
|
|
422
|
+
return "undefined";
|
|
423
|
+
}
|
|
424
|
+
function serializeField(field, indent = " ") {
|
|
425
|
+
const props = [];
|
|
426
|
+
props.push(`${indent}name: ${JSON.stringify(field.name)}`);
|
|
427
|
+
props.push(`${indent}type: ${JSON.stringify(field.type)}`);
|
|
428
|
+
for (const [key, value] of Object.entries(field)) {
|
|
429
|
+
if (key === "name" || key === "type")
|
|
430
|
+
continue;
|
|
431
|
+
if (FIELD_STRIP_KEYS.has(key))
|
|
432
|
+
continue;
|
|
433
|
+
if (value === void 0)
|
|
434
|
+
continue;
|
|
435
|
+
if (key === "collection" && field.type === "relationship" && typeof value === "function") {
|
|
436
|
+
const stub = resolveRelationshipStub(field);
|
|
437
|
+
props.push(`${indent}collection: () => (${stub})`);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (typeof value === "function")
|
|
441
|
+
continue;
|
|
442
|
+
if (key === "admin" && isRecord(value)) {
|
|
443
|
+
const adminVal = serializeFieldAdmin(value, indent + " ");
|
|
444
|
+
if (adminVal !== null) {
|
|
445
|
+
props.push(`${indent}admin: ${adminVal}`);
|
|
446
|
+
}
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (key === "fields" && Array.isArray(value)) {
|
|
450
|
+
props.push(`${indent}fields: ${serializeFieldsArray(value, indent + " ")}`);
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (key === "blocks" && Array.isArray(value)) {
|
|
454
|
+
props.push(`${indent}blocks: ${serializeBlocksArray(value, indent + " ")}`);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (key === "tabs" && Array.isArray(value)) {
|
|
458
|
+
props.push(`${indent}tabs: ${serializeTabsArray(value, indent + " ")}`);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (key === "defaultValue") {
|
|
462
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
463
|
+
props.push(`${indent}defaultValue: ${serializeValue(value)}`);
|
|
464
|
+
}
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
props.push(`${indent}${key}: ${serializeValue(value, indent + " ")}`);
|
|
468
|
+
}
|
|
469
|
+
return `{
|
|
470
|
+
${props.join(",\n")},
|
|
471
|
+
${indent.slice(0, -1)}}`;
|
|
472
|
+
}
|
|
473
|
+
function serializeFieldAdmin(admin, indent) {
|
|
474
|
+
const entries = Object.entries(admin).filter(
|
|
475
|
+
([k, v]) => v !== void 0 && typeof v !== "function" && !FIELD_ADMIN_STRIP_KEYS.has(k)
|
|
476
|
+
);
|
|
477
|
+
if (entries.length === 0)
|
|
478
|
+
return null;
|
|
479
|
+
const props = entries.map(([k, v]) => `${indent} ${k}: ${serializeValue(v, indent + " ")}`).join(",\n");
|
|
480
|
+
return `{
|
|
481
|
+
${props},
|
|
482
|
+
${indent}}`;
|
|
483
|
+
}
|
|
484
|
+
function resolveRelationshipStub(field) {
|
|
485
|
+
if (!field.collection)
|
|
486
|
+
return "{}";
|
|
487
|
+
try {
|
|
488
|
+
const config = field.collection();
|
|
489
|
+
if (!config || typeof config !== "object")
|
|
490
|
+
return "{}";
|
|
491
|
+
const c = config;
|
|
492
|
+
const parts = [];
|
|
493
|
+
if (typeof c["slug"] === "string") {
|
|
494
|
+
parts.push(`slug: ${JSON.stringify(c["slug"])}`);
|
|
495
|
+
}
|
|
496
|
+
const labels = c["labels"];
|
|
497
|
+
if (labels && typeof labels === "object") {
|
|
498
|
+
const l = labels;
|
|
499
|
+
const labelParts = [];
|
|
500
|
+
if (typeof l["singular"] === "string")
|
|
501
|
+
labelParts.push(`singular: ${JSON.stringify(l["singular"])}`);
|
|
502
|
+
if (typeof l["plural"] === "string")
|
|
503
|
+
labelParts.push(`plural: ${JSON.stringify(l["plural"])}`);
|
|
504
|
+
if (labelParts.length > 0) {
|
|
505
|
+
parts.push(`labels: { ${labelParts.join(", ")} }`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const admin = c["admin"];
|
|
509
|
+
if (admin && typeof admin === "object") {
|
|
510
|
+
const a = admin;
|
|
511
|
+
if (typeof a["useAsTitle"] === "string") {
|
|
512
|
+
parts.push(`admin: { useAsTitle: ${JSON.stringify(a["useAsTitle"])} }`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return `{ ${parts.join(", ")} }`;
|
|
516
|
+
} catch {
|
|
517
|
+
return "{}";
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function serializeFieldsArray(fields, indent) {
|
|
521
|
+
if (fields.length === 0)
|
|
522
|
+
return "[]";
|
|
523
|
+
const items = fields.map((f) => `${indent} ${serializeField(f, indent + " ")}`).join(",\n");
|
|
524
|
+
return `[
|
|
525
|
+
${items},
|
|
526
|
+
${indent}]`;
|
|
527
|
+
}
|
|
528
|
+
function serializeBlocksArray(blocks, indent) {
|
|
529
|
+
if (blocks.length === 0)
|
|
530
|
+
return "[]";
|
|
531
|
+
const items = blocks.map((block) => {
|
|
532
|
+
const parts = [];
|
|
533
|
+
parts.push(`${indent} slug: ${JSON.stringify(block.slug)}`);
|
|
534
|
+
parts.push(`${indent} fields: ${serializeFieldsArray(block.fields, indent + " ")}`);
|
|
535
|
+
if (block.labels) {
|
|
536
|
+
parts.push(`${indent} labels: ${serializeValue(block.labels, indent + " ")}`);
|
|
537
|
+
}
|
|
538
|
+
if (block.editor) {
|
|
539
|
+
const editorStr = serializeValue(block.editor, indent + " ");
|
|
540
|
+
if (editorStr !== "undefined") {
|
|
541
|
+
parts.push(`${indent} editor: ${editorStr}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return `${indent} {
|
|
545
|
+
${parts.join(",\n")},
|
|
546
|
+
${indent} }`;
|
|
547
|
+
}).join(",\n");
|
|
548
|
+
return `[
|
|
549
|
+
${items},
|
|
550
|
+
${indent}]`;
|
|
551
|
+
}
|
|
552
|
+
function serializeTabsArray(tabs, indent) {
|
|
553
|
+
if (tabs.length === 0)
|
|
554
|
+
return "[]";
|
|
555
|
+
const items = tabs.map((tab) => {
|
|
556
|
+
const parts = [];
|
|
557
|
+
parts.push(`${indent} label: ${JSON.stringify(tab.label)}`);
|
|
558
|
+
if (tab.description) {
|
|
559
|
+
parts.push(`${indent} description: ${JSON.stringify(tab.description)}`);
|
|
560
|
+
}
|
|
561
|
+
parts.push(`${indent} fields: ${serializeFieldsArray(tab.fields, indent + " ")}`);
|
|
562
|
+
return `${indent} {
|
|
563
|
+
${parts.join(",\n")},
|
|
564
|
+
${indent} }`;
|
|
565
|
+
}).join(",\n");
|
|
566
|
+
return `[
|
|
567
|
+
${items},
|
|
568
|
+
${indent}]`;
|
|
569
|
+
}
|
|
570
|
+
function serializeCollection(collection, indent = " ") {
|
|
571
|
+
const parts = [];
|
|
572
|
+
parts.push(`${indent} slug: ${JSON.stringify(collection.slug)}`);
|
|
573
|
+
if (collection.labels) {
|
|
574
|
+
parts.push(`${indent} labels: ${serializeValue(collection.labels, indent + " ")}`);
|
|
575
|
+
}
|
|
576
|
+
parts.push(`${indent} fields: ${serializeFieldsArray(collection.fields, indent + " ")}`);
|
|
577
|
+
if (collection.admin) {
|
|
578
|
+
const adminEntries = Object.entries(collection.admin).filter(
|
|
579
|
+
([, v]) => v !== void 0 && typeof v !== "function"
|
|
580
|
+
);
|
|
581
|
+
if (adminEntries.length > 0) {
|
|
582
|
+
const adminObj = Object.fromEntries(adminEntries);
|
|
583
|
+
parts.push(`${indent} admin: ${serializeValue(adminObj, indent + " ")}`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (collection.auth) {
|
|
587
|
+
parts.push(`${indent} auth: true`);
|
|
588
|
+
}
|
|
589
|
+
if (collection.timestamps !== void 0) {
|
|
590
|
+
parts.push(`${indent} timestamps: ${serializeValue(collection.timestamps, indent + " ")}`);
|
|
591
|
+
}
|
|
592
|
+
if (collection.versions !== void 0) {
|
|
593
|
+
parts.push(`${indent} versions: ${serializeValue(collection.versions, indent + " ")}`);
|
|
594
|
+
}
|
|
595
|
+
if (collection.softDelete !== void 0) {
|
|
596
|
+
parts.push(`${indent} softDelete: ${serializeValue(collection.softDelete, indent + " ")}`);
|
|
597
|
+
}
|
|
598
|
+
if (collection.managed !== void 0) {
|
|
599
|
+
parts.push(`${indent} managed: ${String(collection.managed)}`);
|
|
600
|
+
}
|
|
601
|
+
if (collection.defaultSort) {
|
|
602
|
+
parts.push(`${indent} defaultSort: ${JSON.stringify(collection.defaultSort)}`);
|
|
603
|
+
}
|
|
604
|
+
return `{
|
|
605
|
+
${parts.join(",\n")},
|
|
606
|
+
${indent}}`;
|
|
607
|
+
}
|
|
608
|
+
function serializeGlobal(global, indent = " ") {
|
|
609
|
+
const parts = [];
|
|
610
|
+
parts.push(`${indent} slug: ${JSON.stringify(global.slug)}`);
|
|
611
|
+
if (global.label) {
|
|
612
|
+
parts.push(`${indent} label: ${JSON.stringify(global.label)}`);
|
|
613
|
+
}
|
|
614
|
+
parts.push(`${indent} fields: ${serializeFieldsArray(global.fields, indent + " ")}`);
|
|
615
|
+
if (global.admin) {
|
|
616
|
+
const adminEntries = Object.entries(global.admin).filter(
|
|
617
|
+
([, v]) => v !== void 0 && typeof v !== "function"
|
|
618
|
+
);
|
|
619
|
+
if (adminEntries.length > 0) {
|
|
620
|
+
const adminObj = Object.fromEntries(adminEntries);
|
|
621
|
+
parts.push(`${indent} admin: ${serializeValue(adminObj, indent + " ")}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (global.versions !== void 0) {
|
|
625
|
+
parts.push(`${indent} versions: ${serializeValue(global.versions, indent + " ")}`);
|
|
626
|
+
}
|
|
627
|
+
return `{
|
|
628
|
+
${parts.join(",\n")},
|
|
629
|
+
${indent}}`;
|
|
630
|
+
}
|
|
631
|
+
function computeRelativeImport(fromFile, toFile) {
|
|
632
|
+
const fromDir = (0, import_node_path.dirname)(fromFile);
|
|
633
|
+
let rel = (0, import_node_path.relative)(fromDir, toFile);
|
|
634
|
+
rel = rel.replace(/\.ts$/, "");
|
|
635
|
+
if (!rel.startsWith(".")) {
|
|
636
|
+
rel = "./" + rel;
|
|
637
|
+
}
|
|
638
|
+
return rel;
|
|
639
|
+
}
|
|
640
|
+
function generateAdminConfig(config, typesRelPath) {
|
|
641
|
+
const lines = [];
|
|
642
|
+
const allCollections = resolveAllCollections(config);
|
|
643
|
+
const globals = config.globals ?? [];
|
|
644
|
+
const plugins = config.plugins ?? [];
|
|
645
|
+
const pluginsWithAdminRoutes = plugins.filter(
|
|
646
|
+
(p) => p.browserImports?.adminRoutes && p.adminRoutes && p.adminRoutes.length > 0
|
|
647
|
+
);
|
|
648
|
+
lines.push("/**");
|
|
649
|
+
lines.push(" * AUTO-GENERATED by @momentumcms/core");
|
|
650
|
+
lines.push(" * DO NOT EDIT - regenerate with: nx run <app>:generate");
|
|
651
|
+
lines.push(" */");
|
|
652
|
+
lines.push("");
|
|
653
|
+
lines.push("import type { MomentumAdminConfig } from '@momentumcms/core';");
|
|
654
|
+
const typeImports = ["CollectionSlug"];
|
|
655
|
+
if (globals.length > 0) {
|
|
656
|
+
typeImports.push("GlobalSlug");
|
|
657
|
+
}
|
|
658
|
+
lines.push(`import type { ${typeImports.join(", ")} } from '${typesRelPath}';`);
|
|
659
|
+
for (const plugin of pluginsWithAdminRoutes) {
|
|
660
|
+
const imp = plugin.browserImports.adminRoutes;
|
|
661
|
+
lines.push(`import { ${imp.exportName} } from '${imp.path}';`);
|
|
662
|
+
}
|
|
663
|
+
lines.push("");
|
|
664
|
+
const genericParams = globals.length > 0 ? "<CollectionSlug, GlobalSlug>" : "<CollectionSlug>";
|
|
665
|
+
lines.push(`export const adminConfig: MomentumAdminConfig${genericParams} = {`);
|
|
666
|
+
if (allCollections.length > 0) {
|
|
667
|
+
const collectionItems = allCollections.map((c) => ` ${serializeCollection(c, " ")}`).join(",\n");
|
|
668
|
+
lines.push(` collections: [
|
|
669
|
+
${collectionItems},
|
|
670
|
+
],`);
|
|
671
|
+
} else {
|
|
672
|
+
lines.push(" collections: [],");
|
|
673
|
+
}
|
|
674
|
+
if (globals.length > 0) {
|
|
675
|
+
const globalItems = globals.map((g) => ` ${serializeGlobal(g, " ")}`).join(",\n");
|
|
676
|
+
lines.push(` globals: [
|
|
677
|
+
${globalItems},
|
|
678
|
+
],`);
|
|
679
|
+
}
|
|
680
|
+
if (config.admin) {
|
|
681
|
+
const adminObj = {};
|
|
682
|
+
if (config.admin.basePath)
|
|
683
|
+
adminObj["basePath"] = config.admin.basePath;
|
|
684
|
+
if (config.admin.branding)
|
|
685
|
+
adminObj["branding"] = config.admin.branding;
|
|
686
|
+
if (config.admin.toasts !== void 0)
|
|
687
|
+
adminObj["toasts"] = config.admin.toasts;
|
|
688
|
+
if (Object.keys(adminObj).length > 0) {
|
|
689
|
+
lines.push(` admin: ${serializeValue(adminObj)},`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (pluginsWithAdminRoutes.length > 0) {
|
|
693
|
+
const pluginItems = pluginsWithAdminRoutes.map((p) => {
|
|
694
|
+
const imp = p.browserImports.adminRoutes;
|
|
695
|
+
return ` { name: ${JSON.stringify(p.name)}, adminRoutes: ${imp.exportName} }`;
|
|
696
|
+
}).join(",\n");
|
|
697
|
+
lines.push(` plugins: [
|
|
698
|
+
${pluginItems},
|
|
699
|
+
],`);
|
|
700
|
+
}
|
|
701
|
+
lines.push("};");
|
|
702
|
+
lines.push("");
|
|
703
|
+
return lines.join("\n");
|
|
704
|
+
}
|
|
705
|
+
function parseArgs(args) {
|
|
706
|
+
const configPath = args[0];
|
|
707
|
+
let typesOutputPath = "";
|
|
708
|
+
let configOutputPath = "";
|
|
709
|
+
let watchMode = false;
|
|
710
|
+
for (let i = 1; i < args.length; i++) {
|
|
711
|
+
if (args[i] === "--types" && args[i + 1]) {
|
|
712
|
+
typesOutputPath = args[++i];
|
|
713
|
+
} else if (args[i] === "--config" && args[i + 1]) {
|
|
714
|
+
configOutputPath = args[++i];
|
|
715
|
+
} else if (args[i] === "--watch") {
|
|
716
|
+
watchMode = true;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (!configPath) {
|
|
720
|
+
console.error(
|
|
721
|
+
"Usage: npx tsx generator.ts <config-path> --types <types-output> --config <config-output> [--watch]"
|
|
722
|
+
);
|
|
723
|
+
process.exit(1);
|
|
724
|
+
}
|
|
725
|
+
if (!typesOutputPath) {
|
|
726
|
+
typesOutputPath = "src/generated/momentum.types.ts";
|
|
727
|
+
}
|
|
728
|
+
if (!configOutputPath) {
|
|
729
|
+
configOutputPath = "src/generated/momentum.config.ts";
|
|
730
|
+
}
|
|
731
|
+
return { configPath, typesOutputPath, configOutputPath, watch: watchMode };
|
|
732
|
+
}
|
|
733
|
+
async function runGenerator(options) {
|
|
734
|
+
const configPath = (0, import_node_path.resolve)(options.configPath);
|
|
735
|
+
const typesOutputPath = (0, import_node_path.resolve)(options.typesOutputPath);
|
|
736
|
+
const configOutputPath = (0, import_node_path.resolve)(options.configOutputPath);
|
|
737
|
+
console.info(`Generating from: ${configPath}`);
|
|
738
|
+
console.info(`Types output: ${typesOutputPath}`);
|
|
739
|
+
console.info(`Config output: ${configOutputPath}`);
|
|
740
|
+
const typesRelPath = computeRelativeImport(configOutputPath, typesOutputPath);
|
|
741
|
+
async function generate() {
|
|
742
|
+
try {
|
|
743
|
+
const config = await loadConfig(configPath);
|
|
744
|
+
const typesContent = generateTypes(config);
|
|
745
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(typesOutputPath), { recursive: true });
|
|
746
|
+
(0, import_node_fs.writeFileSync)(typesOutputPath, typesContent, "utf-8");
|
|
747
|
+
console.info(`Types generated: ${typesOutputPath}`);
|
|
748
|
+
const adminConfigContent = generateAdminConfig(config, typesRelPath);
|
|
749
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(configOutputPath), { recursive: true });
|
|
750
|
+
(0, import_node_fs.writeFileSync)(configOutputPath, adminConfigContent, "utf-8");
|
|
751
|
+
console.info(`Admin config generated: ${configOutputPath}`);
|
|
752
|
+
} catch (error) {
|
|
753
|
+
console.error(`Error generating:`, error);
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
await generate();
|
|
758
|
+
if (options.watch) {
|
|
759
|
+
console.info(`Watching for changes...`);
|
|
760
|
+
const configDir = (0, import_node_path.dirname)(configPath);
|
|
761
|
+
let debounceTimer = null;
|
|
762
|
+
(0, import_node_fs.watch)(configDir, { recursive: true }, (_eventType, filename) => {
|
|
763
|
+
if (!filename?.endsWith(".ts"))
|
|
764
|
+
return;
|
|
765
|
+
if (debounceTimer)
|
|
766
|
+
clearTimeout(debounceTimer);
|
|
767
|
+
debounceTimer = setTimeout(() => {
|
|
768
|
+
debounceTimer = null;
|
|
769
|
+
console.info(`Change detected: ${filename}`);
|
|
770
|
+
try {
|
|
771
|
+
(0, import_node_child_process.execFileSync)(
|
|
772
|
+
process.execPath,
|
|
773
|
+
[
|
|
774
|
+
...process.execArgv,
|
|
775
|
+
process.argv[1],
|
|
776
|
+
configPath,
|
|
777
|
+
"--types",
|
|
778
|
+
typesOutputPath,
|
|
779
|
+
"--config",
|
|
780
|
+
configOutputPath
|
|
781
|
+
],
|
|
782
|
+
{ stdio: "inherit" }
|
|
783
|
+
);
|
|
784
|
+
} catch {
|
|
785
|
+
}
|
|
786
|
+
}, 100);
|
|
787
|
+
});
|
|
788
|
+
return new Promise(() => {
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
return { success: true };
|
|
792
|
+
}
|
|
793
|
+
if (process.argv[1]?.endsWith("generator.ts") || process.argv[1]?.endsWith("generator.js") || process.argv[1]?.endsWith("generator.cjs")) {
|
|
794
|
+
const options = parseArgs(process.argv.slice(2));
|
|
795
|
+
runGenerator(options).then((result) => {
|
|
796
|
+
if (!result.success) {
|
|
797
|
+
process.exit(1);
|
|
798
|
+
}
|
|
799
|
+
}).catch((error) => {
|
|
800
|
+
console.error(error);
|
|
801
|
+
process.exit(1);
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
805
|
+
0 && (module.exports = {
|
|
806
|
+
computeRelativeImport,
|
|
807
|
+
generateAdminConfig,
|
|
808
|
+
generateTypes,
|
|
809
|
+
serializeCollection,
|
|
810
|
+
serializeField,
|
|
811
|
+
serializeGlobal,
|
|
812
|
+
serializeValue
|
|
813
|
+
});
|