@twin.org/ts-to-schema 0.0.1-next.9 → 0.0.2-next.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.
@@ -5,6 +5,7 @@ var node_url = require('node:url');
5
5
  var cliCore = require('@twin.org/cli-core');
6
6
  var promises = require('node:fs/promises');
7
7
  var core = require('@twin.org/core');
8
+ var toolsCore = require('@twin.org/tools-core');
8
9
  var tsJsonSchemaGenerator = require('ts-json-schema-generator');
9
10
 
10
11
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -76,15 +77,33 @@ async function tsToSchema(config, outputFolder, workingDirectory) {
76
77
  await promises.writeFile(path.join(workingDirectory, "tsconfig.json"), JSON.stringify({
77
78
  compilerOptions: {}
78
79
  }, undefined, "\t"));
79
- cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.ts-to-schema.progress.generatingSchemas"));
80
- const schemas = await generateSchemas(config.sources, config.types, workingDirectory);
81
80
  cliCore.CLIDisplay.break();
82
81
  cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.ts-to-schema.progress.writingSchemas"));
83
- for (const type of config.types) {
84
- if (core.Is.empty(schemas[type])) {
85
- throw new core.GeneralError("commands", "commands.ts-to-schema.schemaNotFound", { type });
82
+ for (const typeSource of config.types) {
83
+ const typeSourceParts = typeSource.split("/");
84
+ const type = core.StringHelper.pascalCase(typeSourceParts[typeSourceParts.length - 1].replace(/(\.d)?\.ts$/, ""), false);
85
+ let schemaObject;
86
+ if (core.Is.object(config.overrides?.[type])) {
87
+ cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.ts-to-schema.progress.overridingSchema"));
88
+ schemaObject = config.overrides?.[type];
86
89
  }
87
- let content = JSON.stringify(schemas[type], undefined, "\t");
90
+ else {
91
+ cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.ts-to-schema.progress.generatingSchema"));
92
+ const autoExpandTypes = config.autoExpandTypes ?? [];
93
+ const defaultExpandTypes = ["ObjectOrArray<.*>"];
94
+ for (const defaultType of defaultExpandTypes) {
95
+ if (!autoExpandTypes.includes(defaultType)) {
96
+ autoExpandTypes.push(defaultType);
97
+ }
98
+ }
99
+ const schemas = await generateSchemas(typeSource, type, autoExpandTypes, workingDirectory);
100
+ if (core.Is.empty(schemas[type])) {
101
+ throw new core.GeneralError("commands", "commands.ts-to-schema.schemaNotFound", { type });
102
+ }
103
+ schemaObject = schemas[type];
104
+ }
105
+ schemaObject = finaliseSchema(schemaObject, config.baseUrl, type);
106
+ let content = JSON.stringify(schemaObject, undefined, "\t");
88
107
  if (core.Is.objectValue(config.externalReferences)) {
89
108
  for (const external in config.externalReferences) {
90
109
  content = content.replace(new RegExp(`#/definitions/${external}`, "g"), config.externalReferences[external]);
@@ -96,128 +115,56 @@ async function tsToSchema(config, outputFolder, workingDirectory) {
96
115
  content = content.replace(/#\/definitions\/I?([A-Z].*)/g, `${config.baseUrl}$1`);
97
116
  const filename = path.join(outputFolder, `${core.StringHelper.stripPrefix(type)}.json`);
98
117
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.ts-to-schema.progress.writingSchema"), filename, 1);
99
- await promises.writeFile(filename, content);
118
+ await promises.writeFile(filename, `${content}\n`);
100
119
  }
101
120
  }
102
121
  /**
103
122
  * Generate schemas for the models.
104
123
  * @param modelDirWildcards The filenames for all the models.
105
124
  * @param types The types of the schema objects.
125
+ * @param autoExpandTypes The types to automatically expand.
106
126
  * @param outputWorkingDir The working directory.
107
127
  * @returns Nothing.
108
128
  * @internal
109
129
  */
110
- async function generateSchemas(modelDirWildcards, types, outputWorkingDir) {
130
+ async function generateSchemas(typeSource, type, autoExpandTypes, outputWorkingDir) {
111
131
  const allSchemas = {};
112
- const arraySingularTypes = [];
113
- for (const type of types) {
114
- if (type.endsWith("[]")) {
115
- const singularType = type.slice(0, -2);
116
- arraySingularTypes.push(singularType);
117
- if (!types.includes(singularType)) {
118
- types.push(singularType);
119
- }
120
- }
121
- }
122
- for (const files of modelDirWildcards) {
123
- cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.ts-to-schema.progress.models"), files.replace(/\\/g, "/"), 1);
124
- const generator = tsJsonSchemaGenerator.createGenerator({
125
- path: files.replace(/\\/g, "/"),
126
- type: "*",
127
- tsconfig: path.join(outputWorkingDir, "tsconfig.json"),
128
- skipTypeCheck: true,
129
- expose: "all"
130
- });
131
- const schema = generator.createSchema("*");
132
- if (schema.definitions) {
133
- for (const def in schema.definitions) {
134
- // Remove the partial markers
135
- let defSub = def.replace(/^Partial<(.*?)>/g, "$1");
136
- // Cleanup the generic markers
137
- defSub = defSub.replace(/</g, "%3C").replace(/>/g, "%3E");
138
- allSchemas[defSub] = schema.definitions[def];
139
- }
132
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.ts-to-schema.progress.models"), typeSource, 1);
133
+ const generator = tsJsonSchemaGenerator.createGenerator({
134
+ path: typeSource,
135
+ type,
136
+ tsconfig: path.join(outputWorkingDir, "tsconfig.json"),
137
+ skipTypeCheck: true,
138
+ expose: "all"
139
+ });
140
+ const schema = generator.createSchema("*");
141
+ if (schema.definitions) {
142
+ for (const def in schema.definitions) {
143
+ const defSub = toolsCore.JsonSchemaHelper.normaliseTypeName(def);
144
+ allSchemas[defSub] = schema.definitions[def];
140
145
  }
141
146
  }
142
147
  const referencedSchemas = {};
143
- extractTypes(allSchemas, types, referencedSchemas);
144
- for (const arraySingularType of arraySingularTypes) {
145
- referencedSchemas[`${arraySingularType}[]`] = {
146
- type: "array",
147
- items: {
148
- $ref: `#/components/schemas/${arraySingularType}`
149
- }
150
- };
151
- }
148
+ toolsCore.JsonSchemaHelper.extractTypes(allSchemas, [type, ...autoExpandTypes], referencedSchemas);
149
+ toolsCore.JsonSchemaHelper.expandTypes(referencedSchemas, autoExpandTypes);
152
150
  return referencedSchemas;
153
151
  }
154
152
  /**
155
- * Extract the required types from all the known schemas.
156
- * @param allSchemas All the known schemas.
157
- * @param requiredTypes The required types.
158
- * @param referencedSchemas The references schemas.
159
- * @internal
160
- */
161
- function extractTypes(allSchemas, requiredTypes, referencedSchemas) {
162
- for (const type of requiredTypes) {
163
- if (allSchemas[type] && !referencedSchemas[type]) {
164
- referencedSchemas[type] = allSchemas[type];
165
- extractTypesFromSchema(allSchemas, allSchemas[type], referencedSchemas);
166
- }
167
- }
168
- }
169
- /**
170
- * Extract type from properties definition.
171
- * @param allTypes All the known types.
172
- * @param schema The schema to extract from.
173
- * @param output The output types.
174
- * @internal
153
+ * Process the schema object to ensure it has the correct properties.
154
+ * @param schemaObject The schema object to process.
155
+ * @param baseUrl The base URL for the schema references.
156
+ * @param type The type of the schema object.
157
+ * @returns The finalised schema object.
175
158
  */
176
- function extractTypesFromSchema(allTypes, schema, output) {
177
- const additionalTypes = [];
178
- if (core.Is.stringValue(schema.$ref)) {
179
- additionalTypes.push(schema.$ref.replace("#/definitions/", "").replace(/^Partial%3C(.*?)%3E/g, "$1"));
180
- }
181
- else if (core.Is.object(schema.items)) {
182
- if (core.Is.arrayValue(schema.items)) {
183
- for (const itemSchema of schema.items) {
184
- extractTypesFromSchema(allTypes, itemSchema, output);
185
- }
186
- }
187
- else {
188
- extractTypesFromSchema(allTypes, schema.items, output);
189
- }
190
- }
191
- else if (core.Is.object(schema.properties) || core.Is.object(schema.additionalProperties)) {
192
- if (core.Is.object(schema.properties)) {
193
- for (const prop in schema.properties) {
194
- const p = schema.properties[prop];
195
- if (core.Is.object(p)) {
196
- extractTypesFromSchema(allTypes, p, output);
197
- }
198
- }
199
- }
200
- if (core.Is.object(schema.additionalProperties)) {
201
- extractTypesFromSchema(allTypes, schema.additionalProperties, output);
202
- }
203
- }
204
- else if (core.Is.arrayValue(schema.anyOf)) {
205
- for (const prop of schema.anyOf) {
206
- if (core.Is.object(prop)) {
207
- extractTypesFromSchema(allTypes, prop, output);
208
- }
209
- }
210
- }
211
- else if (core.Is.arrayValue(schema.oneOf)) {
212
- for (const prop of schema.oneOf) {
213
- if (core.Is.object(prop)) {
214
- extractTypesFromSchema(allTypes, prop, output);
215
- }
216
- }
217
- }
218
- if (additionalTypes.length > 0) {
219
- extractTypes(allTypes, additionalTypes, output);
220
- }
159
+ function finaliseSchema(schemaObject, baseUrl, type) {
160
+ toolsCore.JsonSchemaHelper.processArrays(schemaObject);
161
+ const { description, ...rest } = schemaObject;
162
+ return {
163
+ $schema: toolsCore.JsonSchemaHelper.SCHEMA_VERSION,
164
+ $id: `${baseUrl}${core.StringHelper.stripPrefix(type)}`,
165
+ description,
166
+ ...rest
167
+ };
221
168
  }
222
169
 
223
170
  // Copyright 2024 IOTA Stiftung.
@@ -238,7 +185,7 @@ class CLI extends cliCore.CLIBase {
238
185
  return this.execute({
239
186
  title: "TWIN TypeScript To Schema",
240
187
  appName: "ts-to-schema",
241
- version: "0.0.1-next.9",
188
+ version: "0.0.2-next.1", // x-release-please-version
242
189
  icon: "⚙️ ",
243
190
  supportsEnvFiles: false,
244
191
  overrideOutputWidth: options?.overrideOutputWidth
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url';
3
3
  import { CLIDisplay, CLIUtils, CLIBase } from '@twin.org/cli-core';
4
4
  import { mkdir, rm, writeFile } from 'node:fs/promises';
5
5
  import { I18n, GeneralError, Is, StringHelper } from '@twin.org/core';
6
+ import { JsonSchemaHelper } from '@twin.org/tools-core';
6
7
  import { createGenerator } from 'ts-json-schema-generator';
7
8
 
8
9
  // Copyright 2024 IOTA Stiftung.
@@ -73,15 +74,33 @@ async function tsToSchema(config, outputFolder, workingDirectory) {
73
74
  await writeFile(path.join(workingDirectory, "tsconfig.json"), JSON.stringify({
74
75
  compilerOptions: {}
75
76
  }, undefined, "\t"));
76
- CLIDisplay.task(I18n.formatMessage("commands.ts-to-schema.progress.generatingSchemas"));
77
- const schemas = await generateSchemas(config.sources, config.types, workingDirectory);
78
77
  CLIDisplay.break();
79
78
  CLIDisplay.task(I18n.formatMessage("commands.ts-to-schema.progress.writingSchemas"));
80
- for (const type of config.types) {
81
- if (Is.empty(schemas[type])) {
82
- throw new GeneralError("commands", "commands.ts-to-schema.schemaNotFound", { type });
79
+ for (const typeSource of config.types) {
80
+ const typeSourceParts = typeSource.split("/");
81
+ const type = StringHelper.pascalCase(typeSourceParts[typeSourceParts.length - 1].replace(/(\.d)?\.ts$/, ""), false);
82
+ let schemaObject;
83
+ if (Is.object(config.overrides?.[type])) {
84
+ CLIDisplay.task(I18n.formatMessage("commands.ts-to-schema.progress.overridingSchema"));
85
+ schemaObject = config.overrides?.[type];
83
86
  }
84
- let content = JSON.stringify(schemas[type], undefined, "\t");
87
+ else {
88
+ CLIDisplay.task(I18n.formatMessage("commands.ts-to-schema.progress.generatingSchema"));
89
+ const autoExpandTypes = config.autoExpandTypes ?? [];
90
+ const defaultExpandTypes = ["ObjectOrArray<.*>"];
91
+ for (const defaultType of defaultExpandTypes) {
92
+ if (!autoExpandTypes.includes(defaultType)) {
93
+ autoExpandTypes.push(defaultType);
94
+ }
95
+ }
96
+ const schemas = await generateSchemas(typeSource, type, autoExpandTypes, workingDirectory);
97
+ if (Is.empty(schemas[type])) {
98
+ throw new GeneralError("commands", "commands.ts-to-schema.schemaNotFound", { type });
99
+ }
100
+ schemaObject = schemas[type];
101
+ }
102
+ schemaObject = finaliseSchema(schemaObject, config.baseUrl, type);
103
+ let content = JSON.stringify(schemaObject, undefined, "\t");
85
104
  if (Is.objectValue(config.externalReferences)) {
86
105
  for (const external in config.externalReferences) {
87
106
  content = content.replace(new RegExp(`#/definitions/${external}`, "g"), config.externalReferences[external]);
@@ -93,128 +112,56 @@ async function tsToSchema(config, outputFolder, workingDirectory) {
93
112
  content = content.replace(/#\/definitions\/I?([A-Z].*)/g, `${config.baseUrl}$1`);
94
113
  const filename = path.join(outputFolder, `${StringHelper.stripPrefix(type)}.json`);
95
114
  CLIDisplay.value(I18n.formatMessage("commands.ts-to-schema.progress.writingSchema"), filename, 1);
96
- await writeFile(filename, content);
115
+ await writeFile(filename, `${content}\n`);
97
116
  }
98
117
  }
99
118
  /**
100
119
  * Generate schemas for the models.
101
120
  * @param modelDirWildcards The filenames for all the models.
102
121
  * @param types The types of the schema objects.
122
+ * @param autoExpandTypes The types to automatically expand.
103
123
  * @param outputWorkingDir The working directory.
104
124
  * @returns Nothing.
105
125
  * @internal
106
126
  */
107
- async function generateSchemas(modelDirWildcards, types, outputWorkingDir) {
127
+ async function generateSchemas(typeSource, type, autoExpandTypes, outputWorkingDir) {
108
128
  const allSchemas = {};
109
- const arraySingularTypes = [];
110
- for (const type of types) {
111
- if (type.endsWith("[]")) {
112
- const singularType = type.slice(0, -2);
113
- arraySingularTypes.push(singularType);
114
- if (!types.includes(singularType)) {
115
- types.push(singularType);
116
- }
117
- }
118
- }
119
- for (const files of modelDirWildcards) {
120
- CLIDisplay.value(I18n.formatMessage("commands.ts-to-schema.progress.models"), files.replace(/\\/g, "/"), 1);
121
- const generator = createGenerator({
122
- path: files.replace(/\\/g, "/"),
123
- type: "*",
124
- tsconfig: path.join(outputWorkingDir, "tsconfig.json"),
125
- skipTypeCheck: true,
126
- expose: "all"
127
- });
128
- const schema = generator.createSchema("*");
129
- if (schema.definitions) {
130
- for (const def in schema.definitions) {
131
- // Remove the partial markers
132
- let defSub = def.replace(/^Partial<(.*?)>/g, "$1");
133
- // Cleanup the generic markers
134
- defSub = defSub.replace(/</g, "%3C").replace(/>/g, "%3E");
135
- allSchemas[defSub] = schema.definitions[def];
136
- }
129
+ CLIDisplay.value(I18n.formatMessage("commands.ts-to-schema.progress.models"), typeSource, 1);
130
+ const generator = createGenerator({
131
+ path: typeSource,
132
+ type,
133
+ tsconfig: path.join(outputWorkingDir, "tsconfig.json"),
134
+ skipTypeCheck: true,
135
+ expose: "all"
136
+ });
137
+ const schema = generator.createSchema("*");
138
+ if (schema.definitions) {
139
+ for (const def in schema.definitions) {
140
+ const defSub = JsonSchemaHelper.normaliseTypeName(def);
141
+ allSchemas[defSub] = schema.definitions[def];
137
142
  }
138
143
  }
139
144
  const referencedSchemas = {};
140
- extractTypes(allSchemas, types, referencedSchemas);
141
- for (const arraySingularType of arraySingularTypes) {
142
- referencedSchemas[`${arraySingularType}[]`] = {
143
- type: "array",
144
- items: {
145
- $ref: `#/components/schemas/${arraySingularType}`
146
- }
147
- };
148
- }
145
+ JsonSchemaHelper.extractTypes(allSchemas, [type, ...autoExpandTypes], referencedSchemas);
146
+ JsonSchemaHelper.expandTypes(referencedSchemas, autoExpandTypes);
149
147
  return referencedSchemas;
150
148
  }
151
149
  /**
152
- * Extract the required types from all the known schemas.
153
- * @param allSchemas All the known schemas.
154
- * @param requiredTypes The required types.
155
- * @param referencedSchemas The references schemas.
156
- * @internal
157
- */
158
- function extractTypes(allSchemas, requiredTypes, referencedSchemas) {
159
- for (const type of requiredTypes) {
160
- if (allSchemas[type] && !referencedSchemas[type]) {
161
- referencedSchemas[type] = allSchemas[type];
162
- extractTypesFromSchema(allSchemas, allSchemas[type], referencedSchemas);
163
- }
164
- }
165
- }
166
- /**
167
- * Extract type from properties definition.
168
- * @param allTypes All the known types.
169
- * @param schema The schema to extract from.
170
- * @param output The output types.
171
- * @internal
150
+ * Process the schema object to ensure it has the correct properties.
151
+ * @param schemaObject The schema object to process.
152
+ * @param baseUrl The base URL for the schema references.
153
+ * @param type The type of the schema object.
154
+ * @returns The finalised schema object.
172
155
  */
173
- function extractTypesFromSchema(allTypes, schema, output) {
174
- const additionalTypes = [];
175
- if (Is.stringValue(schema.$ref)) {
176
- additionalTypes.push(schema.$ref.replace("#/definitions/", "").replace(/^Partial%3C(.*?)%3E/g, "$1"));
177
- }
178
- else if (Is.object(schema.items)) {
179
- if (Is.arrayValue(schema.items)) {
180
- for (const itemSchema of schema.items) {
181
- extractTypesFromSchema(allTypes, itemSchema, output);
182
- }
183
- }
184
- else {
185
- extractTypesFromSchema(allTypes, schema.items, output);
186
- }
187
- }
188
- else if (Is.object(schema.properties) || Is.object(schema.additionalProperties)) {
189
- if (Is.object(schema.properties)) {
190
- for (const prop in schema.properties) {
191
- const p = schema.properties[prop];
192
- if (Is.object(p)) {
193
- extractTypesFromSchema(allTypes, p, output);
194
- }
195
- }
196
- }
197
- if (Is.object(schema.additionalProperties)) {
198
- extractTypesFromSchema(allTypes, schema.additionalProperties, output);
199
- }
200
- }
201
- else if (Is.arrayValue(schema.anyOf)) {
202
- for (const prop of schema.anyOf) {
203
- if (Is.object(prop)) {
204
- extractTypesFromSchema(allTypes, prop, output);
205
- }
206
- }
207
- }
208
- else if (Is.arrayValue(schema.oneOf)) {
209
- for (const prop of schema.oneOf) {
210
- if (Is.object(prop)) {
211
- extractTypesFromSchema(allTypes, prop, output);
212
- }
213
- }
214
- }
215
- if (additionalTypes.length > 0) {
216
- extractTypes(allTypes, additionalTypes, output);
217
- }
156
+ function finaliseSchema(schemaObject, baseUrl, type) {
157
+ JsonSchemaHelper.processArrays(schemaObject);
158
+ const { description, ...rest } = schemaObject;
159
+ return {
160
+ $schema: JsonSchemaHelper.SCHEMA_VERSION,
161
+ $id: `${baseUrl}${StringHelper.stripPrefix(type)}`,
162
+ description,
163
+ ...rest
164
+ };
218
165
  }
219
166
 
220
167
  // Copyright 2024 IOTA Stiftung.
@@ -235,7 +182,7 @@ class CLI extends CLIBase {
235
182
  return this.execute({
236
183
  title: "TWIN TypeScript To Schema",
237
184
  appName: "ts-to-schema",
238
- version: "0.0.1-next.9",
185
+ version: "0.0.2-next.1", // x-release-please-version
239
186
  icon: "⚙️ ",
240
187
  supportsEnvFiles: false,
241
188
  overrideOutputWidth: options?.overrideOutputWidth