Skip to content

Commit 953f464

Browse files
committed
feat: generate type definitions from the json schema
1 parent 17e6bac commit 953f464

File tree

4 files changed

+135
-7
lines changed

4 files changed

+135
-7
lines changed

ā€Žeslint.config.mjsā€Ž

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export default [
3030
'indent': ['off', 4],
3131
'keyword-spacing': 'error',
3232
'linebreak-style': ['error', 'unix'],
33-
'no-await-in-loop': 'error',
3433
'no-caller': 'error',
3534
'no-catch-shadow': 'error',
3635
'no-console': 'warn',

ā€Žlib/build.jsā€Ž

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { fileURLToPath } from 'node:url';
12
import chalk from 'chalk';
23
import fs from 'fs';
34
import { globSync } from 'glob';
@@ -7,6 +8,7 @@ import shell from 'shelljs';
78
import YAML from 'js-yaml';
89
import marky from 'marky';
910
import { createRequire } from 'module';
11+
import { compile, toSafeIdentifier } from 'json-schema-to-typescript-lite';
1012

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

@@ -187,7 +189,8 @@ function processData(options, type) {
187189
minifyJSON(distDir + '/preset_defaults.json', distDir + '/preset_defaults.min.json'),
188190
minifyJSON(distDir + '/deprecated.json', distDir + '/deprecated.min.json'),
189191
minifyJSON(distDir + '/discarded.json', distDir + '/discarded.min.json'),
190-
minifyJSON(distDir + '/translations/' + sourceLocale + '.json', distDir + '/translations/' + sourceLocale + '.min.json')
192+
minifyJSON(distDir + '/translations/' + sourceLocale + '.json', distDir + '/translations/' + sourceLocale + '.min.json'),
193+
generateTypeDefs(distDir),
191194
];
192195

193196
if (doFetchTranslations) {
@@ -693,6 +696,87 @@ function generateIconsList(presets, fields, categories) {
693696
return Object.keys(icons).sort();
694697
}
695698

699+
/** @param {string} string */
700+
const toPascalCase = string => string.replace(/(_.|^.)/g, match => match.at(-1).toUpperCase());
701+
702+
/** @param {string} string */
703+
const createTypeIdentifier = (string) => toPascalCase(toSafeIdentifier(string));
704+
705+
706+
/** @param {string} distDir */
707+
async function generateTypeDefs(distDir) {
708+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
709+
const inputFolder = path.join(__dirname, '../schemas');
710+
711+
/**
712+
* Some generated files use plural names because they
713+
* export an object, e.g. `Fields = Record<string, Field>`
714+
*/
715+
const KEY_MAP = {
716+
field: 'fields',
717+
preset: 'presets',
718+
preset_category: 'preset_categories',
719+
};
720+
const fileNames = globSync(path.join(inputFolder, '/**/*.json'), { posix: true });
721+
722+
for (const fileName of fileNames) {
723+
const key = path.parse(fileName).name;
724+
const pluralKey = KEY_MAP[key];
725+
726+
const mainExport = createTypeIdentifier(pluralKey || key);
727+
728+
const output = [''];
729+
730+
if (pluralKey) {
731+
output.push(
732+
`export interface ${createTypeIdentifier(pluralKey)} {`,
733+
` [id: string]: ${createTypeIdentifier(key)}`,
734+
'}'
735+
);
736+
}
737+
738+
output.push(
739+
`declare const json: ${mainExport};`,
740+
'export default json;'
741+
);
742+
743+
const fileContent = JSON.parse(await fs.promises.readFile(fileName, 'utf8'));
744+
if (key === 'field') delete fileContent.anyOf;
745+
746+
const tsFile = await compile(fileContent, mainExport, {
747+
additionalProperties: false,
748+
cwd: path.join(__dirname, '../schemas'),
749+
750+
// ensure that the default export uses a consistent name
751+
customName: (schema, fallback) =>
752+
schema.$schema && schema.$id
753+
? createTypeIdentifier(path.parse(schema.$id).name)
754+
: (schema.$id || schema.title || fallback || ''),
755+
});
756+
757+
758+
output.unshift(tsFile);
759+
output.push('');
760+
await fs.promises.writeFile(
761+
path.join(distDir, `${pluralKey || key}.d.json.ts`),
762+
output.join('\n'),
763+
);
764+
}
765+
766+
// finally, create the index file which re-exports everything
767+
// as named types.
768+
const indexFile = fileNames
769+
.map((fileName) => {
770+
const key = path.parse(fileName).name;
771+
return `export type * from './${KEY_MAP[key] || key}.d.json.ts';`;
772+
})
773+
.join('\n');
774+
775+
await fs.promises.writeFile(
776+
path.join(distDir, 'index.d.ts'),
777+
indexFile + '\n',
778+
);
779+
}
696780

697781
function validateCategoryPresets(categories, presets) {
698782
Object.keys(categories).forEach(id => {

ā€Žpackage-lock.jsonā€Ž

Lines changed: 49 additions & 5 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
@@ -16,6 +16,7 @@
1616
"chalk": "^5.0.1",
1717
"glob": "^11.0.2",
1818
"js-yaml": "^4.0.0",
19+
"json-schema-to-typescript-lite": "^15.0.0",
1920
"jsonschema": "^1.1.0",
2021
"marky": "^1.2.4",
2122
"node-fetch": "^3.2.10",

0 commit comments

Comments
Ā (0)
⚔