Skip to content

Commit 0809466

Browse files
committed
feat: generate type definitions from the json schema
1 parent f15c5a2 commit 0809466

File tree

3 files changed

+139
-5
lines changed

3 files changed

+139
-5
lines changed

ā€Žlib/build.jsā€Ž

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { fileURLToPath } from 'node:url';
12
import { styleText } from 'node:util';
23
import fs from 'fs';
34
import jsonschema from 'jsonschema';
@@ -6,6 +7,7 @@ import shell from 'shelljs';
67
import YAML from 'js-yaml';
78
import marky from 'marky';
89
import { createRequire } from 'module';
10+
import { compile, toSafeIdentifier } from 'json-schema-to-typescript-lite';
911

1012
import fetchTranslations from './translations.js';
1113

@@ -186,7 +188,8 @@ function processData(options, type) {
186188
minifyJSON(distDir + '/preset_defaults.json', distDir + '/preset_defaults.min.json'),
187189
minifyJSON(distDir + '/deprecated.json', distDir + '/deprecated.min.json'),
188190
minifyJSON(distDir + '/discarded.json', distDir + '/discarded.min.json'),
189-
minifyJSON(distDir + '/translations/' + sourceLocale + '.json', distDir + '/translations/' + sourceLocale + '.min.json')
191+
minifyJSON(distDir + '/translations/' + sourceLocale + '.json', distDir + '/translations/' + sourceLocale + '.min.json'),
192+
generateTypeDefs(distDir),
190193
];
191194

192195
if (doFetchTranslations) {
@@ -746,6 +749,91 @@ function generateIconsList(presets, fields, categories) {
746749
return Object.keys(icons).sort();
747750
}
748751

752+
/** @param {string} string */
753+
const toPascalCase = string => string.replace(/(_.|^.)/g, match => match.at(-1).toUpperCase());
754+
755+
/** @param {string} string */
756+
const createTypeIdentifier = (string) => toPascalCase(toSafeIdentifier(string));
757+
758+
759+
/** @param {string} distDir */
760+
async function generateTypeDefs(distDir) {
761+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
762+
const inputFolder = path.join(__dirname, '../schemas');
763+
764+
/**
765+
* Some generated files use plural names because they
766+
* export an object, e.g. `Fields = Record<string, Field>`
767+
* @type {Record<string, string>}
768+
*/
769+
const KEY_MAP = {
770+
field: 'fields',
771+
preset: 'presets',
772+
preset_category: 'preset_categories',
773+
};
774+
const fileNames = fs.globSync(path.join(inputFolder, '/**/*.json'));
775+
776+
/** @param {string} fileName */
777+
async function processFile(fileName) {
778+
const key = path.parse(fileName).name;
779+
const pluralKey = KEY_MAP[key];
780+
781+
const mainExport = createTypeIdentifier(pluralKey || key);
782+
783+
const output = [''];
784+
785+
if (pluralKey) {
786+
output.push(
787+
`export interface ${createTypeIdentifier(pluralKey)} {`,
788+
` [id: string]: ${createTypeIdentifier(key)}`,
789+
'}'
790+
);
791+
}
792+
793+
output.push(
794+
`declare const json: ${mainExport};`,
795+
'export default json;'
796+
);
797+
798+
const fileContent = JSON.parse(await fs.promises.readFile(fileName, 'utf8'));
799+
if (key === 'field') delete fileContent.anyOf;
800+
801+
const tsFile = await compile(fileContent, mainExport, {
802+
additionalProperties: false,
803+
ignoreMinAndMaxItems: true,
804+
cwd: path.join(__dirname, '../schemas'),
805+
806+
// ensure that the default export uses a consistent name
807+
customName: (schema, fallback) =>
808+
schema.$schema && schema.$id
809+
? createTypeIdentifier(path.parse(schema.$id).name)
810+
: (schema.$id || schema.title || fallback || ''),
811+
});
812+
813+
814+
output.unshift(tsFile);
815+
output.push('');
816+
await fs.promises.writeFile(
817+
path.join(distDir, `${pluralKey || key}.d.json.ts`),
818+
output.join('\n'),
819+
);
820+
}
821+
await Promise.all(fileNames.map(processFile));
822+
823+
// finally, create the index file which re-exports everything
824+
// as named types.
825+
const indexFile = fileNames
826+
.map((fileName) => {
827+
const key = path.parse(fileName).name;
828+
return `export type * from './${KEY_MAP[key] || key}.d.json.ts';`;
829+
})
830+
.join('\n');
831+
832+
await fs.promises.writeFile(
833+
path.join(distDir, 'index.d.ts'),
834+
indexFile + '\n',
835+
);
836+
}
749837

750838
function validateCategoryPresets(categories, presets) {
751839
Object.keys(categories).forEach(id => {

ā€Žpackage-lock.jsonā€Ž

Lines changed: 49 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ā€Žpackage.jsonā€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"dependencies": {
1515
"@transifex/api": "^7.1.0",
1616
"js-yaml": "^4.0.0",
17+
"json-schema-to-typescript-lite": "^15.0.0",
1718
"jsonschema": "^1.1.0",
1819
"marky": "^1.2.4",
1920
"node-fetch": "^3.2.10",

0 commit comments

Comments
Ā (0)
⚔