@datasworn-community/build-tools 0.2.1 → 0.2.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.
- package/package.json +6 -3
- package/schema-source/builders/IdBuilder.ts +849 -0
- package/schema-source/schema/common/Id.ts +3 -1
- package/schema-source/schema/delve/common.ts +3 -1
- package/schema-source/schema/generic/CollectableNode.ts +3 -1
- package/schema-source/schema/generic/CollectionNode.ts +3 -1
- package/schema-source/schema/generic/EmbedOnlyNode.ts +3 -1
- package/schema-source/schema/generic/NonCollectableNode.ts +3 -1
- package/schema-source/schema/generic/PrimaryTypeNode.ts +3 -1
- package/schema-source/schema/rules/TagRule.ts +3 -1
- package/schema-source/schema/utils/EmbeddedNode.ts +3 -1
- package/schema-source/schema/utils/string.ts +2 -2
- package/schema-source/scripts/augmentations.ts +70 -0
- package/schema-source/scripts/codegen/schemaToTsDeclaration.ts +290 -0
- package/schema-source/scripts/codegen/tsDocTags.ts +75 -0
- package/schema-source/scripts/codegen/writeTypescriptTypes.ts +35 -0
- package/schema-source/scripts/const.ts +40 -2
- package/schema-source/scripts/datasworn/sort.ts +80 -0
- package/schema-source/scripts/json-typedef/typedef.ts +1138 -0
- package/schema-source/scripts/json-typedef/utils.ts +349 -0
- package/schema-source/scripts/schema/buildSchema.ts +109 -0
- package/schema-source/scripts/utils/Log.ts +15 -0
- package/schema-source/scripts/utils/readWrite.ts +180 -0
- package/schema-source/scripts/validation/ajv.ts +29 -0
- package/schema-source/scripts/validation/formats.ts +6 -0
- package/schema-source/scripts/validation/keywords.ts +36 -0
|
@@ -7,11 +7,13 @@ import {
|
|
|
7
7
|
Type
|
|
8
8
|
} from '@sinclair/typebox'
|
|
9
9
|
|
|
10
|
+
import { IdElements } from '@datasworn-community/core'
|
|
10
11
|
import ids from '../../builders/IdBuilder.js'
|
|
11
|
-
import Pattern from '../../pkg-core/IdElements/Pattern.js'
|
|
12
12
|
import JtdType from '../../scripts/json-typedef/typedef.js'
|
|
13
13
|
import { JsonTypeDef } from '../Symbols.js'
|
|
14
14
|
|
|
15
|
+
import Pattern = IdElements.Pattern
|
|
16
|
+
|
|
15
17
|
export const RulesetId = Type.String({
|
|
16
18
|
$id: 'RulesetId',
|
|
17
19
|
examples: ['classic', 'starforged'],
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
type TString,
|
|
6
6
|
Type
|
|
7
7
|
} from '@sinclair/typebox'
|
|
8
|
-
import
|
|
8
|
+
import { IdElements } from '@datasworn-community/core'
|
|
9
|
+
|
|
10
|
+
import type TypeId = IdElements.TypeId
|
|
9
11
|
import { Computed } from '../Utils.js'
|
|
10
12
|
import { pascalCase } from '../utils/string.js'
|
|
11
13
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { CloneType, type ObjectOptions, type TObject } from '@sinclair/typebox'
|
|
2
2
|
import type { SetRequired } from 'type-fest'
|
|
3
|
-
import
|
|
3
|
+
import { IdElements } from '@datasworn-community/core'
|
|
4
|
+
|
|
5
|
+
import type TypeId = IdElements.TypeId
|
|
4
6
|
import { setSourceDataSchema } from '../Utils.js'
|
|
5
7
|
import { pascalCase } from '../utils/string.js'
|
|
6
8
|
import { PrimarySubtypeNode, PrimaryTypeNode } from './PrimaryTypeNode.js'
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
Type
|
|
10
10
|
} from '@sinclair/typebox'
|
|
11
11
|
import type { SetRequired } from 'type-fest'
|
|
12
|
-
import
|
|
12
|
+
import { IdElements } from '@datasworn-community/core'
|
|
13
13
|
import {
|
|
14
14
|
CollectionsKey,
|
|
15
15
|
ContentsKey,
|
|
@@ -19,6 +19,8 @@ import * as Text from '../common/Text.js'
|
|
|
19
19
|
import { setSourceOptional } from '../Utils.js'
|
|
20
20
|
import { Assign } from '../utils/FlatIntersect.js'
|
|
21
21
|
import { pascalCase } from '../utils/string.js'
|
|
22
|
+
|
|
23
|
+
import TypeId = IdElements.TypeId
|
|
22
24
|
import { Dictionary, type TDictionary } from './Dictionary.js'
|
|
23
25
|
import { PrimarySubtypeNode, PrimaryTypeNode } from './PrimaryTypeNode.js'
|
|
24
26
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type ObjectOptions, type TObject, Type } from '@sinclair/typebox'
|
|
2
|
-
import
|
|
2
|
+
import { IdElements } from '@datasworn-community/core'
|
|
3
|
+
|
|
4
|
+
import type TypeId = IdElements.TypeId
|
|
3
5
|
import { pascalCase } from '../utils/string.js'
|
|
4
6
|
import { IdNode } from './IdNode.js'
|
|
5
7
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ObjectOptions, TObject } from '@sinclair/typebox'
|
|
2
|
-
import
|
|
2
|
+
import { IdElements } from '@datasworn-community/core'
|
|
3
|
+
|
|
4
|
+
import type TypeId = IdElements.TypeId
|
|
3
5
|
import { setSourceDataSchema } from '../Utils.js'
|
|
4
6
|
import { pascalCase } from '../utils/string.js'
|
|
5
7
|
import { PrimaryTypeNode } from './PrimaryTypeNode.js'
|
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
Type
|
|
7
7
|
} from '@sinclair/typebox'
|
|
8
8
|
import type { SetRequired } from 'type-fest'
|
|
9
|
-
import
|
|
9
|
+
import { IdElements } from '@datasworn-community/core'
|
|
10
|
+
|
|
11
|
+
import type TypeId = IdElements.TypeId
|
|
10
12
|
import { CssColor, SvgImageUrl, WebpImageUrl } from '../common/Metadata.js'
|
|
11
13
|
import { SourcedNode } from '../generic/SourcedNode.js'
|
|
12
14
|
import { Discriminable } from '../Utils.js'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Static, Type } from '@sinclair/typebox'
|
|
2
|
-
import
|
|
2
|
+
import { IdElements } from '@datasworn-community/core'
|
|
3
3
|
import JtdType from '../../scripts/json-typedef/typedef.js'
|
|
4
4
|
import Id, { RulesetId } from '../common/Id.js'
|
|
5
5
|
import { DiceExpression } from '../common/Rolls.js'
|
|
@@ -9,6 +9,8 @@ import { Nullable, UnionEnum } from '../Utils.js'
|
|
|
9
9
|
import { type PascalCase, pascalCase } from '../utils/string.js'
|
|
10
10
|
import { TagSchema } from './TagSchema.js'
|
|
11
11
|
|
|
12
|
+
import TypeId = IdElements.TypeId
|
|
13
|
+
|
|
12
14
|
type _NodeSchemaName<T extends string> = PascalCase<T>
|
|
13
15
|
type EmbeddedNodeSchemaName<T extends string> = `Embedded${PascalCase<T>}`
|
|
14
16
|
type IdSchemaName<T extends string> = `${PascalCase<T>}Id`
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
Type
|
|
11
11
|
} from '@sinclair/typebox'
|
|
12
12
|
import type { Writable } from 'type-fest'
|
|
13
|
-
import
|
|
13
|
+
import { IdElements } from '@datasworn-community/core'
|
|
14
14
|
import {
|
|
15
15
|
EnhancesKey,
|
|
16
16
|
IdKey,
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
type TypeSep
|
|
20
20
|
} from '../../scripts/const.js'
|
|
21
21
|
import { Computed } from './Computed.js'
|
|
22
|
+
|
|
23
|
+
import TypeId = IdElements.TypeId
|
|
22
24
|
import { pascalCase } from './string.js'
|
|
23
25
|
|
|
24
26
|
const EmbeddedDictionaryKeys = Object.values(TypeId.BranchKey)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { camelCase } from 'lodash-es'
|
|
2
2
|
import type { PascalCase as PascalCaseNoDot } from 'type-fest'
|
|
3
|
-
import type {
|
|
3
|
+
import type { Utils } from '@datasworn-community/core'
|
|
4
4
|
|
|
5
5
|
export function capitalize<Str extends string = string>(str: Str) {
|
|
6
6
|
return (str[0].toLocaleUpperCase('en-US') + str.slice(1)) as Capitalize<Str>
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export type PascalCase<T extends string> = PascalCaseNoDot<
|
|
10
|
-
Join<Split<T, '.'>, '_'>
|
|
10
|
+
Utils.Join<Utils.Split<T, '.'>, '_'>
|
|
11
11
|
>
|
|
12
12
|
|
|
13
13
|
export function pascalCase<Str extends string = string>(str: Str) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type Kind, type Static, type TSchema, Type } from '@sinclair/typebox'
|
|
2
|
+
import { DiceExpression } from '../schema/common/Rolls.js'
|
|
3
|
+
import type {
|
|
4
|
+
Inherits,
|
|
5
|
+
JsonTypeDef,
|
|
6
|
+
Namespace,
|
|
7
|
+
Simplify,
|
|
8
|
+
Typescript
|
|
9
|
+
} from '../schema/Symbols.js'
|
|
10
|
+
import { UnionEnum } from '../schema/Utils.js'
|
|
11
|
+
import type { Metadata } from './json-typedef/typedef.js'
|
|
12
|
+
|
|
13
|
+
export const Keywords = {
|
|
14
|
+
releaseStage: UnionEnum(['experimental', 'release'], {
|
|
15
|
+
default: 'release'
|
|
16
|
+
}),
|
|
17
|
+
i18n: Type.Boolean({ default: false }),
|
|
18
|
+
deprecated: Type.Boolean({ default: false }),
|
|
19
|
+
remarks: Type.String(),
|
|
20
|
+
rollable: Type.Union([Type.Boolean(), DiceExpression], {
|
|
21
|
+
description:
|
|
22
|
+
'This array represents rows in a rollable table. If `true`, use the `dice` property of the parent object to roll. Alternatively, provide a dice expression to use.',
|
|
23
|
+
default: false
|
|
24
|
+
})
|
|
25
|
+
} as const
|
|
26
|
+
|
|
27
|
+
type TypeParamCodeGenData = {
|
|
28
|
+
/** Default value for the type parameter. */
|
|
29
|
+
default?: string
|
|
30
|
+
parameters?: Record<string, TypeParamCodeGenData>
|
|
31
|
+
constraint?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type TypeCodeGenData = {
|
|
35
|
+
id: string
|
|
36
|
+
/**
|
|
37
|
+
* Key is the type generic parameter ID (e.g. `TType`).
|
|
38
|
+
*/
|
|
39
|
+
parameters?: Record<string, TypeParamCodeGenData>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare module '@sinclair/typebox' {
|
|
43
|
+
interface SchemaOptions {
|
|
44
|
+
rollable?: Static<typeof Keywords.rollable>
|
|
45
|
+
remarks?: Static<typeof Keywords.remarks>
|
|
46
|
+
releaseStage?: Static<typeof Keywords.releaseStage>
|
|
47
|
+
deprecated?: Static<typeof Keywords.deprecated>
|
|
48
|
+
[Namespace]?: string
|
|
49
|
+
[Inherits]?: TypeCodeGenData[]
|
|
50
|
+
/** A less complex alternate version of the schema for use with code generation tools. */
|
|
51
|
+
[Simplify]?: TSchema
|
|
52
|
+
[Typescript]?: (identifier: string, schema: TSchema) => string
|
|
53
|
+
[JsonTypeDef]?: {
|
|
54
|
+
/** JTD schema to override the automatic conversion. Schema metadata will automatically be inherited from the JSON schema. */
|
|
55
|
+
schema?: TSchema & { [Kind]: `TypeDef:${string}` }
|
|
56
|
+
/** If true, this schema will be ignored when generating JTD schema. */
|
|
57
|
+
skip?: boolean
|
|
58
|
+
metadata?: Metadata
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
interface StringOptions extends SchemaOptions {
|
|
62
|
+
i18n?: Static<typeof Keywords.i18n>
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare global {
|
|
67
|
+
interface ObjectConstructor {
|
|
68
|
+
keys<T extends object>(object: T): (keyof T)[]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Kind,
|
|
3
|
+
type TArray,
|
|
4
|
+
type TObject,
|
|
5
|
+
type TSchema,
|
|
6
|
+
type TTuple,
|
|
7
|
+
TypeGuard
|
|
8
|
+
} from '@sinclair/typebox'
|
|
9
|
+
import {
|
|
10
|
+
isEqual,
|
|
11
|
+
isNull,
|
|
12
|
+
isUndefined,
|
|
13
|
+
map,
|
|
14
|
+
mapValues,
|
|
15
|
+
pick,
|
|
16
|
+
repeat,
|
|
17
|
+
uniq
|
|
18
|
+
} from 'lodash-es'
|
|
19
|
+
import { Typescript } from '../../schema/Symbols.js'
|
|
20
|
+
import {
|
|
21
|
+
TDiscriminatedUnion,
|
|
22
|
+
TNullable,
|
|
23
|
+
TUnionEnum
|
|
24
|
+
} from '../../schema/Utils.js'
|
|
25
|
+
import { $schema } from '../const.js'
|
|
26
|
+
import Log from '../utils/Log.js'
|
|
27
|
+
|
|
28
|
+
export function extractDefs(defs: Record<string, TSchema>) {
|
|
29
|
+
return mapValues(defs, (v, k) => renderDefinition(k, v))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// keywords where they can be included close to as-is. boolean keywords generally have their value omitted, leaving just the tag: "@i18n" rather than "@i18n true"
|
|
33
|
+
const extractableKeywords: string[] = [
|
|
34
|
+
'minimum',
|
|
35
|
+
'maximum',
|
|
36
|
+
'i18n',
|
|
37
|
+
'deprecated'
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
// keywords where a string value is expected, and the string value is the keyword
|
|
41
|
+
const extractableKeywordValues: string[] = ['releaseStage']
|
|
42
|
+
|
|
43
|
+
function extractKeywords(schema: TSchema) {
|
|
44
|
+
const jsDoc: string[] = []
|
|
45
|
+
|
|
46
|
+
if (schema.description) jsDoc.push(schema.description)
|
|
47
|
+
if (schema.remarks) jsDoc.push(tag('remarks', schema.remarks))
|
|
48
|
+
if (schema.pattern)
|
|
49
|
+
jsDoc.push(tag('pattern', wrapCodeBlock(`/${schema.pattern}/`)))
|
|
50
|
+
if (schema.examples)
|
|
51
|
+
jsDoc.push(
|
|
52
|
+
...schema.examples
|
|
53
|
+
.filter(
|
|
54
|
+
(example: unknown) =>
|
|
55
|
+
typeof example !== 'string' || !example.includes('*')
|
|
56
|
+
)
|
|
57
|
+
.map((example: unknown) => {
|
|
58
|
+
let tagContent = renderJsValue(example)
|
|
59
|
+
if (tagContent.includes('\n'))
|
|
60
|
+
tagContent = formatSourceCode(tagContent)
|
|
61
|
+
|
|
62
|
+
return tag('example', tagContent)
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
if (!isUndefined(schema.default)) {
|
|
66
|
+
if (!isEqual(schema.default, schema.const)) {
|
|
67
|
+
let tagContent = renderJsValue(schema.default)
|
|
68
|
+
if (tagContent.includes('\n'))
|
|
69
|
+
tagContent = `\n${formatSourceCode(tagContent)}`
|
|
70
|
+
jsDoc.push(tag('default', tagContent))
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
jsDoc.push(
|
|
75
|
+
...toTags(
|
|
76
|
+
mapValues(pick(schema, ...extractableKeywords), (v, _k) =>
|
|
77
|
+
v === true ? undefined : v
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
jsDoc.push(
|
|
83
|
+
...Object.values(pick(schema, ...extractableKeywordValues)).map((k) =>
|
|
84
|
+
tag(k as string)
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return jsDoc
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function renderJsDoc(lines: string[]) {
|
|
92
|
+
return [
|
|
93
|
+
'/**',
|
|
94
|
+
...lines
|
|
95
|
+
.join('\n')
|
|
96
|
+
.split('\n')
|
|
97
|
+
.map((line) => ` * ${line}`),
|
|
98
|
+
' */'
|
|
99
|
+
].join('\n')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function extractType(schema: TSchema): string {
|
|
103
|
+
switch (true) {
|
|
104
|
+
case TypeGuard.IsIntersect(schema):
|
|
105
|
+
return uniq(schema.allOf.map(extractType))
|
|
106
|
+
.map((type) => (type.includes('|') ? `(${type})` : type))
|
|
107
|
+
.join(' & ')
|
|
108
|
+
|
|
109
|
+
case TypeGuard.IsThis(schema):
|
|
110
|
+
case TypeGuard.IsRef(schema):
|
|
111
|
+
// special case: embedded schema
|
|
112
|
+
return schema.$ref === $schema ? 'unknown' : schema.$ref
|
|
113
|
+
case TypeGuard.IsLiteral(schema):
|
|
114
|
+
return JSON.stringify(schema.const)
|
|
115
|
+
case TypeGuard.IsInteger(schema):
|
|
116
|
+
return 'number'
|
|
117
|
+
case TypeGuard.IsString(schema):
|
|
118
|
+
case TypeGuard.IsBoolean(schema):
|
|
119
|
+
case TypeGuard.IsNull(schema):
|
|
120
|
+
case TypeGuard.IsNumber(schema):
|
|
121
|
+
return schema.type
|
|
122
|
+
case TypeGuard.IsTuple(schema):
|
|
123
|
+
return extractTupleType(schema)
|
|
124
|
+
case TypeGuard.IsArray(schema):
|
|
125
|
+
return extractArrayType(schema)
|
|
126
|
+
case schema[Kind] === 'Object':
|
|
127
|
+
case TypeGuard.IsObject(schema):
|
|
128
|
+
return extractObjectLiteralType(schema as any)
|
|
129
|
+
case TypeGuard.IsUnion(schema):
|
|
130
|
+
case schema[Kind] === 'Union':
|
|
131
|
+
case TNullable(schema):
|
|
132
|
+
return uniq(schema.anyOf.map(extractType)).join(' | ')
|
|
133
|
+
case TypeGuard.IsRecord(schema): {
|
|
134
|
+
const keyType = 'DictKey'
|
|
135
|
+
const valueType = extractType(Object.values(schema.patternProperties)[0])
|
|
136
|
+
return `Record<${keyType}, ${valueType}>`
|
|
137
|
+
}
|
|
138
|
+
case TDiscriminatedUnion(schema):
|
|
139
|
+
return uniq(schema.allOf.map((item) => extractType(item.then))).join(
|
|
140
|
+
' | '
|
|
141
|
+
)
|
|
142
|
+
case TUnionEnum(schema):
|
|
143
|
+
return uniq(schema.enum.map((v) => JSON.stringify(v))).join(' | ')
|
|
144
|
+
|
|
145
|
+
default: {
|
|
146
|
+
Log.info(JSON.stringify(schema))
|
|
147
|
+
throw new Error(
|
|
148
|
+
`missing transform for kind of ${JSON.stringify(schema?.$id)}: ${JSON.stringify(schema[Kind])}`
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function extractArrayType(schema: TArray<TSchema>): string {
|
|
155
|
+
const innerType = extractType(schema.items)
|
|
156
|
+
const isComplexType = innerType.match(/[^A-z]/)
|
|
157
|
+
return isComplexType ? `Array<${innerType}>` : `${innerType}[]`
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
type ParsedType = { type: string; jsDoc: string[] }
|
|
161
|
+
|
|
162
|
+
function parseType(schema: TSchema): ParsedType {
|
|
163
|
+
return {
|
|
164
|
+
type: extractType(schema),
|
|
165
|
+
jsDoc: extractKeywords(schema)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function indent(text: string, levels = 1) {
|
|
170
|
+
const indentString = repeat('\t', levels)
|
|
171
|
+
return text
|
|
172
|
+
.replaceAll(/\n/g, `\n${indentString}`)
|
|
173
|
+
.replaceAll(/^/g, indentString)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function renderProperty(key: string, schema: TSchema) {
|
|
177
|
+
let head = key
|
|
178
|
+
if (TypeGuard.IsOptional(schema)) head += '?'
|
|
179
|
+
const { type, jsDoc } = parseType(schema)
|
|
180
|
+
|
|
181
|
+
const lines = []
|
|
182
|
+
|
|
183
|
+
if (jsDoc.length > 0) lines.push(renderJsDoc(jsDoc))
|
|
184
|
+
|
|
185
|
+
lines.push(`${head}: ${type};`)
|
|
186
|
+
|
|
187
|
+
return lines.join('\n')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function renderTypeAlias(identifier: string, schema: TSchema) {
|
|
191
|
+
const data = parseType(schema)
|
|
192
|
+
const lines = []
|
|
193
|
+
|
|
194
|
+
if (data.jsDoc.length > 0) lines.push(renderJsDoc(data.jsDoc))
|
|
195
|
+
|
|
196
|
+
lines.push(`export type ${identifier} = ${data.type}`)
|
|
197
|
+
|
|
198
|
+
return lines.join('\n')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function renderInterface(identifier: string, schema: TObject) {
|
|
202
|
+
const data = parseType(schema)
|
|
203
|
+
|
|
204
|
+
const lines = []
|
|
205
|
+
|
|
206
|
+
if (data.jsDoc.length > 0) lines.push(renderJsDoc(data.jsDoc))
|
|
207
|
+
|
|
208
|
+
lines.push(`export interface ${identifier} ${data.type}`)
|
|
209
|
+
|
|
210
|
+
return lines.join('\n')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function extractObjectLiteralType(schema: TObject): string {
|
|
214
|
+
const properties = Object.entries(schema.properties).map((entry) =>
|
|
215
|
+
renderProperty(...entry)
|
|
216
|
+
)
|
|
217
|
+
const typeLines = [`{`, indent(properties.join('\n')), `}`]
|
|
218
|
+
|
|
219
|
+
return typeLines.join('\n')
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function extractTupleType(schema: TTuple): string {
|
|
223
|
+
const items = (schema.items as TSchema[]).map(extractType)
|
|
224
|
+
return [`[`, indent(items.join(',\n')), `]`].join('\n')
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function tag(tag: string, text?: string) {
|
|
228
|
+
if (text) return `@${tag} ${text}`
|
|
229
|
+
return `@${tag}`
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function toTags(tags: Record<string, string | undefined>) {
|
|
233
|
+
return Object.entries(tags).map((strings) => tag(...strings))
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function renderDefinition(identifier: string, schema: TSchema) {
|
|
237
|
+
switch (true) {
|
|
238
|
+
case Typescript in schema:
|
|
239
|
+
return schema[Typescript]?.(identifier, schema)
|
|
240
|
+
case TypeGuard.IsObject(schema):
|
|
241
|
+
return renderInterface(identifier, schema)
|
|
242
|
+
default:
|
|
243
|
+
return renderTypeAlias(identifier, schema)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function renderJsValue(value: unknown): string {
|
|
248
|
+
let result: string
|
|
249
|
+
switch (true) {
|
|
250
|
+
case Array.isArray(value):
|
|
251
|
+
result =
|
|
252
|
+
value.length === 0 ? '[]' : `[ ${value.map(renderJsValue).join(', ')} ]`
|
|
253
|
+
break
|
|
254
|
+
case isUndefined(value):
|
|
255
|
+
result = 'undefined'
|
|
256
|
+
break
|
|
257
|
+
case isNull(value):
|
|
258
|
+
result = 'null'
|
|
259
|
+
break
|
|
260
|
+
|
|
261
|
+
case typeof value === 'object':
|
|
262
|
+
result =
|
|
263
|
+
Object.keys(value as any).length === 0
|
|
264
|
+
? '{}'
|
|
265
|
+
: `{\n${indent(
|
|
266
|
+
map(value as any, (v, k) => `${k}: ${renderJsValue(v)}`).join(
|
|
267
|
+
',\n'
|
|
268
|
+
)
|
|
269
|
+
)}\n}`
|
|
270
|
+
break
|
|
271
|
+
case typeof value === 'string':
|
|
272
|
+
case typeof value === 'number':
|
|
273
|
+
case typeof value === 'boolean':
|
|
274
|
+
result = JSON.stringify(value)
|
|
275
|
+
break
|
|
276
|
+
default:
|
|
277
|
+
throw new Error()
|
|
278
|
+
}
|
|
279
|
+
return result
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function wrapCodeBlock(code: string) {
|
|
283
|
+
return `\`\`\`javascript\n${code}\n\`\`\``
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function formatSourceCode(code: string) {
|
|
287
|
+
if (code.includes('\n')) code = wrapCodeBlock(indent(code))
|
|
288
|
+
|
|
289
|
+
return code
|
|
290
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// @alpha
|
|
2
|
+
// @beta
|
|
3
|
+
// @category
|
|
4
|
+
// @defaultValue
|
|
5
|
+
// @deprecated
|
|
6
|
+
// @enum
|
|
7
|
+
// @event
|
|
8
|
+
// @eventProperty
|
|
9
|
+
// @example
|
|
10
|
+
// @experimental
|
|
11
|
+
// @group
|
|
12
|
+
// @hidden
|
|
13
|
+
// @ignore
|
|
14
|
+
// {@inheritDoc}
|
|
15
|
+
// @interface
|
|
16
|
+
// @internal
|
|
17
|
+
// {@label}
|
|
18
|
+
// {@link}
|
|
19
|
+
// @module
|
|
20
|
+
// @namespace
|
|
21
|
+
// @overload
|
|
22
|
+
// @override
|
|
23
|
+
// @packageDocumentation
|
|
24
|
+
// @param
|
|
25
|
+
// @private
|
|
26
|
+
// @privateRemarks
|
|
27
|
+
// @property
|
|
28
|
+
// @protected
|
|
29
|
+
// @public
|
|
30
|
+
// @readonly
|
|
31
|
+
// @remarks
|
|
32
|
+
// @returns
|
|
33
|
+
// @satisfies
|
|
34
|
+
// @sealed
|
|
35
|
+
// @see
|
|
36
|
+
// @template
|
|
37
|
+
// @throws
|
|
38
|
+
// @typeParam
|
|
39
|
+
|
|
40
|
+
import { Type } from '@sinclair/typebox'
|
|
41
|
+
import type { KeywordDefinition } from 'ajv'
|
|
42
|
+
|
|
43
|
+
// @virtual
|
|
44
|
+
const _tags: Record<string, Omit<KeywordDefinition, 'keyword'>> = {
|
|
45
|
+
alpha: { metaSchema: Type.Boolean() },
|
|
46
|
+
beta: { metaSchema: Type.Boolean() },
|
|
47
|
+
category: { metaSchema: Type.String() },
|
|
48
|
+
deprecated: { metaSchema: Type.Boolean() },
|
|
49
|
+
event: { metaSchema: Type.Boolean() },
|
|
50
|
+
eventProperty: { metaSchema: Type.Boolean() },
|
|
51
|
+
example: { metaSchema: Type.Boolean() },
|
|
52
|
+
experimental: { metaSchema: Type.Boolean() },
|
|
53
|
+
group: { metaSchema: Type.Array(Type.String()) },
|
|
54
|
+
hidden: { metaSchema: Type.Boolean() },
|
|
55
|
+
ignore: { metaSchema: Type.Boolean() },
|
|
56
|
+
interface: { metaSchema: Type.Boolean() },
|
|
57
|
+
internal: { metaSchema: Type.Boolean() },
|
|
58
|
+
namespace: { metaSchema: Type.Array(Type.String()) },
|
|
59
|
+
overload: { metaSchema: Type.Boolean() },
|
|
60
|
+
override: { metaSchema: Type.Boolean() },
|
|
61
|
+
packageDocumentation: { metaSchema: Type.Boolean() },
|
|
62
|
+
param: { metaSchema: Type.Boolean() },
|
|
63
|
+
private: { metaSchema: Type.Boolean() },
|
|
64
|
+
privateRemarks: { metaSchema: Type.String() },
|
|
65
|
+
property: { metaSchema: Type.Boolean() },
|
|
66
|
+
remarks: { metaSchema: Type.String() },
|
|
67
|
+
returns: { metaSchema: Type.Boolean() },
|
|
68
|
+
satisfies: { metaSchema: Type.Boolean() },
|
|
69
|
+
sealed: { metaSchema: Type.Boolean() },
|
|
70
|
+
see: { metaSchema: Type.Array(Type.String()) },
|
|
71
|
+
template: { metaSchema: Type.Boolean() },
|
|
72
|
+
throws: { metaSchema: Type.Boolean() },
|
|
73
|
+
typeParam: { metaSchema: Type.Boolean() },
|
|
74
|
+
virtual: { metaSchema: Type.Boolean() }
|
|
75
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { DataswornSchema, DataswornSourceSchema } from '../../schema/Root.js'
|
|
3
|
+
import {
|
|
4
|
+
CORE_COMMON,
|
|
5
|
+
DefsKey,
|
|
6
|
+
DIR_HISTORY_CURRENT,
|
|
7
|
+
ROOT_TYPES_OUT
|
|
8
|
+
} from '../const.js'
|
|
9
|
+
import { emptyDir, writeCode } from '../utils/readWrite.js'
|
|
10
|
+
import { extractDefs } from './schemaToTsDeclaration.js'
|
|
11
|
+
|
|
12
|
+
const rootSchemas = {
|
|
13
|
+
Datasworn: DataswornSchema,
|
|
14
|
+
DataswornSource: DataswornSourceSchema
|
|
15
|
+
} satisfies Record<string, unknown>
|
|
16
|
+
|
|
17
|
+
await emptyDir(ROOT_TYPES_OUT)
|
|
18
|
+
|
|
19
|
+
const writeJobs: Promise<void>[] = []
|
|
20
|
+
|
|
21
|
+
for (const identifier in rootSchemas) {
|
|
22
|
+
const value = rootSchemas[identifier as keyof typeof rootSchemas]
|
|
23
|
+
|
|
24
|
+
const paths = [
|
|
25
|
+
path.join(ROOT_TYPES_OUT, `${identifier}.d.ts`),
|
|
26
|
+
path.join(CORE_COMMON, `${identifier}.ts`),
|
|
27
|
+
path.join(DIR_HISTORY_CURRENT, `${identifier}.d.ts`)
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const fileContents = Object.values(extractDefs(value[DefsKey])).join('\n\n')
|
|
31
|
+
|
|
32
|
+
for (const path of paths) writeJobs.push(writeCode(path, fileContents))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await Promise.all(writeJobs)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
-
import { DATASWORN_SCHEMA_VERSION } from '@datasworn-community/core'
|
|
2
|
+
import { DATASWORN_SCHEMA_VERSION, IdElements } from '@datasworn-community/core'
|
|
3
3
|
|
|
4
4
|
const root = process.cwd()
|
|
5
5
|
|
|
@@ -16,7 +16,12 @@ export const ROOT_OUTPUT = path.join(root, 'datasworn')
|
|
|
16
16
|
export const ROOT_SOURCE_DATA = path.join(root, 'source_data')
|
|
17
17
|
export const ROOT_HISTORY = path.join(root, 'packages/core/migration')
|
|
18
18
|
export const DIR_HISTORY_CURRENT = path.join(ROOT_HISTORY, VERSION)
|
|
19
|
-
|
|
19
|
+
// Disposable scratch copy of generated types, emptied and rewritten on every
|
|
20
|
+
// `generate` run. Must stay distinct from CORE_COMMON: writeTypescriptTypes
|
|
21
|
+
// empties this directory before writing, and CORE_COMMON holds core's
|
|
22
|
+
// hand-written runtime source alongside the generated Datasworn.ts/
|
|
23
|
+
// DataswornSource.ts files.
|
|
24
|
+
export const ROOT_TYPES_OUT = path.join(root, '.generated/types')
|
|
20
25
|
export const CORE_COMMON = path.join(root, 'packages/core/src')
|
|
21
26
|
|
|
22
27
|
export const SCHEMA_PATH = path.join(
|
|
@@ -53,3 +58,36 @@ export const DefsKey = 'definitions' as const
|
|
|
53
58
|
|
|
54
59
|
/** Identifier for the type at the root of a Datasworn JSON file. */
|
|
55
60
|
export const rootSchemaName = 'RulesPackage'
|
|
61
|
+
|
|
62
|
+
// Re-exported from core's IdElements/CONST.js. Upstream colocates these
|
|
63
|
+
// scripts with pkg-core under one `src/`, so its const.ts can `export *`
|
|
64
|
+
// straight from the relative path; here build-tools and core are separate
|
|
65
|
+
// workspace packages, so the keys are re-exported through core's public API.
|
|
66
|
+
export const COLLECTION_DEPTH_MAX = IdElements.CONST.COLLECTION_DEPTH_MAX
|
|
67
|
+
export type COLLECTION_DEPTH_MAX = typeof COLLECTION_DEPTH_MAX
|
|
68
|
+
export const COLLECTION_DEPTH_MIN = IdElements.CONST.COLLECTION_DEPTH_MIN
|
|
69
|
+
export type COLLECTION_DEPTH_MIN = typeof COLLECTION_DEPTH_MIN
|
|
70
|
+
export const MdLinkPrefix = IdElements.CONST.MdLinkPrefix
|
|
71
|
+
export type MdLinkPrefix = typeof MdLinkPrefix
|
|
72
|
+
export const PathKeySep = IdElements.CONST.PathKeySep
|
|
73
|
+
export type PathKeySep = typeof PathKeySep
|
|
74
|
+
export const TypeSep = IdElements.CONST.TypeSep
|
|
75
|
+
export type TypeSep = typeof TypeSep
|
|
76
|
+
export const PrefixSep = IdElements.CONST.PrefixSep
|
|
77
|
+
export type PrefixSep = typeof PrefixSep
|
|
78
|
+
export const WildcardString = IdElements.CONST.WildcardString
|
|
79
|
+
export type WildcardString = typeof WildcardString
|
|
80
|
+
export const GlobstarString = IdElements.CONST.GlobstarString
|
|
81
|
+
export type GlobstarString = typeof GlobstarString
|
|
82
|
+
export const CollectionsKey = IdElements.CONST.CollectionsKey
|
|
83
|
+
export type CollectionsKey = typeof CollectionsKey
|
|
84
|
+
export const ContentsKey = IdElements.CONST.ContentsKey
|
|
85
|
+
export type ContentsKey = typeof ContentsKey
|
|
86
|
+
export const EnhancesKey = IdElements.CONST.EnhancesKey
|
|
87
|
+
export type EnhancesKey = typeof EnhancesKey
|
|
88
|
+
export const ReplacesKey = IdElements.CONST.ReplacesKey
|
|
89
|
+
export type ReplacesKey = typeof ReplacesKey
|
|
90
|
+
export const IdKey = IdElements.CONST.IdKey
|
|
91
|
+
export type IdKey = typeof IdKey
|
|
92
|
+
export const SourceInfoKey = IdElements.CONST.SourceInfoKey
|
|
93
|
+
export type SourceInfoKey = typeof SourceInfoKey
|