@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.
@@ -1,8 +1,24 @@
1
1
  // Fresh descriptor builder using tree-based traversal from cms0<T>.
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import { Project, SyntaxKind, } from "ts-morph";
4
+ import { Project, SyntaxKind, Node, } from "ts-morph";
5
5
  import { findTsConfig } from "./config-loader.js";
6
+ import { customTypeNames, customInlineDescriptors, customInlineTypeNames, customModelDescriptors, customModelTypeNames, resolveCustomTypeDependencies, } from "../../custom-types/registry.js";
7
+ function resolveCustomTypeName(type) {
8
+ const aliasSymbol = type.getAliasSymbol();
9
+ const symbol = type.getSymbol();
10
+ const name = aliasSymbol?.getName() ?? symbol?.getName();
11
+ if (name && customTypeNames.has(name)) {
12
+ return name;
13
+ }
14
+ const text = type.getText();
15
+ for (const customName of customTypeNames) {
16
+ if (text === customName || text.endsWith(`.${customName}`)) {
17
+ return customName;
18
+ }
19
+ }
20
+ return undefined;
21
+ }
6
22
  function unwrapOptional(type) {
7
23
  let optional = false;
8
24
  let nullable = false;
@@ -25,6 +41,14 @@ function unwrapOptional(type) {
25
41
  }
26
42
  return { base: type, optional, nullable };
27
43
  }
44
+ function applyOptionalityToDescriptor(desc, optional, nullable) {
45
+ const clone = JSON.parse(JSON.stringify(desc));
46
+ if (optional)
47
+ clone.optional = true;
48
+ if (nullable)
49
+ clone.nullable = true;
50
+ return clone;
51
+ }
28
52
  function collectModelNames(sourceFiles) {
29
53
  const names = new Set();
30
54
  sourceFiles.forEach((sf) => {
@@ -50,7 +74,9 @@ function collectReferencedModelNames(type, names, visited, ctx) {
50
74
  return;
51
75
  visited.add(key);
52
76
  if (base.isUnion()) {
53
- base.getUnionTypes().forEach((t, idx) => collectReferencedModelNames(t, names, visited, `${ctx}|${idx}`));
77
+ base
78
+ .getUnionTypes()
79
+ .forEach((t, idx) => collectReferencedModelNames(t, names, visited, `${ctx}|${idx}`));
54
80
  return;
55
81
  }
56
82
  if (base.isArray()) {
@@ -61,7 +87,11 @@ function collectReferencedModelNames(type, names, visited, ctx) {
61
87
  const aliasSymbol = base.getAliasSymbol();
62
88
  const symbol = base.getSymbol();
63
89
  const name = aliasSymbol?.getName() ?? symbol?.getName();
64
- if (name && base.isObject()) {
90
+ const customName = resolveCustomTypeName(base);
91
+ if (customName) {
92
+ names.add(customName);
93
+ }
94
+ else if (name && base.isObject()) {
65
95
  names.add(name);
66
96
  }
67
97
  if (base.isObject()) {
@@ -80,7 +110,10 @@ function buildProperties(type, modelNames, warnings, ctx) {
80
110
  if (!decl)
81
111
  return;
82
112
  const analyzed = unwrapOptional(decl.getType());
83
- props[prop.getName()] = descriptorFromType(analyzed.base, modelNames, warnings, `${ctx}.${prop.getName()}`, { optional: prop.isOptional() || analyzed.optional, nullable: analyzed.nullable });
113
+ props[prop.getName()] = descriptorFromType(analyzed.base, modelNames, warnings, `${ctx}.${prop.getName()}`, {
114
+ optional: prop.isOptional() || analyzed.optional,
115
+ nullable: analyzed.nullable,
116
+ });
84
117
  });
85
118
  return props;
86
119
  }
@@ -89,13 +122,35 @@ function descriptorFromType(type, modelNames, warnings, ctx, opts) {
89
122
  const isOptional = !!opts?.optional || optional;
90
123
  const isNullable = !!opts?.nullable || nullable;
91
124
  if (base.isString()) {
92
- return { kind: "primitive", type: "string", optional: isOptional, nullable: isNullable };
125
+ return {
126
+ kind: "primitive",
127
+ type: "string",
128
+ optional: isOptional,
129
+ nullable: isNullable,
130
+ };
93
131
  }
94
132
  if (base.isNumber()) {
95
- return { kind: "primitive", type: "number", optional: isOptional, nullable: isNullable };
133
+ return {
134
+ kind: "primitive",
135
+ type: "number",
136
+ optional: isOptional,
137
+ nullable: isNullable,
138
+ };
96
139
  }
97
140
  if (base.isBoolean()) {
98
- return { kind: "primitive", type: "boolean", optional: isOptional, nullable: isNullable };
141
+ return {
142
+ kind: "primitive",
143
+ type: "boolean",
144
+ optional: isOptional,
145
+ nullable: isNullable,
146
+ };
147
+ }
148
+ const customName = resolveCustomTypeName(base);
149
+ if (customName && customInlineTypeNames.has(customName)) {
150
+ const inlineDescriptor = customInlineDescriptors[customName];
151
+ if (inlineDescriptor) {
152
+ return applyOptionalityToDescriptor(inlineDescriptor, isOptional, isNullable);
153
+ }
99
154
  }
100
155
  if (base.isArray()) {
101
156
  const elem = base.getArrayElementTypeOrThrow();
@@ -109,8 +164,14 @@ function descriptorFromType(type, modelNames, warnings, ctx, opts) {
109
164
  const aliasSymbol = base.getAliasSymbol();
110
165
  const symbol = base.getSymbol();
111
166
  const name = aliasSymbol?.getName() ?? symbol?.getName();
112
- if (name && modelNames.has(name)) {
113
- return { kind: "modelRef", model: name, optional: isOptional, nullable: isNullable };
167
+ const modelName = customName ?? name;
168
+ if (modelName && modelNames.has(modelName)) {
169
+ return {
170
+ kind: "modelRef",
171
+ model: modelName,
172
+ optional: isOptional,
173
+ nullable: isNullable,
174
+ };
114
175
  }
115
176
  if (base.isObject()) {
116
177
  return {
@@ -121,7 +182,12 @@ function descriptorFromType(type, modelNames, warnings, ctx, opts) {
121
182
  };
122
183
  }
123
184
  warnings.add(`cms0: unsupported type at ${ctx}; falling back to string`);
124
- return { kind: "primitive", type: "string", optional: isOptional, nullable: isNullable };
185
+ return {
186
+ kind: "primitive",
187
+ type: "string",
188
+ optional: isOptional,
189
+ nullable: isNullable,
190
+ };
125
191
  }
126
192
  function collectModels(sourceFiles, modelNames, modelMap, warnings) {
127
193
  const handleShape = (name, type, ctx) => {
@@ -131,7 +197,10 @@ function collectModels(sourceFiles, modelNames, modelMap, warnings) {
131
197
  return;
132
198
  if (!type.isObject())
133
199
  return;
134
- modelMap[name] = { kind: "model", properties: buildProperties(type, modelNames, warnings, ctx) };
200
+ modelMap[name] = {
201
+ kind: "model",
202
+ properties: buildProperties(type, modelNames, warnings, ctx),
203
+ };
135
204
  };
136
205
  sourceFiles.forEach((sf) => {
137
206
  sf.getTypeAliases().forEach((alias) => {
@@ -146,24 +215,57 @@ function collectModels(sourceFiles, modelNames, modelMap, warnings) {
146
215
  });
147
216
  });
148
217
  }
149
- function findRootType(sourceFiles) {
218
+ function readCms0Options(invoc) {
219
+ const arg = invoc.getArguments()[0];
220
+ if (!arg || !Node.isObjectLiteralExpression(arg))
221
+ return {};
222
+ const options = {};
223
+ const localesProp = arg.getProperty("locales");
224
+ if (localesProp && Node.isPropertyAssignment(localesProp)) {
225
+ const init = localesProp.getInitializer();
226
+ if (init && Node.isArrayLiteralExpression(init)) {
227
+ const values = init
228
+ .getElements()
229
+ .map((el) => (Node.isStringLiteral(el) ? el.getLiteralValue() : null))
230
+ .filter((val) => Boolean(val));
231
+ if (values.length)
232
+ options.locales = values;
233
+ }
234
+ }
235
+ const defaultProp = arg.getProperty("defaultLocale");
236
+ if (defaultProp && Node.isPropertyAssignment(defaultProp)) {
237
+ const init = defaultProp.getInitializer();
238
+ if (init && Node.isStringLiteral(init)) {
239
+ options.defaultLocale = init.getLiteralValue();
240
+ }
241
+ }
242
+ return options;
243
+ }
244
+ function findRootInvocation(sourceFiles) {
150
245
  for (const sf of sourceFiles) {
151
246
  const invoc = sf
152
247
  ?.getDescendantsOfKind(SyntaxKind.CallExpression)
153
248
  .find((call) => call.getExpression().getText() === "cms0");
154
249
  if (invoc) {
155
250
  const typeArgs = invoc.getTypeArguments();
156
- if (typeArgs.length > 0)
157
- return typeArgs[0].getType();
251
+ if (typeArgs.length > 0) {
252
+ return {
253
+ rootType: typeArgs[0].getType(),
254
+ ...readCms0Options(invoc),
255
+ };
256
+ }
158
257
  }
159
258
  }
160
259
  return undefined;
161
260
  }
162
261
  function buildDescriptorAlt(resolved) {
163
262
  const tsconfig = resolved.tsconfigPath ?? findTsConfig(resolved.entryFile);
164
- const project = tsconfig ? new Project({ tsConfigFilePath: tsconfig }) : new Project();
263
+ const project = tsconfig
264
+ ? new Project({ tsConfigFilePath: tsconfig })
265
+ : new Project();
165
266
  const warnings = new Set();
166
- if (!project.getSourceFile(resolved.entryFile) && fs.existsSync(resolved.entryFile)) {
267
+ if (!project.getSourceFile(resolved.entryFile) &&
268
+ fs.existsSync(resolved.entryFile)) {
167
269
  project.addSourceFileAtPath(resolved.entryFile);
168
270
  }
169
271
  const sourceFiles = project.getSourceFiles().filter((sf) => {
@@ -175,16 +277,36 @@ function buildDescriptorAlt(resolved) {
175
277
  const rel = path.relative(path.dirname(resolved.entryFile), filePath);
176
278
  return rel && !rel.startsWith("..") && !path.isAbsolute(rel);
177
279
  });
178
- const rootType = findRootType(sourceFiles);
179
- if (!rootType)
280
+ const invocation = findRootInvocation(sourceFiles);
281
+ if (!invocation)
180
282
  throw new Error("Could not locate cms0<T>() invocation in project sources.");
283
+ const rootType = invocation.rootType;
181
284
  // Restrict models to only those referenced by the root type tree and exported in scope.
182
285
  const exportedModelNames = collectModelNames(sourceFiles);
286
+ const collisions = Array.from(customTypeNames).filter((name) => exportedModelNames.has(name));
287
+ if (collisions.length) {
288
+ throw new Error(`cms0: custom type name collision: ${collisions.join(", ")}. Rename your model(s) or use a different custom type name.`);
289
+ }
183
290
  const referencedModelNames = new Set();
184
291
  collectReferencedModelNames(rootType, referencedModelNames, new Set(), "root");
185
- const modelNames = new Set(Array.from(referencedModelNames).filter((n) => exportedModelNames.has(n)));
292
+ const referencedCustomTypeNames = new Set(Array.from(referencedModelNames).filter((n) => customTypeNames.has(n)));
293
+ const referencedCustomModelNames = new Set(Array.from(referencedCustomTypeNames).filter((n) => customModelTypeNames.has(n)));
294
+ const resolvedCustomTypeNames = resolveCustomTypeDependencies(referencedCustomModelNames);
295
+ const modelNames = new Set([
296
+ ...Array.from(referencedModelNames).filter((n) => exportedModelNames.has(n)),
297
+ ...Array.from(resolvedCustomTypeNames),
298
+ ]);
186
299
  const modelMap = {};
187
300
  collectModels(sourceFiles, modelNames, modelMap, warnings);
301
+ resolvedCustomTypeNames.forEach((name) => {
302
+ if (modelMap[name]) {
303
+ return;
304
+ }
305
+ const descriptor = customModelDescriptors[name];
306
+ if (descriptor) {
307
+ modelMap[name] = descriptor;
308
+ }
309
+ });
188
310
  const roots = {};
189
311
  for (const prop of rootType.getProperties()) {
190
312
  const name = prop.getName();
@@ -211,6 +333,17 @@ function buildDescriptorAlt(resolved) {
211
333
  }
212
334
  if (warnings.size)
213
335
  warnings.forEach((w) => console.warn(w));
214
- return { models: modelMap, roots };
336
+ const locales = invocation.locales;
337
+ const defaultLocale = invocation.defaultLocale;
338
+ return {
339
+ models: modelMap,
340
+ roots,
341
+ metadata: locales || defaultLocale
342
+ ? {
343
+ locales,
344
+ defaultLocale,
345
+ }
346
+ : undefined,
347
+ };
215
348
  }
216
349
  export { buildDescriptorAlt };
@@ -1,7 +1,7 @@
1
1
  // Build a schema descriptor by inspecting user types with ts-morph.
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import { Project, SyntaxKind, } from "ts-morph";
4
+ import { Project, SyntaxKind, Node, } from "ts-morph";
5
5
  import { findTsConfig } from "./config-loader.js";
6
6
  function unwrapOptional(type) {
7
7
  let optional = false;
@@ -121,7 +121,33 @@ function collectModels(sourceFiles, modelMap, warnings) {
121
121
  });
122
122
  });
123
123
  }
124
- function findRootType(sourceFiles) {
124
+ function readCms0Options(invoc) {
125
+ const arg = invoc.getArguments()[0];
126
+ if (!arg || !Node.isObjectLiteralExpression(arg))
127
+ return {};
128
+ const options = {};
129
+ const localesProp = arg.getProperty("locales");
130
+ if (localesProp && Node.isPropertyAssignment(localesProp)) {
131
+ const init = localesProp.getInitializer();
132
+ if (init && Node.isArrayLiteralExpression(init)) {
133
+ const values = init
134
+ .getElements()
135
+ .map((el) => (Node.isStringLiteral(el) ? el.getLiteralValue() : null))
136
+ .filter((val) => Boolean(val));
137
+ if (values.length)
138
+ options.locales = values;
139
+ }
140
+ }
141
+ const defaultProp = arg.getProperty("defaultLocale");
142
+ if (defaultProp && Node.isPropertyAssignment(defaultProp)) {
143
+ const init = defaultProp.getInitializer();
144
+ if (init && Node.isStringLiteral(init)) {
145
+ options.defaultLocale = init.getLiteralValue();
146
+ }
147
+ }
148
+ return options;
149
+ }
150
+ function findRootInvocation(sourceFiles) {
125
151
  for (const sf of sourceFiles) {
126
152
  const invoc = sf
127
153
  ?.getDescendantsOfKind(SyntaxKind.CallExpression)
@@ -129,7 +155,10 @@ function findRootType(sourceFiles) {
129
155
  if (invoc) {
130
156
  const typeArgs = invoc.getTypeArguments();
131
157
  if (typeArgs.length > 0) {
132
- return typeArgs[0].getType();
158
+ return {
159
+ rootType: typeArgs[0].getType(),
160
+ ...readCms0Options(invoc),
161
+ };
133
162
  }
134
163
  }
135
164
  }
@@ -153,10 +182,11 @@ function buildDescriptor(resolved) {
153
182
  });
154
183
  const modelMap = {};
155
184
  collectModels(sourceFiles, modelMap, warnings);
156
- const rootType = findRootType(sourceFiles);
157
- if (!rootType) {
185
+ const invocation = findRootInvocation(sourceFiles);
186
+ if (!invocation) {
158
187
  throw new Error("Could not locate cms0<T>() invocation in project sources.");
159
188
  }
189
+ const rootType = invocation.rootType;
160
190
  const roots = {};
161
191
  for (const prop of rootType.getProperties()) {
162
192
  const name = prop.getName();
@@ -222,6 +252,17 @@ function buildDescriptor(resolved) {
222
252
  if (warnings.size) {
223
253
  warnings.forEach((w) => console.warn(w));
224
254
  }
225
- return { models: modelMap, roots };
255
+ const locales = invocation.locales;
256
+ const defaultLocale = invocation.defaultLocale;
257
+ return {
258
+ models: modelMap,
259
+ roots,
260
+ metadata: locales || defaultLocale
261
+ ? {
262
+ locales,
263
+ defaultLocale,
264
+ }
265
+ : undefined,
266
+ };
226
267
  }
227
268
  export { buildDescriptor };
@@ -0,0 +1,35 @@
1
+ export type FileBase = {
2
+ name: string;
3
+ filename: string;
4
+ extension: string;
5
+ mimeType: string;
6
+ size: number;
7
+ };
8
+ export type File = FileBase;
9
+ export type Image = FileBase & {
10
+ width: number;
11
+ height: number;
12
+ alt?: string;
13
+ };
14
+ export type Video = FileBase & {
15
+ width: number;
16
+ height: number;
17
+ length: number;
18
+ alt?: string;
19
+ };
20
+ export type RichText = {
21
+ value: Record<string, any>;
22
+ html: string;
23
+ };
24
+ export type LocalizedString = Array<{
25
+ locale: string;
26
+ isDefault: boolean;
27
+ value: string;
28
+ }>;
29
+ export type LocalizedRichText = Array<{
30
+ locale: string;
31
+ isDefault: boolean;
32
+ value: Record<string, any>;
33
+ html: string;
34
+ }>;
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/custom-types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG,QAAQ,CAAC;AAE5B,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,KAAK,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,KAAK,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CAAC"}
@@ -0,0 +1,65 @@
1
+ import type { FieldDescriptor, ModelDescriptor, PrimitiveType } from "@cms0/shared";
2
+ export declare const customModelDescriptors: {
3
+ readonly File: ModelDescriptor;
4
+ readonly Image: ModelDescriptor;
5
+ readonly Video: ModelDescriptor;
6
+ };
7
+ export declare const customInlineDescriptors: {
8
+ readonly RichText: {
9
+ kind?: "primitive";
10
+ type: PrimitiveType;
11
+ optional?: boolean;
12
+ nullable?: boolean;
13
+ customType?: string;
14
+ };
15
+ readonly LocalizedString: {
16
+ kind?: "array";
17
+ type: "array";
18
+ items: FieldDescriptor;
19
+ optional?: boolean;
20
+ nullable?: boolean;
21
+ customType?: string;
22
+ };
23
+ readonly LocalizedRichText: {
24
+ kind?: "array";
25
+ type: "array";
26
+ items: FieldDescriptor;
27
+ optional?: boolean;
28
+ nullable?: boolean;
29
+ customType?: string;
30
+ };
31
+ };
32
+ export declare const customTypeDescriptors: {
33
+ readonly RichText: {
34
+ kind?: "primitive";
35
+ type: PrimitiveType;
36
+ optional?: boolean;
37
+ nullable?: boolean;
38
+ customType?: string;
39
+ };
40
+ readonly LocalizedString: {
41
+ kind?: "array";
42
+ type: "array";
43
+ items: FieldDescriptor;
44
+ optional?: boolean;
45
+ nullable?: boolean;
46
+ customType?: string;
47
+ };
48
+ readonly LocalizedRichText: {
49
+ kind?: "array";
50
+ type: "array";
51
+ items: FieldDescriptor;
52
+ optional?: boolean;
53
+ nullable?: boolean;
54
+ customType?: string;
55
+ };
56
+ readonly File: ModelDescriptor;
57
+ readonly Image: ModelDescriptor;
58
+ readonly Video: ModelDescriptor;
59
+ };
60
+ export type CustomTypeName = keyof typeof customTypeDescriptors;
61
+ export declare const customTypeNames: Set<string>;
62
+ export declare const customModelTypeNames: Set<string>;
63
+ export declare const customInlineTypeNames: Set<string>;
64
+ export declare function resolveCustomTypeDependencies(names: Iterable<string>): Set<string>;
65
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/custom-types/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA4EpF,eAAO,MAAM,sBAAsB;;;;CAIzB,CAAC;AAEX,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;CAI1B,CAAC;AAEX,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;CAGxB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,qBAAqB,CAAC;AAEhE,eAAO,MAAM,eAAe,aAAsD,CAAC;AACnF,eAAO,MAAM,oBAAoB,aAEhC,CAAC;AACF,eAAO,MAAM,qBAAqB,aAEjC,CAAC;AAEF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAwBlF"}