@twin.org/ts-to-schema 0.0.1-next.9 → 0.0.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.
@@ -10,6 +10,7 @@ var tsJsonSchemaGenerator = require('ts-json-schema-generator');
10
10
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
11
11
  // Copyright 2024 IOTA Stiftung.
12
12
  // SPDX-License-Identifier: Apache-2.0.
13
+ const SCHEMA_VERSION = "https://json-schema.org/draft/2020-12/schema";
13
14
  /**
14
15
  * Build the root command to be consumed by the CLI.
15
16
  * @param program The command to build on.
@@ -76,15 +77,26 @@ 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 schemas = await generateSchemas(typeSource, type, workingDirectory);
93
+ if (core.Is.empty(schemas[type])) {
94
+ throw new core.GeneralError("commands", "commands.ts-to-schema.schemaNotFound", { type });
95
+ }
96
+ schemaObject = schemas[type];
97
+ }
98
+ schemaObject = finaliseSchema(schemaObject, config.baseUrl, type);
99
+ let content = JSON.stringify(schemaObject, undefined, "\t");
88
100
  if (core.Is.objectValue(config.externalReferences)) {
89
101
  for (const external in config.externalReferences) {
90
102
  content = content.replace(new RegExp(`#/definitions/${external}`, "g"), config.externalReferences[external]);
@@ -96,7 +108,7 @@ async function tsToSchema(config, outputFolder, workingDirectory) {
96
108
  content = content.replace(/#\/definitions\/I?([A-Z].*)/g, `${config.baseUrl}$1`);
97
109
  const filename = path.join(outputFolder, `${core.StringHelper.stripPrefix(type)}.json`);
98
110
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.ts-to-schema.progress.writingSchema"), filename, 1);
99
- await promises.writeFile(filename, content);
111
+ await promises.writeFile(filename, `${content}\n`);
100
112
  }
101
113
  }
102
114
  /**
@@ -107,48 +119,25 @@ async function tsToSchema(config, outputFolder, workingDirectory) {
107
119
  * @returns Nothing.
108
120
  * @internal
109
121
  */
110
- async function generateSchemas(modelDirWildcards, types, outputWorkingDir) {
122
+ async function generateSchemas(typeSource, type, outputWorkingDir) {
111
123
  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
- }
124
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.ts-to-schema.progress.models"), typeSource, 1);
125
+ const generator = tsJsonSchemaGenerator.createGenerator({
126
+ path: typeSource,
127
+ type,
128
+ tsconfig: path.join(outputWorkingDir, "tsconfig.json"),
129
+ skipTypeCheck: true,
130
+ expose: "all"
131
+ });
132
+ const schema = generator.createSchema("*");
133
+ if (schema.definitions) {
134
+ for (const def in schema.definitions) {
135
+ const defSub = normaliseTypeName(def);
136
+ allSchemas[defSub] = schema.definitions[def];
140
137
  }
141
138
  }
142
139
  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
- }
140
+ extractTypes(allSchemas, [type], referencedSchemas);
152
141
  return referencedSchemas;
153
142
  }
154
143
  /**
@@ -219,6 +208,96 @@ function extractTypesFromSchema(allTypes, schema, output) {
219
208
  extractTypes(allTypes, additionalTypes, output);
220
209
  }
221
210
  }
211
+ /**
212
+ * Process the schema object to ensure it has the correct properties.
213
+ * @param schemaObject The schema object to process.
214
+ * @param baseUrl The base URL for the schema references.
215
+ * @param type The type of the schema object.
216
+ * @returns The finalised schema object.
217
+ */
218
+ function finaliseSchema(schemaObject, baseUrl, type) {
219
+ processArrays(schemaObject);
220
+ const { description, ...rest } = schemaObject;
221
+ return {
222
+ $schema: SCHEMA_VERSION,
223
+ $id: `${baseUrl}${core.StringHelper.stripPrefix(type)}`,
224
+ description,
225
+ ...rest
226
+ };
227
+ }
228
+ /**
229
+ * Process arrays in the schema object.
230
+ * @param schemaObject The schema object to process.
231
+ */
232
+ function processArrays(schemaObject) {
233
+ if (core.Is.object(schemaObject)) {
234
+ // latest specs have singular items in `items` property
235
+ // and multiple items in prefixItems, so update the schema accordingly
236
+ // https://www.learnjsonschema.com/2020-12/applicator/items/
237
+ // https://www.learnjsonschema.com/2020-12/applicator/prefixitems/
238
+ const schemaItems = schemaObject.items;
239
+ if (core.Is.array(schemaItems) || core.Is.object(schemaItems)) {
240
+ schemaObject.prefixItems = core.ArrayHelper.fromObjectOrArray(schemaItems);
241
+ schemaObject.items = false;
242
+ }
243
+ const additionalItems = schemaObject.additionalItems;
244
+ if (core.Is.array(additionalItems) || core.Is.object(additionalItems)) {
245
+ schemaObject.items = core.ArrayHelper.fromObjectOrArray(additionalItems)[0];
246
+ delete schemaObject.additionalItems;
247
+ }
248
+ processSchemaDictionary(schemaObject.properties);
249
+ processArrays(schemaObject.additionalProperties);
250
+ processSchemaArray(schemaObject.allOf);
251
+ processSchemaArray(schemaObject.anyOf);
252
+ processSchemaArray(schemaObject.oneOf);
253
+ }
254
+ }
255
+ /**
256
+ * Process arrays in the schema object.
257
+ * @param schemaDictionary The schema object to process.
258
+ */
259
+ function processSchemaDictionary(schemaDictionary) {
260
+ if (core.Is.object(schemaDictionary)) {
261
+ for (const item of Object.values(schemaDictionary)) {
262
+ if (core.Is.object(item)) {
263
+ processArrays(item);
264
+ }
265
+ }
266
+ }
267
+ }
268
+ /**
269
+ * Process arrays in the schema object.
270
+ * @param schemaArray The schema object to process.
271
+ */
272
+ function processSchemaArray(schemaArray) {
273
+ if (core.Is.arrayValue(schemaArray)) {
274
+ for (const item of schemaArray) {
275
+ if (core.Is.object(item)) {
276
+ processArrays(item);
277
+ }
278
+ }
279
+ }
280
+ }
281
+ /**
282
+ * Cleanup TypeScript markers from the type name.
283
+ * @param typeName The definition string to clean up.
284
+ * @returns The cleaned up definition string.
285
+ */
286
+ function normaliseTypeName(typeName) {
287
+ // Remove the partial markers
288
+ let sTypeName = typeName.replace(/^Partial<(.*?)>/g, "$1");
289
+ sTypeName = sTypeName.replace(/Partial%3CI(.*?)%3E/g, "$1");
290
+ // Remove the omit markers
291
+ sTypeName = sTypeName.replace(/^Omit<(.*?),.*>/g, "$1");
292
+ sTypeName = sTypeName.replace(/Omit%3CI(.*?)%2C.*%3E/g, "$1");
293
+ // Remove the pick markers
294
+ sTypeName = sTypeName.replace(/^Pick<(.*?),.*>/g, "$1");
295
+ sTypeName = sTypeName.replace(/Pick%3CI(.*?)%2C.*%3E/g, "$1");
296
+ // Cleanup the generic markers
297
+ sTypeName = sTypeName.replace(/</g, "%3C").replace(/>/g, "%3E");
298
+ sTypeName = sTypeName.replace(/%3Cunknown%3E/g, "");
299
+ return sTypeName;
300
+ }
222
301
 
223
302
  // Copyright 2024 IOTA Stiftung.
224
303
  // SPDX-License-Identifier: Apache-2.0.
@@ -238,7 +317,7 @@ class CLI extends cliCore.CLIBase {
238
317
  return this.execute({
239
318
  title: "TWIN TypeScript To Schema",
240
319
  appName: "ts-to-schema",
241
- version: "0.0.1-next.9",
320
+ version: "0.0.1", // x-release-please-version
242
321
  icon: "⚙️ ",
243
322
  supportsEnvFiles: false,
244
323
  overrideOutputWidth: options?.overrideOutputWidth
@@ -2,11 +2,12 @@ import path from 'node:path';
2
2
  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
- import { I18n, GeneralError, Is, StringHelper } from '@twin.org/core';
5
+ import { I18n, GeneralError, Is, StringHelper, ArrayHelper } from '@twin.org/core';
6
6
  import { createGenerator } from 'ts-json-schema-generator';
7
7
 
8
8
  // Copyright 2024 IOTA Stiftung.
9
9
  // SPDX-License-Identifier: Apache-2.0.
10
+ const SCHEMA_VERSION = "https://json-schema.org/draft/2020-12/schema";
10
11
  /**
11
12
  * Build the root command to be consumed by the CLI.
12
13
  * @param program The command to build on.
@@ -73,15 +74,26 @@ 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 schemas = await generateSchemas(typeSource, type, workingDirectory);
90
+ if (Is.empty(schemas[type])) {
91
+ throw new GeneralError("commands", "commands.ts-to-schema.schemaNotFound", { type });
92
+ }
93
+ schemaObject = schemas[type];
94
+ }
95
+ schemaObject = finaliseSchema(schemaObject, config.baseUrl, type);
96
+ let content = JSON.stringify(schemaObject, undefined, "\t");
85
97
  if (Is.objectValue(config.externalReferences)) {
86
98
  for (const external in config.externalReferences) {
87
99
  content = content.replace(new RegExp(`#/definitions/${external}`, "g"), config.externalReferences[external]);
@@ -93,7 +105,7 @@ async function tsToSchema(config, outputFolder, workingDirectory) {
93
105
  content = content.replace(/#\/definitions\/I?([A-Z].*)/g, `${config.baseUrl}$1`);
94
106
  const filename = path.join(outputFolder, `${StringHelper.stripPrefix(type)}.json`);
95
107
  CLIDisplay.value(I18n.formatMessage("commands.ts-to-schema.progress.writingSchema"), filename, 1);
96
- await writeFile(filename, content);
108
+ await writeFile(filename, `${content}\n`);
97
109
  }
98
110
  }
99
111
  /**
@@ -104,48 +116,25 @@ async function tsToSchema(config, outputFolder, workingDirectory) {
104
116
  * @returns Nothing.
105
117
  * @internal
106
118
  */
107
- async function generateSchemas(modelDirWildcards, types, outputWorkingDir) {
119
+ async function generateSchemas(typeSource, type, outputWorkingDir) {
108
120
  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
- }
121
+ CLIDisplay.value(I18n.formatMessage("commands.ts-to-schema.progress.models"), typeSource, 1);
122
+ const generator = createGenerator({
123
+ path: typeSource,
124
+ type,
125
+ tsconfig: path.join(outputWorkingDir, "tsconfig.json"),
126
+ skipTypeCheck: true,
127
+ expose: "all"
128
+ });
129
+ const schema = generator.createSchema("*");
130
+ if (schema.definitions) {
131
+ for (const def in schema.definitions) {
132
+ const defSub = normaliseTypeName(def);
133
+ allSchemas[defSub] = schema.definitions[def];
137
134
  }
138
135
  }
139
136
  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
- }
137
+ extractTypes(allSchemas, [type], referencedSchemas);
149
138
  return referencedSchemas;
150
139
  }
151
140
  /**
@@ -216,6 +205,96 @@ function extractTypesFromSchema(allTypes, schema, output) {
216
205
  extractTypes(allTypes, additionalTypes, output);
217
206
  }
218
207
  }
208
+ /**
209
+ * Process the schema object to ensure it has the correct properties.
210
+ * @param schemaObject The schema object to process.
211
+ * @param baseUrl The base URL for the schema references.
212
+ * @param type The type of the schema object.
213
+ * @returns The finalised schema object.
214
+ */
215
+ function finaliseSchema(schemaObject, baseUrl, type) {
216
+ processArrays(schemaObject);
217
+ const { description, ...rest } = schemaObject;
218
+ return {
219
+ $schema: SCHEMA_VERSION,
220
+ $id: `${baseUrl}${StringHelper.stripPrefix(type)}`,
221
+ description,
222
+ ...rest
223
+ };
224
+ }
225
+ /**
226
+ * Process arrays in the schema object.
227
+ * @param schemaObject The schema object to process.
228
+ */
229
+ function processArrays(schemaObject) {
230
+ if (Is.object(schemaObject)) {
231
+ // latest specs have singular items in `items` property
232
+ // and multiple items in prefixItems, so update the schema accordingly
233
+ // https://www.learnjsonschema.com/2020-12/applicator/items/
234
+ // https://www.learnjsonschema.com/2020-12/applicator/prefixitems/
235
+ const schemaItems = schemaObject.items;
236
+ if (Is.array(schemaItems) || Is.object(schemaItems)) {
237
+ schemaObject.prefixItems = ArrayHelper.fromObjectOrArray(schemaItems);
238
+ schemaObject.items = false;
239
+ }
240
+ const additionalItems = schemaObject.additionalItems;
241
+ if (Is.array(additionalItems) || Is.object(additionalItems)) {
242
+ schemaObject.items = ArrayHelper.fromObjectOrArray(additionalItems)[0];
243
+ delete schemaObject.additionalItems;
244
+ }
245
+ processSchemaDictionary(schemaObject.properties);
246
+ processArrays(schemaObject.additionalProperties);
247
+ processSchemaArray(schemaObject.allOf);
248
+ processSchemaArray(schemaObject.anyOf);
249
+ processSchemaArray(schemaObject.oneOf);
250
+ }
251
+ }
252
+ /**
253
+ * Process arrays in the schema object.
254
+ * @param schemaDictionary The schema object to process.
255
+ */
256
+ function processSchemaDictionary(schemaDictionary) {
257
+ if (Is.object(schemaDictionary)) {
258
+ for (const item of Object.values(schemaDictionary)) {
259
+ if (Is.object(item)) {
260
+ processArrays(item);
261
+ }
262
+ }
263
+ }
264
+ }
265
+ /**
266
+ * Process arrays in the schema object.
267
+ * @param schemaArray The schema object to process.
268
+ */
269
+ function processSchemaArray(schemaArray) {
270
+ if (Is.arrayValue(schemaArray)) {
271
+ for (const item of schemaArray) {
272
+ if (Is.object(item)) {
273
+ processArrays(item);
274
+ }
275
+ }
276
+ }
277
+ }
278
+ /**
279
+ * Cleanup TypeScript markers from the type name.
280
+ * @param typeName The definition string to clean up.
281
+ * @returns The cleaned up definition string.
282
+ */
283
+ function normaliseTypeName(typeName) {
284
+ // Remove the partial markers
285
+ let sTypeName = typeName.replace(/^Partial<(.*?)>/g, "$1");
286
+ sTypeName = sTypeName.replace(/Partial%3CI(.*?)%3E/g, "$1");
287
+ // Remove the omit markers
288
+ sTypeName = sTypeName.replace(/^Omit<(.*?),.*>/g, "$1");
289
+ sTypeName = sTypeName.replace(/Omit%3CI(.*?)%2C.*%3E/g, "$1");
290
+ // Remove the pick markers
291
+ sTypeName = sTypeName.replace(/^Pick<(.*?),.*>/g, "$1");
292
+ sTypeName = sTypeName.replace(/Pick%3CI(.*?)%2C.*%3E/g, "$1");
293
+ // Cleanup the generic markers
294
+ sTypeName = sTypeName.replace(/</g, "%3C").replace(/>/g, "%3E");
295
+ sTypeName = sTypeName.replace(/%3Cunknown%3E/g, "");
296
+ return sTypeName;
297
+ }
219
298
 
220
299
  // Copyright 2024 IOTA Stiftung.
221
300
  // SPDX-License-Identifier: Apache-2.0.
@@ -235,7 +314,7 @@ class CLI extends CLIBase {
235
314
  return this.execute({
236
315
  title: "TWIN TypeScript To Schema",
237
316
  appName: "ts-to-schema",
238
- version: "0.0.1-next.9",
317
+ version: "0.0.1", // x-release-please-version
239
318
  icon: "⚙️ ",
240
319
  supportsEnvFiles: false,
241
320
  overrideOutputWidth: options?.overrideOutputWidth