@genesislcap/ts-builder 14.417.0 → 14.417.1-alpha-efccb2a.0
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/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/react-jsx-generator.d.ts +6 -0
- package/dist/react-jsx-generator.d.ts.map +1 -0
- package/dist/react-jsx-generator.js +374 -0
- package/package.json +3 -3
- package/src/index.ts +8 -0
- package/src/react-jsx-generator.ts +439 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;yBAIrC,KAAK,YAAY;AAAvC,wBAkDE"}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const build_kit_1 = require("@genesislcap/build-kit");
|
|
5
5
|
const consola_1 = tslib_1.__importDefault(require("consola"));
|
|
6
|
+
const react_jsx_generator_1 = require("./react-jsx-generator");
|
|
6
7
|
exports.default = (ctx) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
7
8
|
const { dirs: { cwd }, cli: { isDev, isBuild, options }, config: { clean, copyAssets }, enable: { apiExtractor, cemAnalyzer }, } = ctx;
|
|
8
9
|
if (clean) {
|
|
@@ -34,6 +35,13 @@ exports.default = (ctx) => tslib_1.__awaiter(void 0, void 0, void 0, function* (
|
|
|
34
35
|
const cem = yield (0, build_kit_1.resolveBin)('cem', '@custom-elements-manifest/analyzer');
|
|
35
36
|
(0, build_kit_1.run)(cwd, `${cem} analyze --config custom-elements-manifest.config.*js`);
|
|
36
37
|
}
|
|
38
|
+
const reactTypesResult = yield (0, react_jsx_generator_1.generateReactJsxTypes)(cwd);
|
|
39
|
+
if (reactTypesResult.generated) {
|
|
40
|
+
consola_1.default.log(`\nReact JSX types generated: ${reactTypesResult.path}`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
consola_1.default.debug(`React JSX types were skipped: ${reactTypesResult.reason}`);
|
|
44
|
+
}
|
|
37
45
|
}
|
|
38
46
|
else {
|
|
39
47
|
throw new Error(`Unrecognized command: ${JSON.stringify(options)}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-jsx-generator.d.ts","sourceRoot":"","sources":["../src/react-jsx-generator.ts"],"names":[],"mappings":"AAgZA,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAoCjE"}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateReactJsxTypes = generateReactJsxTypes;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const promises_1 = require("node:fs/promises");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const GENERATED_FILE_NAME = 'react-jsx-runtime.d.ts';
|
|
8
|
+
const primitiveUnionRegex = /^(?:\s*(?:string|number|boolean|bigint|null|undefined|unknown|any|void|'[^']*'|"[^"]*"|`[^`]*`|(?:\d+(?:\.\d+)?))\s*)(?:\|\s*(?:string|number|boolean|bigint|null|undefined|unknown|any|void|'[^']*'|"[^"]*"|`[^`]*`|(?:\d+(?:\.\d+)?))\s*)*$/;
|
|
9
|
+
const allowedTypeIdentifierRegex = /^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/;
|
|
10
|
+
const allowedTypeNameSet = new Set([
|
|
11
|
+
'Array',
|
|
12
|
+
'ReadonlyArray',
|
|
13
|
+
'Promise',
|
|
14
|
+
'Record',
|
|
15
|
+
'Partial',
|
|
16
|
+
'Required',
|
|
17
|
+
'Pick',
|
|
18
|
+
'Omit',
|
|
19
|
+
'Map',
|
|
20
|
+
'Set',
|
|
21
|
+
'WeakMap',
|
|
22
|
+
'WeakSet',
|
|
23
|
+
'Date',
|
|
24
|
+
'RegExp',
|
|
25
|
+
'Error',
|
|
26
|
+
'Node',
|
|
27
|
+
'Element',
|
|
28
|
+
'HTMLElement',
|
|
29
|
+
'SVGElement',
|
|
30
|
+
'Event',
|
|
31
|
+
'CustomEvent',
|
|
32
|
+
'MouseEvent',
|
|
33
|
+
'KeyboardEvent',
|
|
34
|
+
'FocusEvent',
|
|
35
|
+
'InputEvent',
|
|
36
|
+
'PointerEvent',
|
|
37
|
+
'WheelEvent',
|
|
38
|
+
'DragEvent',
|
|
39
|
+
'SubmitEvent',
|
|
40
|
+
'AbortSignal',
|
|
41
|
+
'DOMRect',
|
|
42
|
+
'Document',
|
|
43
|
+
'Window',
|
|
44
|
+
'URL',
|
|
45
|
+
'URLSearchParams',
|
|
46
|
+
'CSSStyleDeclaration',
|
|
47
|
+
'Intl.Locale',
|
|
48
|
+
]);
|
|
49
|
+
function normalizeWhitespace(value) {
|
|
50
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
51
|
+
}
|
|
52
|
+
function toTypeNameSegment(value) {
|
|
53
|
+
const pascal = toPascalCase(value);
|
|
54
|
+
if (!pascal) {
|
|
55
|
+
return 'Pkg';
|
|
56
|
+
}
|
|
57
|
+
if (/^[0-9]/.test(pascal)) {
|
|
58
|
+
return `Pkg${pascal}`;
|
|
59
|
+
}
|
|
60
|
+
return pascal;
|
|
61
|
+
}
|
|
62
|
+
function getPackageTypePrefix(packageName) {
|
|
63
|
+
const withoutScope = packageName.replace(/^@/, '').replace(/\//g, '-');
|
|
64
|
+
const segment = toTypeNameSegment(withoutScope);
|
|
65
|
+
return `${segment}React`;
|
|
66
|
+
}
|
|
67
|
+
function normalizePropertyName(name) {
|
|
68
|
+
let normalized = name.trim();
|
|
69
|
+
if (!normalized)
|
|
70
|
+
return null;
|
|
71
|
+
const bracketQuotedMatch = normalized.match(/^\[\s*['"](.+?)['"]\s*\]$/);
|
|
72
|
+
if (bracketQuotedMatch) {
|
|
73
|
+
normalized = bracketQuotedMatch[1];
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const bracketMatch = normalized.match(/^\[\s*(.+?)\s*\]$/);
|
|
77
|
+
if (bracketMatch) {
|
|
78
|
+
normalized = bracketMatch[1];
|
|
79
|
+
}
|
|
80
|
+
const quotedMatch = normalized.match(/^['"](.+?)['"]$/);
|
|
81
|
+
if (quotedMatch) {
|
|
82
|
+
normalized = quotedMatch[1];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
normalized = normalized.trim();
|
|
86
|
+
if (!normalized)
|
|
87
|
+
return null;
|
|
88
|
+
if (normalized.includes('\n') || normalized.includes('\r'))
|
|
89
|
+
return null;
|
|
90
|
+
return normalized;
|
|
91
|
+
}
|
|
92
|
+
function escapeSingleQuotes(value) {
|
|
93
|
+
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
94
|
+
}
|
|
95
|
+
function isKnownTypeIdentifier(identifier) {
|
|
96
|
+
if (allowedTypeNameSet.has(identifier))
|
|
97
|
+
return true;
|
|
98
|
+
if (allowedTypeIdentifierRegex.test(identifier) && identifier.startsWith('globalThis.'))
|
|
99
|
+
return true;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
function canUseComplexType(typeText) {
|
|
103
|
+
var _a;
|
|
104
|
+
if (!typeText)
|
|
105
|
+
return false;
|
|
106
|
+
if (/[{};=]/.test(typeText))
|
|
107
|
+
return false;
|
|
108
|
+
if (/=>/.test(typeText))
|
|
109
|
+
return false;
|
|
110
|
+
if (!/^[A-Za-z0-9_$<>\[\]\(\)\|&,.?'"`\s:-]+$/.test(typeText))
|
|
111
|
+
return false;
|
|
112
|
+
const identifierMatches = (_a = typeText.match(/[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*/g)) !== null && _a !== void 0 ? _a : [];
|
|
113
|
+
for (const match of identifierMatches) {
|
|
114
|
+
if (match === 'true' ||
|
|
115
|
+
match === 'false' ||
|
|
116
|
+
match === 'null' ||
|
|
117
|
+
match === 'undefined' ||
|
|
118
|
+
match === 'string' ||
|
|
119
|
+
match === 'number' ||
|
|
120
|
+
match === 'boolean' ||
|
|
121
|
+
match === 'bigint' ||
|
|
122
|
+
match === 'symbol' ||
|
|
123
|
+
match === 'unknown' ||
|
|
124
|
+
match === 'any' ||
|
|
125
|
+
match === 'void' ||
|
|
126
|
+
match === 'never') {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (!isKnownTypeIdentifier(match)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
function toPascalCase(value) {
|
|
136
|
+
return value
|
|
137
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
.map((part) => `${part[0].toUpperCase()}${part.slice(1)}`)
|
|
140
|
+
.join('');
|
|
141
|
+
}
|
|
142
|
+
function toCamelCase(value) {
|
|
143
|
+
const pascal = toPascalCase(value);
|
|
144
|
+
if (!pascal)
|
|
145
|
+
return '';
|
|
146
|
+
return `${pascal[0].toLowerCase()}${pascal.slice(1)}`;
|
|
147
|
+
}
|
|
148
|
+
function toSafeType(typeText) {
|
|
149
|
+
if (!typeText)
|
|
150
|
+
return 'unknown';
|
|
151
|
+
const normalized = normalizeWhitespace(typeText);
|
|
152
|
+
if (!normalized)
|
|
153
|
+
return 'unknown';
|
|
154
|
+
if (primitiveUnionRegex.test(normalized)) {
|
|
155
|
+
return normalized;
|
|
156
|
+
}
|
|
157
|
+
if (normalized.endsWith('[]')) {
|
|
158
|
+
const itemType = normalized.slice(0, -2).trim();
|
|
159
|
+
if (primitiveUnionRegex.test(itemType)) {
|
|
160
|
+
return `${itemType}[]`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (canUseComplexType(normalized)) {
|
|
164
|
+
return normalized;
|
|
165
|
+
}
|
|
166
|
+
return 'unknown';
|
|
167
|
+
}
|
|
168
|
+
function getCEMManifestPath(cwd, packageJson) {
|
|
169
|
+
if (typeof packageJson.customElements === 'string' && packageJson.customElements.trim().length > 0) {
|
|
170
|
+
return (0, node_path_1.resolve)(cwd, packageJson.customElements);
|
|
171
|
+
}
|
|
172
|
+
return (0, node_path_1.resolve)(cwd, 'dist/custom-elements.json');
|
|
173
|
+
}
|
|
174
|
+
function collectCustomElements(manifest) {
|
|
175
|
+
var _a, _b;
|
|
176
|
+
const elements = [];
|
|
177
|
+
for (const moduleDefinition of (_a = manifest.modules) !== null && _a !== void 0 ? _a : []) {
|
|
178
|
+
for (const declaration of (_b = moduleDefinition.declarations) !== null && _b !== void 0 ? _b : []) {
|
|
179
|
+
if (declaration.customElement && declaration.tagName) {
|
|
180
|
+
elements.push(declaration);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return elements;
|
|
185
|
+
}
|
|
186
|
+
function normalizeTagNames(tagName) {
|
|
187
|
+
const prefixToken = '%%prefix%%-';
|
|
188
|
+
if (!tagName.includes(prefixToken)) {
|
|
189
|
+
return [tagName];
|
|
190
|
+
}
|
|
191
|
+
const suffix = tagName.replace(prefixToken, '');
|
|
192
|
+
return [`rapid-${suffix}`, `zero-${suffix}`];
|
|
193
|
+
}
|
|
194
|
+
function toEventHandlerType(typeText) {
|
|
195
|
+
var _a;
|
|
196
|
+
if (!typeText) {
|
|
197
|
+
return '(event: CustomEvent<unknown>) => void';
|
|
198
|
+
}
|
|
199
|
+
const normalized = typeText.trim();
|
|
200
|
+
if (!normalized) {
|
|
201
|
+
return '(event: CustomEvent<unknown>) => void';
|
|
202
|
+
}
|
|
203
|
+
if (normalized === 'Event') {
|
|
204
|
+
return '(event: Event) => void';
|
|
205
|
+
}
|
|
206
|
+
if (normalized.startsWith('CustomEvent<')) {
|
|
207
|
+
const match = normalized.match(/^CustomEvent<(.+)>$/);
|
|
208
|
+
if (!match)
|
|
209
|
+
return '(event: CustomEvent<unknown>) => void';
|
|
210
|
+
const detailType = toSafeType((_a = match[1]) === null || _a === void 0 ? void 0 : _a.trim());
|
|
211
|
+
return `(event: CustomEvent<${detailType}>) => void`;
|
|
212
|
+
}
|
|
213
|
+
if (normalized === 'CustomEvent') {
|
|
214
|
+
return '(event: CustomEvent<unknown>) => void';
|
|
215
|
+
}
|
|
216
|
+
return '(event: CustomEvent<unknown>) => void';
|
|
217
|
+
}
|
|
218
|
+
function buildElementProps(declaration) {
|
|
219
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
220
|
+
const propertyMap = new Map();
|
|
221
|
+
const eventMap = new Map();
|
|
222
|
+
const addProperty = (name, typeText) => {
|
|
223
|
+
if (!name)
|
|
224
|
+
return;
|
|
225
|
+
const normalizedName = normalizePropertyName(name);
|
|
226
|
+
if (!normalizedName)
|
|
227
|
+
return;
|
|
228
|
+
const safeType = toSafeType(typeText);
|
|
229
|
+
if (!propertyMap.has(normalizedName)) {
|
|
230
|
+
propertyMap.set(normalizedName, safeType);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
for (const attribute of (_a = declaration.attributes) !== null && _a !== void 0 ? _a : []) {
|
|
234
|
+
// Prefer React-friendly property casing when available.
|
|
235
|
+
if (attribute.fieldName) {
|
|
236
|
+
addProperty(attribute.fieldName, (_b = attribute.type) === null || _b === void 0 ? void 0 : _b.text);
|
|
237
|
+
}
|
|
238
|
+
else if (attribute.name) {
|
|
239
|
+
addProperty(attribute.name, (_c = attribute.type) === null || _c === void 0 ? void 0 : _c.text);
|
|
240
|
+
if (attribute.name.includes('-')) {
|
|
241
|
+
const camelName = toCamelCase(attribute.name);
|
|
242
|
+
if (camelName) {
|
|
243
|
+
addProperty(camelName, (_d = attribute.type) === null || _d === void 0 ? void 0 : _d.text);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Only include public fields that are reflected as attributes.
|
|
249
|
+
for (const member of (_e = declaration.members) !== null && _e !== void 0 ? _e : []) {
|
|
250
|
+
if (member.kind === 'field' &&
|
|
251
|
+
member.privacy !== 'private' &&
|
|
252
|
+
member.privacy !== 'protected' &&
|
|
253
|
+
member.attribute &&
|
|
254
|
+
member.name &&
|
|
255
|
+
!member.name.startsWith('_') &&
|
|
256
|
+
!member.name.startsWith('$')) {
|
|
257
|
+
// React canonical typing should expose property name (camelCase).
|
|
258
|
+
addProperty(member.name, (_f = member.type) === null || _f === void 0 ? void 0 : _f.text);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
for (const event of (_g = declaration.events) !== null && _g !== void 0 ? _g : []) {
|
|
262
|
+
if (!event.name)
|
|
263
|
+
continue;
|
|
264
|
+
const normalizedEventName = normalizePropertyName(event.name);
|
|
265
|
+
if (!normalizedEventName)
|
|
266
|
+
continue;
|
|
267
|
+
const eventHandlerType = toEventHandlerType((_h = event.type) === null || _h === void 0 ? void 0 : _h.text);
|
|
268
|
+
const pascalName = toPascalCase(normalizedEventName);
|
|
269
|
+
if (pascalName) {
|
|
270
|
+
eventMap.set(`on${pascalName}`, eventHandlerType);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const lines = [
|
|
274
|
+
'children?: unknown;',
|
|
275
|
+
'class?: string;',
|
|
276
|
+
'className?: string;',
|
|
277
|
+
'id?: string;',
|
|
278
|
+
'slot?: string;',
|
|
279
|
+
'part?: string;',
|
|
280
|
+
'style?: string | Partial<CSSStyleDeclaration>;',
|
|
281
|
+
'role?: string;',
|
|
282
|
+
'tabIndex?: number | string;',
|
|
283
|
+
'[key: `data-${string}`]: string | number | boolean | undefined;',
|
|
284
|
+
'[key: `aria-${string}`]: string | number | boolean | undefined;',
|
|
285
|
+
];
|
|
286
|
+
for (const [name, typeText] of propertyMap) {
|
|
287
|
+
lines.push(`'${escapeSingleQuotes(name)}'?: ${typeText};`);
|
|
288
|
+
}
|
|
289
|
+
for (const [name, handlerType] of eventMap) {
|
|
290
|
+
lines.push(`'${escapeSingleQuotes(name)}'?: ${handlerType};`);
|
|
291
|
+
}
|
|
292
|
+
return lines;
|
|
293
|
+
}
|
|
294
|
+
function generateDeclarations(packageName, declarations) {
|
|
295
|
+
const packagePrefix = getPackageTypePrefix(packageName);
|
|
296
|
+
const interfacesByTag = new Map();
|
|
297
|
+
let interfaceCounter = 0;
|
|
298
|
+
for (const declaration of declarations) {
|
|
299
|
+
const interfaceName = `${packagePrefix}ElementProps${interfaceCounter + 1}`;
|
|
300
|
+
interfaceCounter += 1;
|
|
301
|
+
const props = buildElementProps(declaration).map((line) => ` ${line}`);
|
|
302
|
+
const code = [` interface ${interfaceName} {`, ...props, ' }'].join('\n');
|
|
303
|
+
for (const tagName of normalizeTagNames(declaration.tagName)) {
|
|
304
|
+
if (!interfacesByTag.has(tagName)) {
|
|
305
|
+
interfacesByTag.set(tagName, { interfaceName, code, tagName });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const elementInterfaces = [...interfacesByTag.values()].sort((a, b) => a.tagName.localeCompare(b.tagName));
|
|
310
|
+
const intrinsicEntries = elementInterfaces.map((entry) => ` '${entry.tagName}': ${entry.interfaceName};`);
|
|
311
|
+
return [
|
|
312
|
+
'/**',
|
|
313
|
+
' * AUTO-GENERATED FILE - DO NOT EDIT.',
|
|
314
|
+
` * Source package: ${packageName}`,
|
|
315
|
+
' * Generated from custom-elements manifest.',
|
|
316
|
+
' */',
|
|
317
|
+
'',
|
|
318
|
+
"declare module 'react/jsx-runtime' {",
|
|
319
|
+
' namespace JSX {',
|
|
320
|
+
...elementInterfaces.map((entry) => entry.code),
|
|
321
|
+
' interface IntrinsicElements {',
|
|
322
|
+
...intrinsicEntries,
|
|
323
|
+
' }',
|
|
324
|
+
' }',
|
|
325
|
+
'}',
|
|
326
|
+
'',
|
|
327
|
+
'export {};',
|
|
328
|
+
'',
|
|
329
|
+
].join('\n');
|
|
330
|
+
}
|
|
331
|
+
function fileExists(path) {
|
|
332
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
333
|
+
try {
|
|
334
|
+
yield (0, promises_1.stat)(path);
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
catch (_a) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
function generateReactJsxTypes(cwd) {
|
|
343
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
344
|
+
const packageJsonPath = (0, node_path_1.resolve)(cwd, 'package.json');
|
|
345
|
+
if (!(yield fileExists(packageJsonPath))) {
|
|
346
|
+
return { generated: false, reason: 'No package.json found.' };
|
|
347
|
+
}
|
|
348
|
+
const packageJson = JSON.parse(yield (0, promises_1.readFile)(packageJsonPath, 'utf8'));
|
|
349
|
+
const manifestPath = getCEMManifestPath(cwd, packageJson);
|
|
350
|
+
if (!(yield fileExists(manifestPath))) {
|
|
351
|
+
return { generated: false, reason: 'No custom elements manifest found.' };
|
|
352
|
+
}
|
|
353
|
+
const manifest = JSON.parse(yield (0, promises_1.readFile)(manifestPath, 'utf8'));
|
|
354
|
+
const declarations = collectCustomElements(manifest);
|
|
355
|
+
if (!declarations.length) {
|
|
356
|
+
return { generated: false, reason: 'No custom elements discovered in manifest.' };
|
|
357
|
+
}
|
|
358
|
+
const dtsRoot = (0, node_path_1.resolve)(cwd, 'dist/dts');
|
|
359
|
+
const outputPath = (0, node_path_1.resolve)(dtsRoot, GENERATED_FILE_NAME);
|
|
360
|
+
yield (0, promises_1.mkdir)((0, node_path_1.dirname)(outputPath), { recursive: true });
|
|
361
|
+
const packageName = typeof packageJson.name === 'string' ? packageJson.name : 'unknown-package';
|
|
362
|
+
const content = generateDeclarations(packageName, declarations);
|
|
363
|
+
yield (0, promises_1.writeFile)(outputPath, content, 'utf8');
|
|
364
|
+
const rootTypesPath = (0, node_path_1.resolve)(cwd, 'dist/dts/index.d.ts');
|
|
365
|
+
if (yield fileExists(rootTypesPath)) {
|
|
366
|
+
const indexTypes = yield (0, promises_1.readFile)(rootTypesPath, 'utf8');
|
|
367
|
+
const referenceLine = `/// <reference path="./${GENERATED_FILE_NAME}" />`;
|
|
368
|
+
if (!indexTypes.includes(referenceLine)) {
|
|
369
|
+
yield (0, promises_1.writeFile)(rootTypesPath, `${referenceLine}\n${indexTypes}`, 'utf8');
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return { generated: true, path: outputPath };
|
|
373
|
+
});
|
|
374
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/ts-builder",
|
|
3
3
|
"description": "Typescript builder",
|
|
4
|
-
"version": "14.417.0",
|
|
4
|
+
"version": "14.417.1-alpha-efccb2a.0",
|
|
5
5
|
"license": "SEE LICENSE IN license.txt",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@custom-elements-manifest/analyzer": "^0.8.2",
|
|
20
|
-
"@genesislcap/build-kit": "14.417.0",
|
|
20
|
+
"@genesislcap/build-kit": "14.417.1-alpha-efccb2a.0",
|
|
21
21
|
"consola": "^3.0.2",
|
|
22
22
|
"copyfiles": "^2.4.1",
|
|
23
23
|
"pkg-types": "^1.0.2"
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"publishConfig": {
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "00d0d357de82849ea9dc574f615ba69d71b78ede"
|
|
34
34
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { rm, run, resolveBin } from '@genesislcap/build-kit';
|
|
2
2
|
import type { BuildContext } from '@genesislcap/build-kit';
|
|
3
3
|
import consola from 'consola';
|
|
4
|
+
import { generateReactJsxTypes } from './react-jsx-generator';
|
|
4
5
|
|
|
5
6
|
export default async (ctx: BuildContext) => {
|
|
6
7
|
const {
|
|
@@ -42,6 +43,13 @@ export default async (ctx: BuildContext) => {
|
|
|
42
43
|
const cem = await resolveBin('cem', '@custom-elements-manifest/analyzer');
|
|
43
44
|
run(cwd, `${cem} analyze --config custom-elements-manifest.config.*js`);
|
|
44
45
|
}
|
|
46
|
+
|
|
47
|
+
const reactTypesResult = await generateReactJsxTypes(cwd);
|
|
48
|
+
if (reactTypesResult.generated) {
|
|
49
|
+
consola.log(`\nReact JSX types generated: ${reactTypesResult.path}`);
|
|
50
|
+
} else {
|
|
51
|
+
consola.debug(`React JSX types were skipped: ${reactTypesResult.reason}`);
|
|
52
|
+
}
|
|
45
53
|
} else {
|
|
46
54
|
throw new Error(`Unrecognized command: ${JSON.stringify(options)}`);
|
|
47
55
|
}
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
type CEMType = { text?: string };
|
|
5
|
+
type CEMEvent = { name?: string; type?: CEMType };
|
|
6
|
+
type CEMMember = {
|
|
7
|
+
name?: string;
|
|
8
|
+
fieldName?: string;
|
|
9
|
+
attribute?: string | null;
|
|
10
|
+
kind?: string;
|
|
11
|
+
privacy?: string;
|
|
12
|
+
type?: CEMType;
|
|
13
|
+
};
|
|
14
|
+
type CEMDeclaration = {
|
|
15
|
+
customElement?: boolean;
|
|
16
|
+
tagName?: string;
|
|
17
|
+
members?: CEMMember[];
|
|
18
|
+
attributes?: CEMMember[];
|
|
19
|
+
events?: CEMEvent[];
|
|
20
|
+
};
|
|
21
|
+
type CEMModule = {
|
|
22
|
+
declarations?: CEMDeclaration[];
|
|
23
|
+
};
|
|
24
|
+
type CEMManifest = {
|
|
25
|
+
modules?: CEMModule[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const GENERATED_FILE_NAME = 'react-jsx-runtime.d.ts';
|
|
29
|
+
|
|
30
|
+
const primitiveUnionRegex =
|
|
31
|
+
/^(?:\s*(?:string|number|boolean|bigint|null|undefined|unknown|any|void|'[^']*'|"[^"]*"|`[^`]*`|(?:\d+(?:\.\d+)?))\s*)(?:\|\s*(?:string|number|boolean|bigint|null|undefined|unknown|any|void|'[^']*'|"[^"]*"|`[^`]*`|(?:\d+(?:\.\d+)?))\s*)*$/;
|
|
32
|
+
|
|
33
|
+
const allowedTypeIdentifierRegex = /^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*$/;
|
|
34
|
+
const allowedTypeNameSet = new Set([
|
|
35
|
+
'Array',
|
|
36
|
+
'ReadonlyArray',
|
|
37
|
+
'Promise',
|
|
38
|
+
'Record',
|
|
39
|
+
'Partial',
|
|
40
|
+
'Required',
|
|
41
|
+
'Pick',
|
|
42
|
+
'Omit',
|
|
43
|
+
'Map',
|
|
44
|
+
'Set',
|
|
45
|
+
'WeakMap',
|
|
46
|
+
'WeakSet',
|
|
47
|
+
'Date',
|
|
48
|
+
'RegExp',
|
|
49
|
+
'Error',
|
|
50
|
+
'Node',
|
|
51
|
+
'Element',
|
|
52
|
+
'HTMLElement',
|
|
53
|
+
'SVGElement',
|
|
54
|
+
'Event',
|
|
55
|
+
'CustomEvent',
|
|
56
|
+
'MouseEvent',
|
|
57
|
+
'KeyboardEvent',
|
|
58
|
+
'FocusEvent',
|
|
59
|
+
'InputEvent',
|
|
60
|
+
'PointerEvent',
|
|
61
|
+
'WheelEvent',
|
|
62
|
+
'DragEvent',
|
|
63
|
+
'SubmitEvent',
|
|
64
|
+
'AbortSignal',
|
|
65
|
+
'DOMRect',
|
|
66
|
+
'Document',
|
|
67
|
+
'Window',
|
|
68
|
+
'URL',
|
|
69
|
+
'URLSearchParams',
|
|
70
|
+
'CSSStyleDeclaration',
|
|
71
|
+
'Intl.Locale',
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
function normalizeWhitespace(value: string): string {
|
|
75
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function toTypeNameSegment(value: string): string {
|
|
79
|
+
const pascal = toPascalCase(value);
|
|
80
|
+
if (!pascal) {
|
|
81
|
+
return 'Pkg';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (/^[0-9]/.test(pascal)) {
|
|
85
|
+
return `Pkg${pascal}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return pascal;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getPackageTypePrefix(packageName: string): string {
|
|
92
|
+
const withoutScope = packageName.replace(/^@/, '').replace(/\//g, '-');
|
|
93
|
+
const segment = toTypeNameSegment(withoutScope);
|
|
94
|
+
return `${segment}React`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizePropertyName(name: string): string | null {
|
|
98
|
+
let normalized = name.trim();
|
|
99
|
+
if (!normalized) return null;
|
|
100
|
+
|
|
101
|
+
const bracketQuotedMatch = normalized.match(/^\[\s*['"](.+?)['"]\s*\]$/);
|
|
102
|
+
if (bracketQuotedMatch) {
|
|
103
|
+
normalized = bracketQuotedMatch[1];
|
|
104
|
+
} else {
|
|
105
|
+
const bracketMatch = normalized.match(/^\[\s*(.+?)\s*\]$/);
|
|
106
|
+
if (bracketMatch) {
|
|
107
|
+
normalized = bracketMatch[1];
|
|
108
|
+
}
|
|
109
|
+
const quotedMatch = normalized.match(/^['"](.+?)['"]$/);
|
|
110
|
+
if (quotedMatch) {
|
|
111
|
+
normalized = quotedMatch[1];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
normalized = normalized.trim();
|
|
116
|
+
if (!normalized) return null;
|
|
117
|
+
if (normalized.includes('\n') || normalized.includes('\r')) return null;
|
|
118
|
+
return normalized;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function escapeSingleQuotes(value: string): string {
|
|
122
|
+
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isKnownTypeIdentifier(identifier: string): boolean {
|
|
126
|
+
if (allowedTypeNameSet.has(identifier)) return true;
|
|
127
|
+
if (allowedTypeIdentifierRegex.test(identifier) && identifier.startsWith('globalThis.')) return true;
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function canUseComplexType(typeText: string): boolean {
|
|
132
|
+
if (!typeText) return false;
|
|
133
|
+
if (/[{};=]/.test(typeText)) return false;
|
|
134
|
+
if (/=>/.test(typeText)) return false;
|
|
135
|
+
if (!/^[A-Za-z0-9_$<>\[\]\(\)\|&,.?'"`\s:-]+$/.test(typeText)) return false;
|
|
136
|
+
|
|
137
|
+
const identifierMatches = typeText.match(/[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*/g) ?? [];
|
|
138
|
+
for (const match of identifierMatches) {
|
|
139
|
+
if (
|
|
140
|
+
match === 'true' ||
|
|
141
|
+
match === 'false' ||
|
|
142
|
+
match === 'null' ||
|
|
143
|
+
match === 'undefined' ||
|
|
144
|
+
match === 'string' ||
|
|
145
|
+
match === 'number' ||
|
|
146
|
+
match === 'boolean' ||
|
|
147
|
+
match === 'bigint' ||
|
|
148
|
+
match === 'symbol' ||
|
|
149
|
+
match === 'unknown' ||
|
|
150
|
+
match === 'any' ||
|
|
151
|
+
match === 'void' ||
|
|
152
|
+
match === 'never'
|
|
153
|
+
) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!isKnownTypeIdentifier(match)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function toPascalCase(value: string): string {
|
|
166
|
+
return value
|
|
167
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
168
|
+
.filter(Boolean)
|
|
169
|
+
.map((part) => `${part[0].toUpperCase()}${part.slice(1)}`)
|
|
170
|
+
.join('');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function toCamelCase(value: string): string {
|
|
174
|
+
const pascal = toPascalCase(value);
|
|
175
|
+
if (!pascal) return '';
|
|
176
|
+
return `${pascal[0].toLowerCase()}${pascal.slice(1)}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function toSafeType(typeText?: string): string {
|
|
180
|
+
if (!typeText) return 'unknown';
|
|
181
|
+
const normalized = normalizeWhitespace(typeText);
|
|
182
|
+
if (!normalized) return 'unknown';
|
|
183
|
+
|
|
184
|
+
if (primitiveUnionRegex.test(normalized)) {
|
|
185
|
+
return normalized;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (normalized.endsWith('[]')) {
|
|
189
|
+
const itemType = normalized.slice(0, -2).trim();
|
|
190
|
+
if (primitiveUnionRegex.test(itemType)) {
|
|
191
|
+
return `${itemType}[]`;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (canUseComplexType(normalized)) {
|
|
196
|
+
return normalized;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return 'unknown';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function getCEMManifestPath(cwd: string, packageJson: Record<string, unknown>): string {
|
|
203
|
+
if (typeof packageJson.customElements === 'string' && packageJson.customElements.trim().length > 0) {
|
|
204
|
+
return resolve(cwd, packageJson.customElements);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return resolve(cwd, 'dist/custom-elements.json');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function collectCustomElements(manifest: CEMManifest): CEMDeclaration[] {
|
|
211
|
+
const elements: CEMDeclaration[] = [];
|
|
212
|
+
|
|
213
|
+
for (const moduleDefinition of manifest.modules ?? []) {
|
|
214
|
+
for (const declaration of moduleDefinition.declarations ?? []) {
|
|
215
|
+
if (declaration.customElement && declaration.tagName) {
|
|
216
|
+
elements.push(declaration);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return elements;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function normalizeTagNames(tagName: string): string[] {
|
|
225
|
+
const prefixToken = '%%prefix%%-';
|
|
226
|
+
if (!tagName.includes(prefixToken)) {
|
|
227
|
+
return [tagName];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const suffix = tagName.replace(prefixToken, '');
|
|
231
|
+
return [`rapid-${suffix}`, `zero-${suffix}`];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function toEventHandlerType(typeText?: string): string {
|
|
235
|
+
if (!typeText) {
|
|
236
|
+
return '(event: CustomEvent<unknown>) => void';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const normalized = typeText.trim();
|
|
240
|
+
if (!normalized) {
|
|
241
|
+
return '(event: CustomEvent<unknown>) => void';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (normalized === 'Event') {
|
|
245
|
+
return '(event: Event) => void';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (normalized.startsWith('CustomEvent<')) {
|
|
249
|
+
const match = normalized.match(/^CustomEvent<(.+)>$/);
|
|
250
|
+
if (!match) return '(event: CustomEvent<unknown>) => void';
|
|
251
|
+
const detailType = toSafeType(match[1]?.trim());
|
|
252
|
+
return `(event: CustomEvent<${detailType}>) => void`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (normalized === 'CustomEvent') {
|
|
256
|
+
return '(event: CustomEvent<unknown>) => void';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return '(event: CustomEvent<unknown>) => void';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function buildElementProps(declaration: CEMDeclaration): string[] {
|
|
263
|
+
const propertyMap = new Map<string, string>();
|
|
264
|
+
const eventMap = new Map<string, string>();
|
|
265
|
+
|
|
266
|
+
const addProperty = (name: string, typeText?: string) => {
|
|
267
|
+
if (!name) return;
|
|
268
|
+
const normalizedName = normalizePropertyName(name);
|
|
269
|
+
if (!normalizedName) return;
|
|
270
|
+
const safeType = toSafeType(typeText);
|
|
271
|
+
if (!propertyMap.has(normalizedName)) {
|
|
272
|
+
propertyMap.set(normalizedName, safeType);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
for (const attribute of declaration.attributes ?? []) {
|
|
277
|
+
// Prefer React-friendly property casing when available.
|
|
278
|
+
if (attribute.fieldName) {
|
|
279
|
+
addProperty(attribute.fieldName, attribute.type?.text);
|
|
280
|
+
} else if (attribute.name) {
|
|
281
|
+
addProperty(attribute.name, attribute.type?.text);
|
|
282
|
+
if (attribute.name.includes('-')) {
|
|
283
|
+
const camelName = toCamelCase(attribute.name);
|
|
284
|
+
if (camelName) {
|
|
285
|
+
addProperty(camelName, attribute.type?.text);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Only include public fields that are reflected as attributes.
|
|
292
|
+
for (const member of declaration.members ?? []) {
|
|
293
|
+
if (
|
|
294
|
+
member.kind === 'field' &&
|
|
295
|
+
member.privacy !== 'private' &&
|
|
296
|
+
member.privacy !== 'protected' &&
|
|
297
|
+
member.attribute &&
|
|
298
|
+
member.name &&
|
|
299
|
+
!member.name.startsWith('_') &&
|
|
300
|
+
!member.name.startsWith('$')
|
|
301
|
+
) {
|
|
302
|
+
// React canonical typing should expose property name (camelCase).
|
|
303
|
+
addProperty(member.name, member.type?.text);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
for (const event of declaration.events ?? []) {
|
|
308
|
+
if (!event.name) continue;
|
|
309
|
+
const normalizedEventName = normalizePropertyName(event.name);
|
|
310
|
+
if (!normalizedEventName) continue;
|
|
311
|
+
const eventHandlerType = toEventHandlerType(event.type?.text);
|
|
312
|
+
const pascalName = toPascalCase(normalizedEventName);
|
|
313
|
+
if (pascalName) {
|
|
314
|
+
eventMap.set(`on${pascalName}`, eventHandlerType);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const lines: string[] = [
|
|
319
|
+
'children?: unknown;',
|
|
320
|
+
'class?: string;',
|
|
321
|
+
'className?: string;',
|
|
322
|
+
'id?: string;',
|
|
323
|
+
'slot?: string;',
|
|
324
|
+
'part?: string;',
|
|
325
|
+
'style?: string | Partial<CSSStyleDeclaration>;',
|
|
326
|
+
'role?: string;',
|
|
327
|
+
'tabIndex?: number | string;',
|
|
328
|
+
'[key: `data-${string}`]: string | number | boolean | undefined;',
|
|
329
|
+
'[key: `aria-${string}`]: string | number | boolean | undefined;',
|
|
330
|
+
];
|
|
331
|
+
|
|
332
|
+
for (const [name, typeText] of propertyMap) {
|
|
333
|
+
lines.push(`'${escapeSingleQuotes(name)}'?: ${typeText};`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
for (const [name, handlerType] of eventMap) {
|
|
337
|
+
lines.push(`'${escapeSingleQuotes(name)}'?: ${handlerType};`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return lines;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function generateDeclarations(packageName: string, declarations: CEMDeclaration[]): string {
|
|
344
|
+
const packagePrefix = getPackageTypePrefix(packageName);
|
|
345
|
+
const interfacesByTag = new Map<
|
|
346
|
+
string,
|
|
347
|
+
{ interfaceName: string; code: string; tagName: string }
|
|
348
|
+
>();
|
|
349
|
+
let interfaceCounter = 0;
|
|
350
|
+
|
|
351
|
+
for (const declaration of declarations) {
|
|
352
|
+
const interfaceName = `${packagePrefix}ElementProps${interfaceCounter + 1}`;
|
|
353
|
+
interfaceCounter += 1;
|
|
354
|
+
const props = buildElementProps(declaration).map((line) => ` ${line}`);
|
|
355
|
+
const code = [` interface ${interfaceName} {`, ...props, ' }'].join('\n');
|
|
356
|
+
for (const tagName of normalizeTagNames(declaration.tagName!)) {
|
|
357
|
+
if (!interfacesByTag.has(tagName)) {
|
|
358
|
+
interfacesByTag.set(tagName, { interfaceName, code, tagName });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const elementInterfaces = [...interfacesByTag.values()].sort((a, b) =>
|
|
364
|
+
a.tagName.localeCompare(b.tagName),
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const intrinsicEntries = elementInterfaces.map(
|
|
368
|
+
(entry) => ` '${entry.tagName}': ${entry.interfaceName};`,
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
return [
|
|
372
|
+
'/**',
|
|
373
|
+
' * AUTO-GENERATED FILE - DO NOT EDIT.',
|
|
374
|
+
` * Source package: ${packageName}`,
|
|
375
|
+
' * Generated from custom-elements manifest.',
|
|
376
|
+
' */',
|
|
377
|
+
'',
|
|
378
|
+
"declare module 'react/jsx-runtime' {",
|
|
379
|
+
' namespace JSX {',
|
|
380
|
+
...elementInterfaces.map((entry) => entry.code),
|
|
381
|
+
' interface IntrinsicElements {',
|
|
382
|
+
...intrinsicEntries,
|
|
383
|
+
' }',
|
|
384
|
+
' }',
|
|
385
|
+
'}',
|
|
386
|
+
'',
|
|
387
|
+
'export {};',
|
|
388
|
+
'',
|
|
389
|
+
].join('\n');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function fileExists(path: string): Promise<boolean> {
|
|
393
|
+
try {
|
|
394
|
+
await stat(path);
|
|
395
|
+
return true;
|
|
396
|
+
} catch {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export async function generateReactJsxTypes(
|
|
402
|
+
cwd: string,
|
|
403
|
+
): Promise<{ generated: boolean; path?: string; reason?: string }> {
|
|
404
|
+
const packageJsonPath = resolve(cwd, 'package.json');
|
|
405
|
+
if (!(await fileExists(packageJsonPath))) {
|
|
406
|
+
return { generated: false, reason: 'No package.json found.' };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')) as Record<string, unknown>;
|
|
410
|
+
const manifestPath = getCEMManifestPath(cwd, packageJson);
|
|
411
|
+
if (!(await fileExists(manifestPath))) {
|
|
412
|
+
return { generated: false, reason: 'No custom elements manifest found.' };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as CEMManifest;
|
|
416
|
+
const declarations = collectCustomElements(manifest);
|
|
417
|
+
if (!declarations.length) {
|
|
418
|
+
return { generated: false, reason: 'No custom elements discovered in manifest.' };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const dtsRoot = resolve(cwd, 'dist/dts');
|
|
422
|
+
const outputPath = resolve(dtsRoot, GENERATED_FILE_NAME);
|
|
423
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
424
|
+
|
|
425
|
+
const packageName = typeof packageJson.name === 'string' ? packageJson.name : 'unknown-package';
|
|
426
|
+
const content = generateDeclarations(packageName, declarations);
|
|
427
|
+
await writeFile(outputPath, content, 'utf8');
|
|
428
|
+
|
|
429
|
+
const rootTypesPath = resolve(cwd, 'dist/dts/index.d.ts');
|
|
430
|
+
if (await fileExists(rootTypesPath)) {
|
|
431
|
+
const indexTypes = await readFile(rootTypesPath, 'utf8');
|
|
432
|
+
const referenceLine = `/// <reference path="./${GENERATED_FILE_NAME}" />`;
|
|
433
|
+
if (!indexTypes.includes(referenceLine)) {
|
|
434
|
+
await writeFile(rootTypesPath, `${referenceLine}\n${indexTypes}`, 'utf8');
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return { generated: true, path: outputPath };
|
|
439
|
+
}
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/index.ts"],"version":"5.9.2"}
|
|
1
|
+
{"root":["./src/index.ts","./src/react-jsx-generator.ts"],"version":"5.9.2"}
|