@openwebf/webf 0.23.10 ā 0.24.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/commands.js +135 -7
- package/dist/generator.js +37 -36
- package/dist/module.js +43 -14
- package/dist/peerDeps.js +27 -0
- package/dist/react.js +10 -18
- package/dist/vue.js +138 -132
- package/package.json +1 -1
- package/src/commands.ts +153 -7
- package/src/generator.ts +38 -37
- package/src/module.ts +53 -21
- package/src/peerDeps.ts +21 -0
- package/src/react.ts +10 -18
- package/src/vue.ts +157 -142
- package/templates/react.component.tsx.tpl +2 -2
- package/templates/react.index.ts.tpl +2 -1
- package/templates/react.package.json.tpl +4 -5
- package/templates/react.tsconfig.json.tpl +8 -1
- package/templates/react.tsup.config.ts.tpl +1 -1
- package/templates/vue.component.partial.tpl +4 -4
- package/templates/vue.components.d.ts.tpl +24 -9
- package/templates/vue.package.json.tpl +4 -2
- package/test/commands.test.ts +77 -12
- package/test/generator.test.ts +17 -10
- package/test/peerDeps.test.ts +30 -0
- package/test/react-consts.test.ts +9 -3
- package/test/standard-props.test.ts +14 -14
- package/test/templates.test.ts +17 -0
- package/test/vue.test.ts +36 -11
- package/dist/constants.js +0 -242
package/dist/vue.js
CHANGED
|
@@ -8,7 +8,6 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const declaration_1 = require("./declaration");
|
|
11
|
-
const logger_1 = require("./logger");
|
|
12
11
|
const utils_1 = require("./utils");
|
|
13
12
|
function readTemplate(name) {
|
|
14
13
|
return fs_1.default.readFileSync(path_1.default.join(__dirname, '../templates/' + name + '.tpl'), { encoding: 'utf-8' });
|
|
@@ -50,19 +49,18 @@ function generateReturnType(type) {
|
|
|
50
49
|
return 'any';
|
|
51
50
|
if (typeof pointerType === 'string' && pointerType.startsWith('typeof ')) {
|
|
52
51
|
const ident = pointerType.substring('typeof '.length).trim();
|
|
53
|
-
return `typeof
|
|
52
|
+
return `typeof ${ident}`;
|
|
54
53
|
}
|
|
55
54
|
return pointerType;
|
|
56
55
|
}
|
|
57
56
|
if (type.isArray && typeof type.value === 'object' && !Array.isArray(type.value)) {
|
|
58
|
-
const elemType = (
|
|
59
|
-
if (elemType
|
|
57
|
+
const elemType = generateReturnType(type.value);
|
|
58
|
+
if (!elemType)
|
|
60
59
|
return 'any[]';
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
return `(typeof __webfTypes.${ident})[]`;
|
|
60
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(elemType)) {
|
|
61
|
+
return `${elemType}[]`;
|
|
64
62
|
}
|
|
65
|
-
return
|
|
63
|
+
return `(${elemType})[]`;
|
|
66
64
|
}
|
|
67
65
|
switch (type.value) {
|
|
68
66
|
case declaration_1.FunctionArgumentType.int:
|
|
@@ -111,125 +109,111 @@ function generateMethodDeclaration(method) {
|
|
|
111
109
|
var returnType = generateReturnType(method.returnType);
|
|
112
110
|
return `${methodName}(${args}): ${returnType};`;
|
|
113
111
|
}
|
|
114
|
-
function
|
|
115
|
-
const classObjects = blob.objects;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
112
|
+
function getVueComponentSpecs(blob) {
|
|
113
|
+
const classObjects = blob.objects.filter(obj => obj instanceof declaration_1.ClassObject);
|
|
114
|
+
if (classObjects.length === 0)
|
|
115
|
+
return [];
|
|
116
|
+
const properties = classObjects.filter(object => object.name.endsWith('Properties'));
|
|
117
|
+
const events = classObjects.filter(object => object.name.endsWith('Events'));
|
|
118
|
+
const methods = classObjects.filter(object => object.name.endsWith('Methods'));
|
|
119
|
+
const componentMap = new Map();
|
|
120
|
+
properties.forEach(prop => {
|
|
121
|
+
const className = prop.name.replace(/Properties$/, '');
|
|
122
|
+
if (!componentMap.has(className))
|
|
123
|
+
componentMap.set(className, { className });
|
|
124
|
+
componentMap.get(className).properties = prop;
|
|
125
125
|
});
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
events.forEach(ev => {
|
|
127
|
+
const className = ev.name.replace(/Events$/, '');
|
|
128
|
+
if (!componentMap.has(className))
|
|
129
|
+
componentMap.set(className, { className });
|
|
130
|
+
componentMap.get(className).events = ev;
|
|
128
131
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
+
methods.forEach(m => {
|
|
133
|
+
const className = m.name.replace(/Methods$/, '');
|
|
134
|
+
if (!componentMap.has(className))
|
|
135
|
+
componentMap.set(className, { className });
|
|
136
|
+
componentMap.get(className).methods = m;
|
|
132
137
|
});
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
return Array.from(componentMap.values())
|
|
139
|
+
.filter(spec => spec.className.trim().length > 0)
|
|
140
|
+
.sort((a, b) => a.className.localeCompare(b.className));
|
|
141
|
+
}
|
|
142
|
+
function renderSupportingInterface(object) {
|
|
143
|
+
const hasProps = !!object.props && object.props.length > 0;
|
|
144
|
+
const hasMethods = !!object.methods && object.methods.length > 0;
|
|
145
|
+
if (!hasProps && !hasMethods) {
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
148
|
+
const interfaceLines = [];
|
|
149
|
+
if (object.documentation && object.documentation.trim().length > 0) {
|
|
150
|
+
interfaceLines.push('/**');
|
|
151
|
+
object.documentation.split('\n').forEach(line => {
|
|
152
|
+
interfaceLines.push(` * ${line}`);
|
|
153
|
+
});
|
|
154
|
+
interfaceLines.push(' */');
|
|
155
|
+
}
|
|
156
|
+
interfaceLines.push(`export interface ${object.name} {`);
|
|
157
|
+
const propLines = (object.props || []).map(prop => {
|
|
158
|
+
const lines = [];
|
|
159
|
+
if (prop.documentation && prop.documentation.trim().length > 0) {
|
|
160
|
+
lines.push(' /**');
|
|
161
|
+
prop.documentation.split('\n').forEach(line => {
|
|
162
|
+
lines.push(` * ${line}`);
|
|
142
163
|
});
|
|
143
|
-
|
|
164
|
+
lines.push(' */');
|
|
144
165
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
lines.push(' */');
|
|
154
|
-
}
|
|
155
|
-
const optionalToken = prop.optional ? '?' : '';
|
|
156
|
-
lines.push(` ${prop.name}${optionalToken}: ${generateReturnType(prop.type)};`);
|
|
157
|
-
return lines.join('\n');
|
|
166
|
+
const optionalToken = prop.optional ? '?' : '';
|
|
167
|
+
lines.push(` ${prop.name}${optionalToken}: ${generateReturnType(prop.type)};`);
|
|
168
|
+
return lines.join('\n');
|
|
169
|
+
});
|
|
170
|
+
interfaceLines.push(propLines.join('\n'));
|
|
171
|
+
if (object.methods && object.methods.length > 0) {
|
|
172
|
+
const methodLines = object.methods.map(method => {
|
|
173
|
+
return ` ${generateMethodDeclaration(method)}`;
|
|
158
174
|
});
|
|
159
|
-
interfaceLines.push(
|
|
160
|
-
interfaceLines.push('}');
|
|
161
|
-
return interfaceLines.join('\n');
|
|
162
|
-
}).filter(dep => dep.trim() !== '').join('\n\n');
|
|
163
|
-
const componentProperties = properties.length > 0 ? properties[0] : undefined;
|
|
164
|
-
const componentEvents = events.length > 0 ? events[0] : undefined;
|
|
165
|
-
const className = (() => {
|
|
166
|
-
if (componentProperties) {
|
|
167
|
-
return componentProperties.name.replace(/Properties$/, '');
|
|
168
|
-
}
|
|
169
|
-
if (componentEvents) {
|
|
170
|
-
return componentEvents.name.replace(/Events$/, '');
|
|
171
|
-
}
|
|
172
|
-
return '';
|
|
173
|
-
})();
|
|
174
|
-
if (!className) {
|
|
175
|
-
return '';
|
|
175
|
+
interfaceLines.push(methodLines.join('\n'));
|
|
176
176
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
properties: componentProperties,
|
|
180
|
-
events: componentEvents,
|
|
181
|
-
classObjectDictionary,
|
|
182
|
-
dependencies,
|
|
183
|
-
blob,
|
|
184
|
-
generateReturnType,
|
|
185
|
-
generateMethodDeclaration,
|
|
186
|
-
generateEventHandlerType,
|
|
187
|
-
});
|
|
188
|
-
const result = content.split('\n').filter(str => {
|
|
189
|
-
return str.trim().length > 0;
|
|
190
|
-
}).join('\n');
|
|
191
|
-
return result;
|
|
177
|
+
interfaceLines.push('}');
|
|
178
|
+
return interfaceLines.join('\n');
|
|
192
179
|
}
|
|
193
|
-
function toVueTagName(
|
|
180
|
+
function toVueTagName(rawClassName) {
|
|
181
|
+
const className = rawClassName.trim();
|
|
194
182
|
if (className.startsWith('WebF')) {
|
|
195
183
|
const withoutPrefix = className.substring(4);
|
|
196
|
-
|
|
184
|
+
const suffix = lodash_1.default.kebabCase(withoutPrefix);
|
|
185
|
+
return suffix.length > 0 ? 'webf-' + suffix : 'webf';
|
|
197
186
|
}
|
|
198
|
-
|
|
187
|
+
if (className.startsWith('Flutter')) {
|
|
199
188
|
const withoutPrefix = className.substring(7);
|
|
200
|
-
|
|
189
|
+
const suffix = lodash_1.default.kebabCase(withoutPrefix);
|
|
190
|
+
return suffix.length > 0 ? 'flutter-' + suffix : 'flutter';
|
|
201
191
|
}
|
|
202
|
-
|
|
192
|
+
const kebab = lodash_1.default.kebabCase(className);
|
|
193
|
+
return kebab.replace(/^web-f-/, 'webf-');
|
|
203
194
|
}
|
|
204
195
|
function generateVueTypings(blobs) {
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return object.name.endsWith('Events');
|
|
212
|
-
});
|
|
213
|
-
const componentProperties = properties.length > 0 ? properties[0] : undefined;
|
|
214
|
-
const componentEvents = events.length > 0 ? events[0] : undefined;
|
|
215
|
-
const className = (() => {
|
|
216
|
-
if (componentProperties) {
|
|
217
|
-
return componentProperties.name.replace(/Properties$/, '');
|
|
218
|
-
}
|
|
219
|
-
if (componentEvents) {
|
|
220
|
-
return componentEvents.name.replace(/Events$/, '');
|
|
221
|
-
}
|
|
222
|
-
return '';
|
|
223
|
-
})();
|
|
224
|
-
return className;
|
|
225
|
-
}).filter(name => {
|
|
226
|
-
return name.length > 0;
|
|
196
|
+
const componentSpecMap = new Map();
|
|
197
|
+
blobs
|
|
198
|
+
.flatMap(blob => getVueComponentSpecs(blob))
|
|
199
|
+
.forEach(spec => {
|
|
200
|
+
if (!componentSpecMap.has(spec.className))
|
|
201
|
+
componentSpecMap.set(spec.className, spec);
|
|
227
202
|
});
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
203
|
+
const componentSpecs = Array.from(componentSpecMap.values()).sort((a, b) => a.className.localeCompare(b.className));
|
|
204
|
+
const componentNames = componentSpecs.map(spec => spec.className);
|
|
205
|
+
const components = componentSpecs.map(spec => {
|
|
206
|
+
const content = lodash_1.default.template(readTemplate('vue.component.partial'))({
|
|
207
|
+
className: spec.className,
|
|
208
|
+
properties: spec.properties,
|
|
209
|
+
events: spec.events,
|
|
210
|
+
methods: spec.methods,
|
|
211
|
+
generateReturnType,
|
|
212
|
+
generateMethodDeclaration,
|
|
213
|
+
generateEventHandlerType,
|
|
214
|
+
});
|
|
215
|
+
return content.split('\n').filter(str => str.trim().length > 0).join('\n');
|
|
216
|
+
}).filter(Boolean).join('\n\n');
|
|
233
217
|
// Collect declare consts across blobs and render as exported ambient declarations
|
|
234
218
|
const consts = blobs
|
|
235
219
|
.flatMap(blob => blob.objects)
|
|
@@ -241,34 +225,55 @@ function generateVueTypings(blobs) {
|
|
|
241
225
|
uniqueConsts.set(c.name, c);
|
|
242
226
|
});
|
|
243
227
|
const constDeclarations = Array.from(uniqueConsts.values())
|
|
228
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
244
229
|
.map(c => `export declare const ${c.name}: ${c.type};`)
|
|
245
230
|
.join('\n');
|
|
246
231
|
// Collect declare enums across blobs
|
|
247
232
|
const enums = blobs
|
|
248
233
|
.flatMap(blob => blob.objects)
|
|
249
234
|
.filter(obj => obj instanceof declaration_1.EnumObject);
|
|
250
|
-
const
|
|
235
|
+
const uniqueEnums = new Map();
|
|
236
|
+
enums.forEach(e => {
|
|
237
|
+
if (!uniqueEnums.has(e.name))
|
|
238
|
+
uniqueEnums.set(e.name, e);
|
|
239
|
+
});
|
|
240
|
+
const enumDeclarations = Array.from(uniqueEnums.values())
|
|
241
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
242
|
+
.map(e => {
|
|
251
243
|
const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
|
|
252
244
|
return `export declare enum ${e.name} { ${members} }`;
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
245
|
+
})
|
|
246
|
+
.join('\n');
|
|
247
|
+
// Collect type aliases across blobs and render as exported declarations.
|
|
248
|
+
const typeAliases = blobs
|
|
249
|
+
.flatMap(blob => blob.objects)
|
|
250
|
+
.filter(obj => obj instanceof declaration_1.TypeAliasObject);
|
|
251
|
+
const uniqueTypeAliases = new Map();
|
|
252
|
+
typeAliases.forEach(t => {
|
|
253
|
+
if (!uniqueTypeAliases.has(t.name))
|
|
254
|
+
uniqueTypeAliases.set(t.name, t);
|
|
255
|
+
});
|
|
256
|
+
const typeAliasDeclarations = Array.from(uniqueTypeAliases.values())
|
|
257
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
258
|
+
.map(t => `export type ${t.name} = ${t.type};`)
|
|
259
|
+
.join('\n');
|
|
260
|
+
// Collect supporting interfaces (non-component interfaces) so referenced types exist.
|
|
261
|
+
const supportingInterfaces = blobs
|
|
262
|
+
.flatMap(blob => blob.objects)
|
|
263
|
+
.filter(obj => obj instanceof declaration_1.ClassObject);
|
|
264
|
+
const supporting = supportingInterfaces.filter(obj => {
|
|
265
|
+
return !obj.name.endsWith('Properties') && !obj.name.endsWith('Events') && !obj.name.endsWith('Methods');
|
|
266
|
+
});
|
|
267
|
+
const uniqueSupporting = new Map();
|
|
268
|
+
supporting.forEach(obj => {
|
|
269
|
+
if (!uniqueSupporting.has(obj.name))
|
|
270
|
+
uniqueSupporting.set(obj.name, obj);
|
|
271
|
+
});
|
|
272
|
+
const supportingDeclarations = Array.from(uniqueSupporting.values())
|
|
273
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
274
|
+
.map(obj => renderSupportingInterface(obj))
|
|
275
|
+
.filter(Boolean)
|
|
276
|
+
.join('\n\n');
|
|
272
277
|
// Build mapping of template tag names to class names for GlobalComponents
|
|
273
278
|
const componentMetas = componentNames.map(className => ({
|
|
274
279
|
className,
|
|
@@ -284,7 +289,8 @@ function generateVueTypings(blobs) {
|
|
|
284
289
|
components,
|
|
285
290
|
consts: constDeclarations,
|
|
286
291
|
enums: enumDeclarations,
|
|
287
|
-
|
|
292
|
+
typeAliases: typeAliasDeclarations,
|
|
293
|
+
dependencies: supportingDeclarations,
|
|
288
294
|
});
|
|
289
295
|
return content.split('\n').filter(str => {
|
|
290
296
|
return str.trim().length > 0;
|
package/package.json
CHANGED
package/src/commands.ts
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import { dartGen, reactGen, vueGen } from './generator';
|
|
6
6
|
import { generateModuleArtifacts } from './module';
|
|
7
|
+
import { getPackageTypesFileFromDir, isPackageTypesReady, readJsonFile } from './peerDeps';
|
|
7
8
|
import { globSync } from 'glob';
|
|
8
9
|
import _ from 'lodash';
|
|
9
10
|
import inquirer from 'inquirer';
|
|
@@ -237,6 +238,40 @@ function readFlutterPackageMetadata(packagePath: string): FlutterPackageMetadata
|
|
|
237
238
|
}
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
function copyReadmeToPackageRoot(params: {
|
|
242
|
+
sourceRoot: string;
|
|
243
|
+
targetRoot: string;
|
|
244
|
+
}): { copied: boolean; sourcePath?: string; targetPath: string } {
|
|
245
|
+
const { sourceRoot, targetRoot } = params;
|
|
246
|
+
const targetPath = path.join(targetRoot, 'README.md');
|
|
247
|
+
|
|
248
|
+
if (fs.existsSync(targetPath)) {
|
|
249
|
+
return { copied: false, targetPath };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const candidateNames = ['README.md', 'Readme.md', 'readme.md'];
|
|
253
|
+
let sourcePath: string | null = null;
|
|
254
|
+
for (const candidate of candidateNames) {
|
|
255
|
+
const abs = path.join(sourceRoot, candidate);
|
|
256
|
+
if (fs.existsSync(abs)) {
|
|
257
|
+
sourcePath = abs;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!sourcePath) {
|
|
263
|
+
return { copied: false, targetPath };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const content = fs.readFileSync(sourcePath, 'utf-8');
|
|
268
|
+
writeFileIfChanged(targetPath, content);
|
|
269
|
+
return { copied: true, sourcePath, targetPath };
|
|
270
|
+
} catch {
|
|
271
|
+
return { copied: false, targetPath };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
240
275
|
// Copy markdown docs that match .d.ts basenames from source to the built dist folder,
|
|
241
276
|
// and generate an aggregated README.md in the dist directory.
|
|
242
277
|
async function copyMarkdownDocsToDist(params: {
|
|
@@ -441,7 +476,8 @@ function createCommand(target: string, options: { framework: string; packageName
|
|
|
441
476
|
// Leave merge to the codegen step which appends exports safely
|
|
442
477
|
}
|
|
443
478
|
|
|
444
|
-
|
|
479
|
+
// Ensure devDependencies are installed even if the user's shell has NODE_ENV=production.
|
|
480
|
+
spawnSync(NPM, ['install', '--production=false'], {
|
|
445
481
|
cwd: target,
|
|
446
482
|
stdio: 'inherit'
|
|
447
483
|
});
|
|
@@ -463,12 +499,8 @@ function createCommand(target: string, options: { framework: string; packageName
|
|
|
463
499
|
const gitignoreContent = _.template(gitignore)({});
|
|
464
500
|
writeFileIfChanged(gitignorePath, gitignoreContent);
|
|
465
501
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
stdio: 'inherit'
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
spawnSync(NPM, ['install', 'vue', '-D'], {
|
|
502
|
+
// Ensure devDependencies are installed even if the user's shell has NODE_ENV=production.
|
|
503
|
+
spawnSync(NPM, ['install', '--production=false'], {
|
|
472
504
|
cwd: target,
|
|
473
505
|
stdio: 'inherit'
|
|
474
506
|
});
|
|
@@ -781,6 +813,17 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
781
813
|
// Auto-initialize typings in the output directory if needed
|
|
782
814
|
ensureInitialized(resolvedDistPath);
|
|
783
815
|
|
|
816
|
+
// Copy README.md from the source Flutter package into the npm package root (so `npm publish` includes it).
|
|
817
|
+
if (options.flutterPackageSrc) {
|
|
818
|
+
const { copied } = copyReadmeToPackageRoot({
|
|
819
|
+
sourceRoot: options.flutterPackageSrc,
|
|
820
|
+
targetRoot: resolvedDistPath,
|
|
821
|
+
});
|
|
822
|
+
if (copied) {
|
|
823
|
+
console.log('š Copied README.md to package root');
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
784
827
|
console.log(`\nGenerating ${framework} code from ${options.flutterPackageSrc}...`);
|
|
785
828
|
|
|
786
829
|
await dartGen({
|
|
@@ -1179,6 +1222,101 @@ async function buildPackage(packagePath: string): Promise<void> {
|
|
|
1179
1222
|
const packageName = packageJson.name;
|
|
1180
1223
|
const packageVersion = packageJson.version;
|
|
1181
1224
|
|
|
1225
|
+
function getInstalledPackageJsonPath(pkgName: string): string {
|
|
1226
|
+
const parts = pkgName.split('/');
|
|
1227
|
+
return path.join(packagePath, 'node_modules', ...parts, 'package.json');
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function getInstalledPackageDir(pkgName: string): string {
|
|
1231
|
+
const parts = pkgName.split('/');
|
|
1232
|
+
return path.join(packagePath, 'node_modules', ...parts);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function findUp(startDir: string, relativePathToFind: string): string | null {
|
|
1236
|
+
let dir = path.resolve(startDir);
|
|
1237
|
+
while (true) {
|
|
1238
|
+
const candidate = path.join(dir, relativePathToFind);
|
|
1239
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
1240
|
+
const parent = path.dirname(dir);
|
|
1241
|
+
if (parent === dir) return null;
|
|
1242
|
+
dir = parent;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
function ensurePeerDependencyAvailableForBuild(peerName: string): void {
|
|
1247
|
+
const installedPkgJson = getInstalledPackageJsonPath(peerName);
|
|
1248
|
+
if (fs.existsSync(installedPkgJson)) return;
|
|
1249
|
+
|
|
1250
|
+
const peerRange = packageJson.peerDependencies?.[peerName];
|
|
1251
|
+
const localMap: Record<string, string> = {
|
|
1252
|
+
'@openwebf/react-core-ui': path.join('packages', 'react-core-ui'),
|
|
1253
|
+
'@openwebf/vue-core-ui': path.join('packages', 'vue-core-ui'),
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
let installSpec: string | null = null;
|
|
1257
|
+
|
|
1258
|
+
const localRel = localMap[peerName];
|
|
1259
|
+
if (localRel) {
|
|
1260
|
+
const localPath = findUp(process.cwd(), localRel);
|
|
1261
|
+
if (localPath) {
|
|
1262
|
+
if (!isPackageTypesReady(localPath)) {
|
|
1263
|
+
const localPkgJsonPath = path.join(localPath, 'package.json');
|
|
1264
|
+
if (fs.existsSync(localPkgJsonPath)) {
|
|
1265
|
+
const localPkgJson = readJsonFile(localPkgJsonPath);
|
|
1266
|
+
if (localPkgJson.scripts?.build) {
|
|
1267
|
+
if (process.env.WEBF_CODEGEN_BUILD_LOCAL_PEERS !== '1') {
|
|
1268
|
+
console.warn(
|
|
1269
|
+
`\nā ļø Local ${peerName} found at ${localPath} but type declarations are missing; falling back to registry install.`
|
|
1270
|
+
);
|
|
1271
|
+
} else {
|
|
1272
|
+
console.log(
|
|
1273
|
+
`\nš§ Local ${peerName} found at ${localPath} but build artifacts are missing; building it for DTS...`
|
|
1274
|
+
);
|
|
1275
|
+
const buildLocalResult = spawnSync(NPM, ['run', 'build'], {
|
|
1276
|
+
cwd: localPath,
|
|
1277
|
+
stdio: 'inherit'
|
|
1278
|
+
});
|
|
1279
|
+
if (buildLocalResult.status === 0) {
|
|
1280
|
+
if (isPackageTypesReady(localPath)) {
|
|
1281
|
+
installSpec = localPath;
|
|
1282
|
+
} else {
|
|
1283
|
+
console.warn(
|
|
1284
|
+
`\nā ļø Built local ${peerName} but type declarations are still missing; falling back to registry install.`
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
} else {
|
|
1288
|
+
console.warn(`\nā ļø Failed to build local ${peerName}; falling back to registry install.`);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
} else {
|
|
1294
|
+
installSpec = localPath;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
if (!installSpec) {
|
|
1300
|
+
installSpec = peerRange ? `${peerName}@${peerRange}` : peerName;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
console.log(`\nš¦ Installing peer dependency for build: ${peerName}...`);
|
|
1304
|
+
const installResult = spawnSync(NPM, ['install', '--no-save', installSpec], {
|
|
1305
|
+
cwd: packagePath,
|
|
1306
|
+
stdio: 'inherit'
|
|
1307
|
+
});
|
|
1308
|
+
if (installResult.status !== 0) {
|
|
1309
|
+
throw new Error(`Failed to install peer dependency for build: ${peerName}`);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
const installedTypesFile = getPackageTypesFileFromDir(getInstalledPackageDir(peerName));
|
|
1313
|
+
if (installedTypesFile && !fs.existsSync(installedTypesFile)) {
|
|
1314
|
+
throw new Error(
|
|
1315
|
+
`Peer dependency ${peerName} was installed but type declarations were not found at ${installedTypesFile}`
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1182
1320
|
// Check if node_modules exists
|
|
1183
1321
|
const nodeModulesPath = path.join(packagePath, 'node_modules');
|
|
1184
1322
|
if (!fs.existsSync(nodeModulesPath)) {
|
|
@@ -1204,6 +1342,14 @@ async function buildPackage(packagePath: string): Promise<void> {
|
|
|
1204
1342
|
|
|
1205
1343
|
// Check if package has a build script
|
|
1206
1344
|
if (packageJson.scripts?.build) {
|
|
1345
|
+
// DTS build needs peer deps present locally to resolve types (even though they are not bundled).
|
|
1346
|
+
if (packageJson.peerDependencies?.['@openwebf/react-core-ui']) {
|
|
1347
|
+
ensurePeerDependencyAvailableForBuild('@openwebf/react-core-ui');
|
|
1348
|
+
}
|
|
1349
|
+
if (packageJson.peerDependencies?.['@openwebf/vue-core-ui']) {
|
|
1350
|
+
ensurePeerDependencyAvailableForBuild('@openwebf/vue-core-ui');
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1207
1353
|
console.log(`\nBuilding ${packageName}@${packageVersion}...`);
|
|
1208
1354
|
const buildResult = spawnSync(NPM, ['run', 'build'], {
|
|
1209
1355
|
cwd: packagePath,
|
package/src/generator.ts
CHANGED
|
@@ -407,13 +407,9 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
|
|
|
407
407
|
warn(`Failed to merge into existing index.ts. Skipping modifications: ${indexFilePath}`);
|
|
408
408
|
}
|
|
409
409
|
}
|
|
410
|
-
|
|
411
|
-
timeEnd('reactGen');
|
|
412
|
-
success(`React code generation completed. ${filesChanged} files changed.`);
|
|
413
|
-
info(`Output directory: ${normalizedTarget}`);
|
|
414
|
-
info('You can now import these components in your React project.');
|
|
415
410
|
|
|
416
|
-
//
|
|
411
|
+
// Always generate src/types.ts so generated components can safely import it.
|
|
412
|
+
// When there are no standalone declarations, emit an empty module (`export {};`).
|
|
417
413
|
try {
|
|
418
414
|
const consts = blobs.flatMap(b => b.objects.filter(o => o instanceof ConstObject) as ConstObject[]);
|
|
419
415
|
const enums = blobs.flatMap(b => b.objects.filter(o => o instanceof EnumObject) as EnumObject[]);
|
|
@@ -426,40 +422,40 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
|
|
|
426
422
|
typeAliases.forEach(t => { if (!typeAliasMap.has(t.name)) typeAliasMap.set(t.name, t); });
|
|
427
423
|
|
|
428
424
|
const hasAny = constMap.size > 0 || enums.length > 0 || typeAliasMap.size > 0;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
.join('\n');
|
|
425
|
+
const constDecl = Array.from(constMap.values())
|
|
426
|
+
.map(c => `export declare const ${c.name}: ${c.type};`)
|
|
427
|
+
.join('\n');
|
|
428
|
+
const enumDecl = enums
|
|
429
|
+
.map(e => `export enum ${e.name} { ${e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ')} }`)
|
|
430
|
+
.join('\n');
|
|
431
|
+
const typeAliasDecl = Array.from(typeAliasMap.values())
|
|
432
|
+
.map(t => `export type ${t.name} = ${t.type};`)
|
|
433
|
+
.join('\n');
|
|
439
434
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
435
|
+
const typesContent = [
|
|
436
|
+
'/* Generated by WebF CLI - aggregated type declarations */',
|
|
437
|
+
hasAny ? typeAliasDecl : '',
|
|
438
|
+
hasAny ? constDecl : '',
|
|
439
|
+
hasAny ? enumDecl : '',
|
|
440
|
+
hasAny ? '' : 'export {};',
|
|
441
|
+
''
|
|
442
|
+
].filter(Boolean).join('\n');
|
|
447
443
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
444
|
+
const typesPath = path.join(normalizedTarget, 'src', 'types.ts');
|
|
445
|
+
if (writeFileIfChanged(typesPath, typesContent)) {
|
|
446
|
+
filesChanged++;
|
|
447
|
+
debug(`Generated: src/types.ts`);
|
|
448
|
+
try {
|
|
449
|
+
const constNames = Array.from(constMap.keys());
|
|
450
|
+
const aliasNames = Array.from(typeAliasMap.keys());
|
|
451
|
+
const enumNames = enums.map(e => e.name);
|
|
452
|
+
debug(`[react] Aggregated types - consts: ${constNames.join(', ') || '(none)'}; typeAliases: ${aliasNames.join(', ') || '(none)'}; enums: ${enumNames.join(', ') || '(none)'}\n`);
|
|
453
|
+
debug(`[react] src/types.ts preview:\n` + typesContent.split('\n').slice(0, 20).join('\n'));
|
|
454
|
+
} catch {}
|
|
455
|
+
}
|
|
460
456
|
|
|
461
|
-
|
|
462
|
-
|
|
457
|
+
// Only re-export from index.ts when there are actual declarations to surface.
|
|
458
|
+
if (hasAny) {
|
|
463
459
|
try {
|
|
464
460
|
let current = '';
|
|
465
461
|
if (fs.existsSync(indexFilePath)) {
|
|
@@ -478,6 +474,11 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
|
|
|
478
474
|
} catch (e) {
|
|
479
475
|
warn('Failed to generate aggregated React types');
|
|
480
476
|
}
|
|
477
|
+
|
|
478
|
+
timeEnd('reactGen');
|
|
479
|
+
success(`React code generation completed. ${filesChanged} files changed.`);
|
|
480
|
+
info(`Output directory: ${normalizedTarget}`);
|
|
481
|
+
info('You can now import these components in your React project.');
|
|
481
482
|
}
|
|
482
483
|
|
|
483
484
|
export async function vueGen({ source, target, exclude }: GenerateOptions) {
|