@cms0/cms0 0.0.1 → 0.0.3

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.
@@ -9,6 +9,22 @@ const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const ts_morph_1 = require("ts-morph");
11
11
  const config_loader_js_1 = require("./config-loader.cjs");
12
+ const registry_js_1 = require("../../custom-types/registry.cjs");
13
+ function resolveCustomTypeName(type) {
14
+ const aliasSymbol = type.getAliasSymbol();
15
+ const symbol = type.getSymbol();
16
+ const name = aliasSymbol?.getName() ?? symbol?.getName();
17
+ if (name && registry_js_1.customTypeNames.has(name)) {
18
+ return name;
19
+ }
20
+ const text = type.getText();
21
+ for (const customName of registry_js_1.customTypeNames) {
22
+ if (text === customName || text.endsWith(`.${customName}`)) {
23
+ return customName;
24
+ }
25
+ }
26
+ return undefined;
27
+ }
12
28
  function unwrapOptional(type) {
13
29
  let optional = false;
14
30
  let nullable = false;
@@ -31,6 +47,14 @@ function unwrapOptional(type) {
31
47
  }
32
48
  return { base: type, optional, nullable };
33
49
  }
50
+ function applyOptionalityToDescriptor(desc, optional, nullable) {
51
+ const clone = JSON.parse(JSON.stringify(desc));
52
+ if (optional)
53
+ clone.optional = true;
54
+ if (nullable)
55
+ clone.nullable = true;
56
+ return clone;
57
+ }
34
58
  function collectModelNames(sourceFiles) {
35
59
  const names = new Set();
36
60
  sourceFiles.forEach((sf) => {
@@ -56,7 +80,9 @@ function collectReferencedModelNames(type, names, visited, ctx) {
56
80
  return;
57
81
  visited.add(key);
58
82
  if (base.isUnion()) {
59
- base.getUnionTypes().forEach((t, idx) => collectReferencedModelNames(t, names, visited, `${ctx}|${idx}`));
83
+ base
84
+ .getUnionTypes()
85
+ .forEach((t, idx) => collectReferencedModelNames(t, names, visited, `${ctx}|${idx}`));
60
86
  return;
61
87
  }
62
88
  if (base.isArray()) {
@@ -67,7 +93,11 @@ function collectReferencedModelNames(type, names, visited, ctx) {
67
93
  const aliasSymbol = base.getAliasSymbol();
68
94
  const symbol = base.getSymbol();
69
95
  const name = aliasSymbol?.getName() ?? symbol?.getName();
70
- if (name && base.isObject()) {
96
+ const customName = resolveCustomTypeName(base);
97
+ if (customName) {
98
+ names.add(customName);
99
+ }
100
+ else if (name && base.isObject()) {
71
101
  names.add(name);
72
102
  }
73
103
  if (base.isObject()) {
@@ -86,7 +116,10 @@ function buildProperties(type, modelNames, warnings, ctx) {
86
116
  if (!decl)
87
117
  return;
88
118
  const analyzed = unwrapOptional(decl.getType());
89
- props[prop.getName()] = descriptorFromType(analyzed.base, modelNames, warnings, `${ctx}.${prop.getName()}`, { optional: prop.isOptional() || analyzed.optional, nullable: analyzed.nullable });
119
+ props[prop.getName()] = descriptorFromType(analyzed.base, modelNames, warnings, `${ctx}.${prop.getName()}`, {
120
+ optional: prop.isOptional() || analyzed.optional,
121
+ nullable: analyzed.nullable,
122
+ });
90
123
  });
91
124
  return props;
92
125
  }
@@ -95,13 +128,35 @@ function descriptorFromType(type, modelNames, warnings, ctx, opts) {
95
128
  const isOptional = !!opts?.optional || optional;
96
129
  const isNullable = !!opts?.nullable || nullable;
97
130
  if (base.isString()) {
98
- return { kind: "primitive", type: "string", optional: isOptional, nullable: isNullable };
131
+ return {
132
+ kind: "primitive",
133
+ type: "string",
134
+ optional: isOptional,
135
+ nullable: isNullable,
136
+ };
99
137
  }
100
138
  if (base.isNumber()) {
101
- return { kind: "primitive", type: "number", optional: isOptional, nullable: isNullable };
139
+ return {
140
+ kind: "primitive",
141
+ type: "number",
142
+ optional: isOptional,
143
+ nullable: isNullable,
144
+ };
102
145
  }
103
146
  if (base.isBoolean()) {
104
- return { kind: "primitive", type: "boolean", optional: isOptional, nullable: isNullable };
147
+ return {
148
+ kind: "primitive",
149
+ type: "boolean",
150
+ optional: isOptional,
151
+ nullable: isNullable,
152
+ };
153
+ }
154
+ const customName = resolveCustomTypeName(base);
155
+ if (customName && registry_js_1.customInlineTypeNames.has(customName)) {
156
+ const inlineDescriptor = registry_js_1.customInlineDescriptors[customName];
157
+ if (inlineDescriptor) {
158
+ return applyOptionalityToDescriptor(inlineDescriptor, isOptional, isNullable);
159
+ }
105
160
  }
106
161
  if (base.isArray()) {
107
162
  const elem = base.getArrayElementTypeOrThrow();
@@ -115,8 +170,14 @@ function descriptorFromType(type, modelNames, warnings, ctx, opts) {
115
170
  const aliasSymbol = base.getAliasSymbol();
116
171
  const symbol = base.getSymbol();
117
172
  const name = aliasSymbol?.getName() ?? symbol?.getName();
118
- if (name && modelNames.has(name)) {
119
- return { kind: "modelRef", model: name, optional: isOptional, nullable: isNullable };
173
+ const modelName = customName ?? name;
174
+ if (modelName && modelNames.has(modelName)) {
175
+ return {
176
+ kind: "modelRef",
177
+ model: modelName,
178
+ optional: isOptional,
179
+ nullable: isNullable,
180
+ };
120
181
  }
121
182
  if (base.isObject()) {
122
183
  return {
@@ -127,7 +188,12 @@ function descriptorFromType(type, modelNames, warnings, ctx, opts) {
127
188
  };
128
189
  }
129
190
  warnings.add(`cms0: unsupported type at ${ctx}; falling back to string`);
130
- return { kind: "primitive", type: "string", optional: isOptional, nullable: isNullable };
191
+ return {
192
+ kind: "primitive",
193
+ type: "string",
194
+ optional: isOptional,
195
+ nullable: isNullable,
196
+ };
131
197
  }
132
198
  function collectModels(sourceFiles, modelNames, modelMap, warnings) {
133
199
  const handleShape = (name, type, ctx) => {
@@ -137,7 +203,10 @@ function collectModels(sourceFiles, modelNames, modelMap, warnings) {
137
203
  return;
138
204
  if (!type.isObject())
139
205
  return;
140
- modelMap[name] = { kind: "model", properties: buildProperties(type, modelNames, warnings, ctx) };
206
+ modelMap[name] = {
207
+ kind: "model",
208
+ properties: buildProperties(type, modelNames, warnings, ctx),
209
+ };
141
210
  };
142
211
  sourceFiles.forEach((sf) => {
143
212
  sf.getTypeAliases().forEach((alias) => {
@@ -152,24 +221,57 @@ function collectModels(sourceFiles, modelNames, modelMap, warnings) {
152
221
  });
153
222
  });
154
223
  }
155
- function findRootType(sourceFiles) {
224
+ function readCms0Options(invoc) {
225
+ const arg = invoc.getArguments()[0];
226
+ if (!arg || !ts_morph_1.Node.isObjectLiteralExpression(arg))
227
+ return {};
228
+ const options = {};
229
+ const localesProp = arg.getProperty("locales");
230
+ if (localesProp && ts_morph_1.Node.isPropertyAssignment(localesProp)) {
231
+ const init = localesProp.getInitializer();
232
+ if (init && ts_morph_1.Node.isArrayLiteralExpression(init)) {
233
+ const values = init
234
+ .getElements()
235
+ .map((el) => (ts_morph_1.Node.isStringLiteral(el) ? el.getLiteralValue() : null))
236
+ .filter((val) => Boolean(val));
237
+ if (values.length)
238
+ options.locales = values;
239
+ }
240
+ }
241
+ const defaultProp = arg.getProperty("defaultLocale");
242
+ if (defaultProp && ts_morph_1.Node.isPropertyAssignment(defaultProp)) {
243
+ const init = defaultProp.getInitializer();
244
+ if (init && ts_morph_1.Node.isStringLiteral(init)) {
245
+ options.defaultLocale = init.getLiteralValue();
246
+ }
247
+ }
248
+ return options;
249
+ }
250
+ function findRootInvocation(sourceFiles) {
156
251
  for (const sf of sourceFiles) {
157
252
  const invoc = sf
158
253
  ?.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
159
254
  .find((call) => call.getExpression().getText() === "cms0");
160
255
  if (invoc) {
161
256
  const typeArgs = invoc.getTypeArguments();
162
- if (typeArgs.length > 0)
163
- return typeArgs[0].getType();
257
+ if (typeArgs.length > 0) {
258
+ return {
259
+ rootType: typeArgs[0].getType(),
260
+ ...readCms0Options(invoc),
261
+ };
262
+ }
164
263
  }
165
264
  }
166
265
  return undefined;
167
266
  }
168
267
  function buildDescriptorAlt(resolved) {
169
268
  const tsconfig = resolved.tsconfigPath ?? (0, config_loader_js_1.findTsConfig)(resolved.entryFile);
170
- const project = tsconfig ? new ts_morph_1.Project({ tsConfigFilePath: tsconfig }) : new ts_morph_1.Project();
269
+ const project = tsconfig
270
+ ? new ts_morph_1.Project({ tsConfigFilePath: tsconfig })
271
+ : new ts_morph_1.Project();
171
272
  const warnings = new Set();
172
- if (!project.getSourceFile(resolved.entryFile) && fs_1.default.existsSync(resolved.entryFile)) {
273
+ if (!project.getSourceFile(resolved.entryFile) &&
274
+ fs_1.default.existsSync(resolved.entryFile)) {
173
275
  project.addSourceFileAtPath(resolved.entryFile);
174
276
  }
175
277
  const sourceFiles = project.getSourceFiles().filter((sf) => {
@@ -181,16 +283,36 @@ function buildDescriptorAlt(resolved) {
181
283
  const rel = path_1.default.relative(path_1.default.dirname(resolved.entryFile), filePath);
182
284
  return rel && !rel.startsWith("..") && !path_1.default.isAbsolute(rel);
183
285
  });
184
- const rootType = findRootType(sourceFiles);
185
- if (!rootType)
286
+ const invocation = findRootInvocation(sourceFiles);
287
+ if (!invocation)
186
288
  throw new Error("Could not locate cms0<T>() invocation in project sources.");
289
+ const rootType = invocation.rootType;
187
290
  // Restrict models to only those referenced by the root type tree and exported in scope.
188
291
  const exportedModelNames = collectModelNames(sourceFiles);
292
+ const collisions = Array.from(registry_js_1.customTypeNames).filter((name) => exportedModelNames.has(name));
293
+ if (collisions.length) {
294
+ throw new Error(`cms0: custom type name collision: ${collisions.join(", ")}. Rename your model(s) or use a different custom type name.`);
295
+ }
189
296
  const referencedModelNames = new Set();
190
297
  collectReferencedModelNames(rootType, referencedModelNames, new Set(), "root");
191
- const modelNames = new Set(Array.from(referencedModelNames).filter((n) => exportedModelNames.has(n)));
298
+ const referencedCustomTypeNames = new Set(Array.from(referencedModelNames).filter((n) => registry_js_1.customTypeNames.has(n)));
299
+ const referencedCustomModelNames = new Set(Array.from(referencedCustomTypeNames).filter((n) => registry_js_1.customModelTypeNames.has(n)));
300
+ const resolvedCustomTypeNames = (0, registry_js_1.resolveCustomTypeDependencies)(referencedCustomModelNames);
301
+ const modelNames = new Set([
302
+ ...Array.from(referencedModelNames).filter((n) => exportedModelNames.has(n)),
303
+ ...Array.from(resolvedCustomTypeNames),
304
+ ]);
192
305
  const modelMap = {};
193
306
  collectModels(sourceFiles, modelNames, modelMap, warnings);
307
+ resolvedCustomTypeNames.forEach((name) => {
308
+ if (modelMap[name]) {
309
+ return;
310
+ }
311
+ const descriptor = registry_js_1.customModelDescriptors[name];
312
+ if (descriptor) {
313
+ modelMap[name] = descriptor;
314
+ }
315
+ });
194
316
  const roots = {};
195
317
  for (const prop of rootType.getProperties()) {
196
318
  const name = prop.getName();
@@ -217,5 +339,16 @@ function buildDescriptorAlt(resolved) {
217
339
  }
218
340
  if (warnings.size)
219
341
  warnings.forEach((w) => console.warn(w));
220
- return { models: modelMap, roots };
342
+ const locales = invocation.locales;
343
+ const defaultLocale = invocation.defaultLocale;
344
+ return {
345
+ models: modelMap,
346
+ roots,
347
+ metadata: locales || defaultLocale
348
+ ? {
349
+ locales,
350
+ defaultLocale,
351
+ }
352
+ : undefined,
353
+ };
221
354
  }
@@ -127,7 +127,33 @@ function collectModels(sourceFiles, modelMap, warnings) {
127
127
  });
128
128
  });
129
129
  }
130
- function findRootType(sourceFiles) {
130
+ function readCms0Options(invoc) {
131
+ const arg = invoc.getArguments()[0];
132
+ if (!arg || !ts_morph_1.Node.isObjectLiteralExpression(arg))
133
+ return {};
134
+ const options = {};
135
+ const localesProp = arg.getProperty("locales");
136
+ if (localesProp && ts_morph_1.Node.isPropertyAssignment(localesProp)) {
137
+ const init = localesProp.getInitializer();
138
+ if (init && ts_morph_1.Node.isArrayLiteralExpression(init)) {
139
+ const values = init
140
+ .getElements()
141
+ .map((el) => (ts_morph_1.Node.isStringLiteral(el) ? el.getLiteralValue() : null))
142
+ .filter((val) => Boolean(val));
143
+ if (values.length)
144
+ options.locales = values;
145
+ }
146
+ }
147
+ const defaultProp = arg.getProperty("defaultLocale");
148
+ if (defaultProp && ts_morph_1.Node.isPropertyAssignment(defaultProp)) {
149
+ const init = defaultProp.getInitializer();
150
+ if (init && ts_morph_1.Node.isStringLiteral(init)) {
151
+ options.defaultLocale = init.getLiteralValue();
152
+ }
153
+ }
154
+ return options;
155
+ }
156
+ function findRootInvocation(sourceFiles) {
131
157
  for (const sf of sourceFiles) {
132
158
  const invoc = sf
133
159
  ?.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
@@ -135,7 +161,10 @@ function findRootType(sourceFiles) {
135
161
  if (invoc) {
136
162
  const typeArgs = invoc.getTypeArguments();
137
163
  if (typeArgs.length > 0) {
138
- return typeArgs[0].getType();
164
+ return {
165
+ rootType: typeArgs[0].getType(),
166
+ ...readCms0Options(invoc),
167
+ };
139
168
  }
140
169
  }
141
170
  }
@@ -159,10 +188,11 @@ function buildDescriptor(resolved) {
159
188
  });
160
189
  const modelMap = {};
161
190
  collectModels(sourceFiles, modelMap, warnings);
162
- const rootType = findRootType(sourceFiles);
163
- if (!rootType) {
191
+ const invocation = findRootInvocation(sourceFiles);
192
+ if (!invocation) {
164
193
  throw new Error("Could not locate cms0<T>() invocation in project sources.");
165
194
  }
195
+ const rootType = invocation.rootType;
166
196
  const roots = {};
167
197
  for (const prop of rootType.getProperties()) {
168
198
  const name = prop.getName();
@@ -228,5 +258,16 @@ function buildDescriptor(resolved) {
228
258
  if (warnings.size) {
229
259
  warnings.forEach((w) => console.warn(w));
230
260
  }
231
- return { models: modelMap, roots };
261
+ const locales = invocation.locales;
262
+ const defaultLocale = invocation.defaultLocale;
263
+ return {
264
+ models: modelMap,
265
+ roots,
266
+ metadata: locales || defaultLocale
267
+ ? {
268
+ locales,
269
+ defaultLocale,
270
+ }
271
+ : undefined,
272
+ };
232
273
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,119 @@
1
+ const primitive = (type) => ({
2
+ kind: "primitive",
3
+ type,
4
+ optional: false,
5
+ nullable: false,
6
+ });
7
+ const fileFieldDescriptors = {
8
+ name: primitive("string"),
9
+ filename: primitive("string"),
10
+ extension: primitive("string"),
11
+ mimeType: primitive("string"),
12
+ size: primitive("number"),
13
+ };
14
+ const fileDescriptor = {
15
+ kind: "model",
16
+ properties: fileFieldDescriptors,
17
+ };
18
+ const imageDescriptor = {
19
+ kind: "model",
20
+ properties: {
21
+ ...fileFieldDescriptors,
22
+ width: primitive("number"),
23
+ height: primitive("number"),
24
+ alt: { kind: "primitive", type: "string", optional: true, nullable: false },
25
+ },
26
+ };
27
+ const videoDescriptor = {
28
+ kind: "model",
29
+ properties: {
30
+ ...fileFieldDescriptors,
31
+ width: primitive("number"),
32
+ height: primitive("number"),
33
+ length: primitive("number"),
34
+ alt: { kind: "primitive", type: "string", optional: true, nullable: false },
35
+ },
36
+ };
37
+ const richTextDescriptor = {
38
+ kind: "primitive",
39
+ type: "json",
40
+ customType: "RichText",
41
+ };
42
+ const localizedStringDescriptor = {
43
+ type: "array",
44
+ items: {
45
+ type: "object",
46
+ properties: {
47
+ locale: primitive("string"),
48
+ isDefault: primitive("boolean"),
49
+ value: primitive("string"),
50
+ },
51
+ },
52
+ customType: "LocalizedString",
53
+ };
54
+ const localizedRichTextDescriptor = {
55
+ type: "array",
56
+ items: {
57
+ type: "object",
58
+ properties: {
59
+ locale: primitive("string"),
60
+ isDefault: primitive("boolean"),
61
+ value: primitive("json"),
62
+ html: primitive("string"),
63
+ },
64
+ },
65
+ customType: "LocalizedRichText",
66
+ };
67
+ export const customModelDescriptors = {
68
+ File: fileDescriptor,
69
+ Image: imageDescriptor,
70
+ Video: videoDescriptor,
71
+ };
72
+ export const customInlineDescriptors = {
73
+ RichText: richTextDescriptor,
74
+ LocalizedString: localizedStringDescriptor,
75
+ LocalizedRichText: localizedRichTextDescriptor,
76
+ };
77
+ export const customTypeDescriptors = {
78
+ ...customModelDescriptors,
79
+ ...customInlineDescriptors,
80
+ };
81
+ export const customTypeNames = new Set(Object.keys(customTypeDescriptors));
82
+ export const customModelTypeNames = new Set(Object.keys(customModelDescriptors));
83
+ export const customInlineTypeNames = new Set(Object.keys(customInlineDescriptors));
84
+ export function resolveCustomTypeDependencies(names) {
85
+ const resolved = new Set();
86
+ const queue = Array.from(names);
87
+ while (queue.length) {
88
+ const name = queue.shift();
89
+ if (!name || resolved.has(name))
90
+ continue;
91
+ const descriptor = customModelDescriptors[name];
92
+ if (!descriptor)
93
+ continue;
94
+ resolved.add(name);
95
+ const refs = new Set();
96
+ for (const prop of Object.values(descriptor.properties)) {
97
+ collectModelRefs(prop, refs);
98
+ }
99
+ refs.forEach((ref) => {
100
+ if (customTypeDescriptors[ref] && !resolved.has(ref)) {
101
+ queue.push(ref);
102
+ }
103
+ });
104
+ }
105
+ return resolved;
106
+ }
107
+ function collectModelRefs(desc, out) {
108
+ if (desc.kind === "modelRef") {
109
+ out.add(desc.model);
110
+ return;
111
+ }
112
+ if (desc.type === "array") {
113
+ collectModelRefs(desc.items, out);
114
+ return;
115
+ }
116
+ if (desc.type === "object") {
117
+ Object.values(desc.properties).forEach((child) => collectModelRefs(child, out));
118
+ }
119
+ }