@dusted/anqst 1.6.0 → 1.7.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/src/app.js +35 -4
- package/dist/src/boundary-codec-analysis.js +4 -2
- package/dist/src/emit.js +218 -45
- package/dist/src/parser.js +161 -6
- package/dist/src/verify.js +4 -2
- package/package.json +1 -1
package/dist/src/app.js
CHANGED
|
@@ -25,6 +25,14 @@ class CliUsageError extends Error {
|
|
|
25
25
|
this.name = "CliUsageError";
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
function firstBrowserFrontendTarget(targets) {
|
|
29
|
+
for (const target of targets) {
|
|
30
|
+
if (target === "AngularService" || target === "VanillaTS" || target === "VanillaJS") {
|
|
31
|
+
return target;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
28
36
|
const ANQSTGEN_ACTIVE_STAMP_FILE = ".anqstgen-version-active.json";
|
|
29
37
|
function renderHelp() {
|
|
30
38
|
const version = readActiveBuildStamp();
|
|
@@ -219,7 +227,9 @@ function runBuild(cwd, designerPlugin = false) {
|
|
|
219
227
|
try {
|
|
220
228
|
const specPath = (0, project_1.resolveAnQstSpecPath)(cwd);
|
|
221
229
|
const configuredWidgetName = (0, project_1.resolveAnQstWidgetName)(cwd);
|
|
230
|
+
const configuredTargets = (0, project_1.resolveAnQstGenerateTargets)(cwd);
|
|
222
231
|
const generationTargets = resolveGenerationTargetsFromCwd(cwd, true);
|
|
232
|
+
const preferredFrontendTarget = firstBrowserFrontendTarget(configuredTargets);
|
|
223
233
|
const parsed = (0, parser_1.parseSpecFile)(specPath);
|
|
224
234
|
(0, verify_1.verifySpec)(parsed);
|
|
225
235
|
if (parsed.widgetName !== configuredWidgetName) {
|
|
@@ -231,7 +241,9 @@ function runBuild(cwd, designerPlugin = false) {
|
|
|
231
241
|
if (generationTargets.emitQWidget) {
|
|
232
242
|
(0, emit_1.installQtIntegrationCMake)(cwd, parsed.widgetName);
|
|
233
243
|
}
|
|
234
|
-
const shouldRunAngularBuild = generationTargets.emitQWidget
|
|
244
|
+
const shouldRunAngularBuild = generationTargets.emitQWidget
|
|
245
|
+
&& preferredFrontendTarget === "AngularService"
|
|
246
|
+
&& node_fs_1.default.existsSync(node_path_1.default.join(cwd, "angular.json"));
|
|
235
247
|
if (shouldRunAngularBuild) {
|
|
236
248
|
const angularBuild = (0, node_child_process_1.spawnSync)("npx", ["ng", "build", "--configuration", "production"], {
|
|
237
249
|
cwd,
|
|
@@ -242,8 +254,25 @@ function runBuild(cwd, designerPlugin = false) {
|
|
|
242
254
|
throw new errors_1.VerifyError("Angular build failed while preparing embedded widget assets.");
|
|
243
255
|
}
|
|
244
256
|
}
|
|
257
|
+
let embeddedAssetsRefreshed = false;
|
|
245
258
|
if (generationTargets.emitQWidget) {
|
|
259
|
+
if (preferredFrontendTarget === "VanillaJS") {
|
|
260
|
+
try {
|
|
261
|
+
(0, emit_1.buildVanillaJsBrowserBundle)(cwd, parsed.widgetName);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
265
|
+
throw new errors_1.VerifyError(`VanillaJS browser packaging failed: ${message}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
246
268
|
const embedded = (0, emit_1.installEmbeddedWebBundle)(cwd, parsed.widgetName);
|
|
269
|
+
embeddedAssetsRefreshed = embedded;
|
|
270
|
+
if (preferredFrontendTarget === "VanillaJS" && !embedded) {
|
|
271
|
+
throw new errors_1.VerifyError("Unable to embed VanillaJS browser output. Ensure src/index.html and src/main.js produced a browser bundle.");
|
|
272
|
+
}
|
|
273
|
+
if (preferredFrontendTarget === "VanillaTS" && !embedded) {
|
|
274
|
+
throw new errors_1.VerifyError("Unable to embed VanillaTS browser output. Ensure the widget frontend build produced dist/browser with index.html.");
|
|
275
|
+
}
|
|
247
276
|
if (shouldRunAngularBuild && !embedded) {
|
|
248
277
|
throw new errors_1.VerifyError("Unable to embed browser output. Ensure the browser build produced a dist bundle with index.html.");
|
|
249
278
|
}
|
|
@@ -284,19 +313,21 @@ function runBuild(cwd, designerPlugin = false) {
|
|
|
284
313
|
if (generationTargets.emitVanillaTS) {
|
|
285
314
|
detailLines.push(" Target VanillaTS:");
|
|
286
315
|
detailLines.push(` - Browser bundle root: ${(0, layout_1.toProjectRelative)(cwd, layout.vanillaTsFrontendRoot)}`);
|
|
287
|
-
detailLines.push(` - Browser global: window.AnQstGenerated
|
|
316
|
+
detailLines.push(` - Browser global: window.AnQstGenerated.${parsed.widgetName}`);
|
|
288
317
|
}
|
|
289
318
|
if (generationTargets.emitVanillaJS) {
|
|
290
319
|
detailLines.push(" Target VanillaJS:");
|
|
291
320
|
detailLines.push(` - Browser bundle root: ${(0, layout_1.toProjectRelative)(cwd, layout.vanillaJsFrontendRoot)}`);
|
|
292
|
-
detailLines.push(` - Browser global: window.AnQstGenerated
|
|
321
|
+
detailLines.push(` - Browser global: window.AnQstGenerated.${parsed.widgetName}`);
|
|
293
322
|
}
|
|
294
323
|
if (generationTargets.emitQWidget) {
|
|
295
324
|
detailLines.push(" Target QWidget:");
|
|
296
325
|
detailLines.push(` - Qt integration CMake: ${(0, layout_1.toProjectRelative)(cwd, node_path_1.default.join(layout.cppCmakeRoot, "CMakeLists.txt"))}`);
|
|
297
326
|
detailLines.push(` - Widget output root: ${(0, layout_1.toProjectRelative)(cwd, layout.cppQtWidgetRoot)}`);
|
|
298
327
|
detailLines.push(" - C++ handoff: downstream CMake consumes this generated tree directly");
|
|
299
|
-
|
|
328
|
+
if (embeddedAssetsRefreshed) {
|
|
329
|
+
detailLines.push(" - Embedded web assets refreshed from detected browser dist output");
|
|
330
|
+
}
|
|
300
331
|
}
|
|
301
332
|
if (generationTargets.emitNodeExpressWs) {
|
|
302
333
|
detailLines.push(" Target node_express_ws:");
|
|
@@ -128,8 +128,10 @@ class BoundaryTransportAnalyzer {
|
|
|
128
128
|
const out = new Map();
|
|
129
129
|
for (const decl of this.spec.namespaceTypeDecls)
|
|
130
130
|
out.set(decl.name, decl);
|
|
131
|
-
for (const decl of this.spec.importedTypeDecls.values())
|
|
132
|
-
out.
|
|
131
|
+
for (const decl of this.spec.importedTypeDecls.values()) {
|
|
132
|
+
if (!out.has(decl.name))
|
|
133
|
+
out.set(decl.name, decl);
|
|
134
|
+
}
|
|
133
135
|
return [...out.values()];
|
|
134
136
|
}
|
|
135
137
|
nodeMeta(typeText, path, identityParts) {
|
package/dist/src/emit.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.generateOutputs = generateOutputs;
|
|
7
7
|
exports.writeGeneratedOutputs = writeGeneratedOutputs;
|
|
8
|
+
exports.buildVanillaJsBrowserBundle = buildVanillaJsBrowserBundle;
|
|
8
9
|
exports.installEmbeddedWebBundle = installEmbeddedWebBundle;
|
|
9
10
|
exports.installQtIntegrationCMake = installQtIntegrationCMake;
|
|
10
11
|
exports.installQtDesignerPluginCMake = installQtDesignerPluginCMake;
|
|
@@ -461,8 +462,10 @@ function collectStructDecls(spec) {
|
|
|
461
462
|
const out = new Map();
|
|
462
463
|
for (const d of spec.namespaceTypeDecls)
|
|
463
464
|
out.set(d.name, d);
|
|
464
|
-
for (const d of spec.importedTypeDecls.values())
|
|
465
|
-
out.
|
|
465
|
+
for (const d of spec.importedTypeDecls.values()) {
|
|
466
|
+
if (!out.has(d.name))
|
|
467
|
+
out.set(d.name, d);
|
|
468
|
+
}
|
|
466
469
|
return [...out.values()];
|
|
467
470
|
}
|
|
468
471
|
function mapTypeTextToTs(typeText) {
|
|
@@ -574,14 +577,18 @@ function normalizeImportPathForGenerated(specFilePath, generatedFileRelPath, mod
|
|
|
574
577
|
return normalized;
|
|
575
578
|
return `./${normalized}`;
|
|
576
579
|
}
|
|
577
|
-
function renderRequiredTypeImports(spec, generatedFileRelPath) {
|
|
580
|
+
function renderRequiredTypeImports(spec, generatedFileRelPath, omitSymbols = new Set()) {
|
|
578
581
|
const requiredSymbols = collectRequiredImportedSymbols(spec);
|
|
579
582
|
if (requiredSymbols.size === 0)
|
|
580
583
|
return "";
|
|
581
584
|
const importLines = [];
|
|
582
585
|
for (const imp of spec.specImports) {
|
|
583
|
-
const defaultImport = imp.defaultImport
|
|
584
|
-
|
|
586
|
+
const defaultImport = imp.defaultImport
|
|
587
|
+
&& requiredSymbols.has(imp.defaultImport)
|
|
588
|
+
&& !omitSymbols.has(imp.defaultImport)
|
|
589
|
+
? imp.defaultImport
|
|
590
|
+
: null;
|
|
591
|
+
const named = imp.namedImports.filter((n) => requiredSymbols.has(n.localName) && !omitSymbols.has(n.localName));
|
|
585
592
|
if (!defaultImport && named.length === 0)
|
|
586
593
|
continue;
|
|
587
594
|
const moduleSpecifier = normalizeImportPathForGenerated(spec.filePath, generatedFileRelPath, imp.moduleSpecifier);
|
|
@@ -3370,19 +3377,132 @@ ${constructorLines.join("\n")}${formatTsServiceSetAndOnSlotObjectLiterals(setMem
|
|
|
3370
3377
|
}
|
|
3371
3378
|
`;
|
|
3372
3379
|
}
|
|
3373
|
-
function
|
|
3380
|
+
function collectVanillaValueClasses(spec) {
|
|
3381
|
+
const classes = [];
|
|
3382
|
+
const requiredImportedSymbols = collectRequiredImportedSymbols(spec);
|
|
3383
|
+
const candidateDecls = [
|
|
3384
|
+
...spec.namespaceTypeDecls,
|
|
3385
|
+
...[...spec.importedTypeDecls.values()].filter((decl) => requiredImportedSymbols.has(decl.name))
|
|
3386
|
+
];
|
|
3387
|
+
for (const decl of candidateDecls) {
|
|
3388
|
+
if (decl.kind !== "interface")
|
|
3389
|
+
continue;
|
|
3390
|
+
const node = parseTypeDeclNode(decl.nodeText);
|
|
3391
|
+
if (!node || !typescript_1.default.isInterfaceDeclaration(node))
|
|
3392
|
+
continue;
|
|
3393
|
+
const fields = [];
|
|
3394
|
+
let supported = true;
|
|
3395
|
+
for (const member of node.members) {
|
|
3396
|
+
if (!typescript_1.default.isPropertySignature(member) || !member.type || !member.name || !typescript_1.default.isIdentifier(member.name)) {
|
|
3397
|
+
supported = false;
|
|
3398
|
+
break;
|
|
3399
|
+
}
|
|
3400
|
+
fields.push({
|
|
3401
|
+
name: member.name.text,
|
|
3402
|
+
typeText: member.type.getText(),
|
|
3403
|
+
optional: !!member.questionToken
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
if (supported) {
|
|
3407
|
+
classes.push({
|
|
3408
|
+
name: decl.name,
|
|
3409
|
+
fields
|
|
3410
|
+
});
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
return classes;
|
|
3414
|
+
}
|
|
3415
|
+
function renderVanillaValueClassConstructorArgs(fields) {
|
|
3416
|
+
let lastRequiredIndex = -1;
|
|
3417
|
+
for (let i = 0; i < fields.length; i += 1) {
|
|
3418
|
+
if (!fields[i].optional) {
|
|
3419
|
+
lastRequiredIndex = i;
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
return fields.map((field, index) => {
|
|
3423
|
+
const tsType = mapTypeTextToTs(field.typeText);
|
|
3424
|
+
if (!field.optional) {
|
|
3425
|
+
return `${field.name}: ${tsType}`;
|
|
3426
|
+
}
|
|
3427
|
+
if (index > lastRequiredIndex) {
|
|
3428
|
+
return `${field.name}?: ${tsType}`;
|
|
3429
|
+
}
|
|
3430
|
+
return `${field.name}: ${tsType} | undefined`;
|
|
3431
|
+
}).join(", ");
|
|
3432
|
+
}
|
|
3433
|
+
function renderVanillaValueClassTs(model) {
|
|
3434
|
+
const fieldLines = model.fields.map((field) => {
|
|
3435
|
+
const tsType = mapTypeTextToTs(field.typeText);
|
|
3436
|
+
return field.optional
|
|
3437
|
+
? ` ${field.name}?: ${tsType};`
|
|
3438
|
+
: ` ${field.name}: ${tsType};`;
|
|
3439
|
+
});
|
|
3440
|
+
const constructorArgs = renderVanillaValueClassConstructorArgs(model.fields);
|
|
3441
|
+
const constructorBody = model.fields.map((field) => ` this.${field.name} = ${field.name};`);
|
|
3442
|
+
const constructorLines = [
|
|
3443
|
+
` constructor(${constructorArgs}) {`,
|
|
3444
|
+
...constructorBody,
|
|
3445
|
+
" }"
|
|
3446
|
+
];
|
|
3447
|
+
return `class ${model.name} {
|
|
3448
|
+
${fieldLines.join("\n")}
|
|
3449
|
+
|
|
3450
|
+
${constructorLines.join("\n")}
|
|
3451
|
+
}`;
|
|
3452
|
+
}
|
|
3453
|
+
function renderVanillaValueClassDts(model) {
|
|
3454
|
+
const fieldLines = model.fields.map((field) => {
|
|
3455
|
+
const tsType = mapTypeTextToTs(field.typeText);
|
|
3456
|
+
return field.optional
|
|
3457
|
+
? ` ${field.name}?: ${tsType};`
|
|
3458
|
+
: ` ${field.name}: ${tsType};`;
|
|
3459
|
+
});
|
|
3460
|
+
const constructorArgs = renderVanillaValueClassConstructorArgs(model.fields);
|
|
3461
|
+
return `interface ${model.name} {
|
|
3462
|
+
${fieldLines.join("\n")}
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3465
|
+
declare const ${model.name}: {
|
|
3466
|
+
new (${constructorArgs}): ${model.name};
|
|
3467
|
+
prototype: ${model.name};
|
|
3468
|
+
};`;
|
|
3469
|
+
}
|
|
3470
|
+
function renderVanillaBrowserTs(spec, codecCatalog, emitExports = false) {
|
|
3374
3471
|
const localTypeDecls = renderTypeDeclarations(spec).trim();
|
|
3375
3472
|
const localTypesBlock = localTypeDecls.length > 0 ? `${localTypeDecls}\n\n` : "";
|
|
3473
|
+
const valueClasses = collectVanillaValueClasses(spec);
|
|
3474
|
+
const valueClassDecls = valueClasses.map((model) => renderVanillaValueClassTs(model)).join("\n\n");
|
|
3475
|
+
const valueClassBlock = valueClassDecls.length > 0 ? `${valueClassDecls}\n\n` : "";
|
|
3376
3476
|
const dragDropHelperBlock = renderTsDragDropPayloadHelpers(spec, codecCatalog).trim();
|
|
3377
3477
|
const dragDropHelpers = dragDropHelperBlock.length > 0 ? `\n// Drag/drop payload helpers\n${dragDropHelperBlock}\n` : "";
|
|
3378
3478
|
const serviceClasses = spec.services.map((s) => renderVanillaServiceTs(spec, s.name, codecCatalog)).join("\n");
|
|
3379
|
-
const
|
|
3380
|
-
|
|
3381
|
-
:
|
|
3382
|
-
|
|
3383
|
-
|
|
3479
|
+
const frontendShapeLines = [
|
|
3480
|
+
" diagnostics: AnQstBridgeDiagnostics;",
|
|
3481
|
+
...spec.services.map((s) => ` ${s.name}: ${s.name};`),
|
|
3482
|
+
...valueClasses.map((model) => ` ${model.name}: typeof ${model.name};`)
|
|
3483
|
+
];
|
|
3484
|
+
const frontendFactoryLines = [
|
|
3485
|
+
" diagnostics: new AnQstBridgeDiagnostics(bridge)",
|
|
3486
|
+
...spec.services.map((s) => ` ${s.name}: new ${s.name}(bridge)`),
|
|
3487
|
+
...valueClasses.map((model) => ` ${model.name}`)
|
|
3488
|
+
];
|
|
3489
|
+
const exportedRuntimeSymbols = [
|
|
3490
|
+
"AnQstBridgeDiagnostics",
|
|
3491
|
+
...spec.services.map((s) => s.name),
|
|
3492
|
+
...valueClasses.map((model) => model.name),
|
|
3493
|
+
"createFrontend"
|
|
3494
|
+
];
|
|
3495
|
+
const exportedTypeSymbols = [
|
|
3496
|
+
"AnQstBridgeDiagnostic",
|
|
3497
|
+
"AnQstBridgeState",
|
|
3498
|
+
`${spec.widgetName}Frontend`,
|
|
3499
|
+
`${spec.widgetName}Global`,
|
|
3500
|
+
"AnQstGeneratedRoot"
|
|
3501
|
+
];
|
|
3502
|
+
const exportsBlock = emitExports
|
|
3503
|
+
? `\nexport { ${exportedRuntimeSymbols.join(", ")} };\nexport type { ${exportedTypeSymbols.join(", ")} };\n`
|
|
3384
3504
|
: "";
|
|
3385
|
-
return `${localTypesBlock}// Boundary codec plan helpers
|
|
3505
|
+
return `${localTypesBlock}${valueClassBlock}// Boundary codec plan helpers
|
|
3386
3506
|
${(0, boundary_codecs_1.renderTsBoundaryCodecHelpers)(codecCatalog)}
|
|
3387
3507
|
${dragDropHelpers}
|
|
3388
3508
|
|
|
@@ -4200,34 +4320,25 @@ class AnQstBridgeDiagnostics {
|
|
|
4200
4320
|
}
|
|
4201
4321
|
|
|
4202
4322
|
${serviceClasses}
|
|
4203
|
-
interface ${spec.widgetName}FrontendServices {
|
|
4204
|
-
${frontendServices}
|
|
4205
|
-
}
|
|
4206
|
-
|
|
4207
4323
|
interface ${spec.widgetName}Frontend {
|
|
4208
|
-
|
|
4209
|
-
services: ${spec.widgetName}FrontendServices;
|
|
4324
|
+
${frontendShapeLines.join("\n")}
|
|
4210
4325
|
}
|
|
4211
4326
|
|
|
4212
4327
|
async function createFrontend(): Promise<${spec.widgetName}Frontend> {
|
|
4213
4328
|
const bridge = new AnQstBridgeRuntime();
|
|
4214
4329
|
await bridge.ready();
|
|
4215
4330
|
return {
|
|
4216
|
-
|
|
4217
|
-
services: {
|
|
4218
|
-
${frontendServiceFactories}
|
|
4219
|
-
}
|
|
4331
|
+
${frontendFactoryLines.join(",\n")}
|
|
4220
4332
|
};
|
|
4221
4333
|
}
|
|
4222
4334
|
|
|
4223
|
-
(function bootstrapAnQstGenerated(global: typeof globalThis & { AnQstGenerated?:
|
|
4335
|
+
(function bootstrapAnQstGenerated(global: typeof globalThis & { AnQstGenerated?: Record<string, unknown> }) {
|
|
4224
4336
|
const root = global.AnQstGenerated ?? (global.AnQstGenerated = {});
|
|
4225
|
-
|
|
4226
|
-
widgets["${spec.widgetName}"] = {
|
|
4337
|
+
root["${spec.widgetName}"] = {
|
|
4227
4338
|
createFrontend
|
|
4228
4339
|
};
|
|
4229
|
-
})(window as typeof globalThis & { AnQstGenerated?:
|
|
4230
|
-
`;
|
|
4340
|
+
})(window as typeof globalThis & { AnQstGenerated?: Record<string, unknown> });
|
|
4341
|
+
${exportsBlock}`;
|
|
4231
4342
|
}
|
|
4232
4343
|
function renderVanillaServiceDts(spec, serviceName) {
|
|
4233
4344
|
const members = spec.services.find((s) => s.name === serviceName)?.members ?? [];
|
|
@@ -4289,16 +4400,34 @@ ${declareBodyLines.join("\n")}
|
|
|
4289
4400
|
}`;
|
|
4290
4401
|
}
|
|
4291
4402
|
function renderVanillaIndexDts(spec) {
|
|
4292
|
-
const
|
|
4403
|
+
const valueClasses = collectVanillaValueClasses(spec);
|
|
4404
|
+
const valueClassNames = new Set(valueClasses.map((model) => model.name));
|
|
4405
|
+
const externalTypeImports = renderRequiredTypeImports(spec, `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName, "VanillaTS")}/index.d.ts`, valueClassNames).trim();
|
|
4293
4406
|
// Export widget namespace types so other packages can `import type { ... }` from this declaration file
|
|
4294
4407
|
// (e.g. a second widget spec that reuses structs generated for the first widget).
|
|
4295
4408
|
const localTypeDecls = renderTypeDeclarations(spec, true).trim();
|
|
4409
|
+
const valueClassDecls = valueClasses.map((model) => renderVanillaValueClassDts(model)).join("\n\n");
|
|
4296
4410
|
const serviceDecls = spec.services.map((s) => renderVanillaServiceDts(spec, s.name)).join("\n\n");
|
|
4297
|
-
const
|
|
4298
|
-
|
|
4299
|
-
:
|
|
4300
|
-
|
|
4411
|
+
const frontendShapeLines = [
|
|
4412
|
+
" diagnostics: AnQstBridgeDiagnostics;",
|
|
4413
|
+
...spec.services.map((s) => ` ${s.name}: ${s.name};`),
|
|
4414
|
+
...valueClasses.map((model) => ` ${model.name}: typeof ${model.name};`)
|
|
4415
|
+
];
|
|
4416
|
+
const sections = [externalTypeImports, localTypeDecls, valueClassDecls].filter((s) => s.length > 0);
|
|
4301
4417
|
const prelude = sections.length > 0 ? `${sections.join("\n\n")}\n\n` : "";
|
|
4418
|
+
const exportedRuntimeSymbols = [
|
|
4419
|
+
"AnQstBridgeDiagnostics",
|
|
4420
|
+
...spec.services.map((s) => s.name),
|
|
4421
|
+
...valueClasses.map((model) => model.name),
|
|
4422
|
+
"createFrontend"
|
|
4423
|
+
];
|
|
4424
|
+
const exportedTypeSymbols = [
|
|
4425
|
+
"AnQstBridgeDiagnostic",
|
|
4426
|
+
"AnQstBridgeState",
|
|
4427
|
+
`${spec.widgetName}Frontend`,
|
|
4428
|
+
`${spec.widgetName}Global`,
|
|
4429
|
+
"AnQstGeneratedRoot"
|
|
4430
|
+
];
|
|
4302
4431
|
return `export {};
|
|
4303
4432
|
${prelude}type AnQstBridgeSeverity = "info" | "warn" | "error" | "fatal";
|
|
4304
4433
|
|
|
@@ -4331,25 +4460,18 @@ declare class AnQstBridgeDiagnostics {
|
|
|
4331
4460
|
|
|
4332
4461
|
${serviceDecls}
|
|
4333
4462
|
|
|
4334
|
-
interface ${spec.widgetName}FrontendServices {
|
|
4335
|
-
${servicesShape}
|
|
4336
|
-
}
|
|
4337
|
-
|
|
4338
4463
|
interface ${spec.widgetName}Frontend {
|
|
4339
|
-
|
|
4340
|
-
services: ${spec.widgetName}FrontendServices;
|
|
4464
|
+
${frontendShapeLines.join("\n")}
|
|
4341
4465
|
}
|
|
4342
4466
|
|
|
4467
|
+
declare function createFrontend(): Promise<${spec.widgetName}Frontend>;
|
|
4468
|
+
|
|
4343
4469
|
interface ${spec.widgetName}Global {
|
|
4344
4470
|
createFrontend(): Promise<${spec.widgetName}Frontend>;
|
|
4345
4471
|
}
|
|
4346
4472
|
|
|
4347
|
-
interface AnQstGeneratedWidgets {
|
|
4348
|
-
${spec.widgetName}: ${spec.widgetName}Global;
|
|
4349
|
-
}
|
|
4350
|
-
|
|
4351
4473
|
interface AnQstGeneratedRoot {
|
|
4352
|
-
|
|
4474
|
+
${spec.widgetName}: ${spec.widgetName}Global;
|
|
4353
4475
|
}
|
|
4354
4476
|
|
|
4355
4477
|
declare global {
|
|
@@ -4359,6 +4481,9 @@ declare global {
|
|
|
4359
4481
|
|
|
4360
4482
|
var AnQstGenerated: AnQstGeneratedRoot;
|
|
4361
4483
|
}
|
|
4484
|
+
|
|
4485
|
+
export { ${exportedRuntimeSymbols.join(", ")} };
|
|
4486
|
+
export type { ${exportedTypeSymbols.join(", ")} };
|
|
4362
4487
|
`;
|
|
4363
4488
|
}
|
|
4364
4489
|
function transpileBrowserTsToJs(source) {
|
|
@@ -5205,10 +5330,12 @@ function generateOutputs(spec, options = {}) {
|
|
|
5205
5330
|
outputs[`${angularFrontendDir}/types/types.d.ts`] = renderTypeTypesDts(spec);
|
|
5206
5331
|
}
|
|
5207
5332
|
if (normalizedOptions.emitVanillaTS || normalizedOptions.emitVanillaJS) {
|
|
5208
|
-
const vanillaBrowserTs = renderVanillaBrowserTs(spec, codecCatalog);
|
|
5209
|
-
const
|
|
5333
|
+
const vanillaBrowserTs = renderVanillaBrowserTs(spec, codecCatalog, true);
|
|
5334
|
+
const vanillaBrowserScriptTs = renderVanillaBrowserTs(spec, codecCatalog, false);
|
|
5335
|
+
const vanillaBrowserJs = transpileBrowserTsToJs(vanillaBrowserScriptTs);
|
|
5210
5336
|
if (normalizedOptions.emitVanillaTS) {
|
|
5211
5337
|
outputs[`${vanillaTsFrontendDir}/package.json`] = renderVanillaPackage(spec, "VanillaTS");
|
|
5338
|
+
outputs[`${vanillaTsFrontendDir}/index.ts`] = vanillaBrowserTs;
|
|
5212
5339
|
outputs[`${vanillaTsFrontendDir}/index.js`] = vanillaBrowserJs;
|
|
5213
5340
|
outputs[`${vanillaTsFrontendDir}/index.d.ts`] = renderVanillaIndexDts(spec);
|
|
5214
5341
|
}
|
|
@@ -5241,6 +5368,52 @@ function writeGeneratedOutputs(cwd, outputs) {
|
|
|
5241
5368
|
node_fs_1.default.writeFileSync(filePath, withBuildStamp(relPath, content), "utf8");
|
|
5242
5369
|
}
|
|
5243
5370
|
}
|
|
5371
|
+
function normalizeVanillaJsIndexHtml(html) {
|
|
5372
|
+
let normalized = html.replace(/<script\b[^>]*src=["'](?:\.\/)?main\.js["'][^>]*>\s*<\/script>\s*/gi, "");
|
|
5373
|
+
const bundleScriptTag = ' <script defer src="./main.js"></script>\n';
|
|
5374
|
+
if (/<\/head>/i.test(normalized)) {
|
|
5375
|
+
normalized = normalized.replace(/<\/head>/i, `${bundleScriptTag}</head>`);
|
|
5376
|
+
}
|
|
5377
|
+
else if (/<\/body>/i.test(normalized)) {
|
|
5378
|
+
normalized = normalized.replace(/<\/body>/i, `${bundleScriptTag}</body>`);
|
|
5379
|
+
}
|
|
5380
|
+
else {
|
|
5381
|
+
normalized = `${normalized}\n${bundleScriptTag}`;
|
|
5382
|
+
}
|
|
5383
|
+
return normalized;
|
|
5384
|
+
}
|
|
5385
|
+
function buildVanillaJsBrowserBundle(cwd, widgetName) {
|
|
5386
|
+
const srcRoot = node_path_1.default.join(cwd, "src");
|
|
5387
|
+
const srcIndexPath = node_path_1.default.join(srcRoot, "index.html");
|
|
5388
|
+
const srcMainPath = node_path_1.default.join(srcRoot, "main.js");
|
|
5389
|
+
const generatedRuntimePath = node_path_1.default.join((0, layout_1.resolveGeneratedLayoutPaths)(cwd, widgetName).vanillaJsFrontendRoot, "index.js");
|
|
5390
|
+
if (!node_fs_1.default.existsSync(srcIndexPath)) {
|
|
5391
|
+
throw new Error("VanillaJS build requires src/index.html.");
|
|
5392
|
+
}
|
|
5393
|
+
if (!node_fs_1.default.existsSync(srcMainPath)) {
|
|
5394
|
+
throw new Error("VanillaJS build requires src/main.js.");
|
|
5395
|
+
}
|
|
5396
|
+
if (!node_fs_1.default.existsSync(generatedRuntimePath)) {
|
|
5397
|
+
throw new Error("VanillaJS generated runtime is missing. Run generation before packaging the browser bundle.");
|
|
5398
|
+
}
|
|
5399
|
+
const distWebRoot = node_path_1.default.join(cwd, "dist", "browser");
|
|
5400
|
+
node_fs_1.default.rmSync(distWebRoot, { recursive: true, force: true });
|
|
5401
|
+
node_fs_1.default.mkdirSync(distWebRoot, { recursive: true });
|
|
5402
|
+
copyDirectoryRecursive(srcRoot, distWebRoot);
|
|
5403
|
+
const generatedRuntime = node_fs_1.default.readFileSync(generatedRuntimePath, "utf8").trimEnd();
|
|
5404
|
+
const appMain = node_fs_1.default.readFileSync(srcMainPath, "utf8").trim();
|
|
5405
|
+
const bundleSource = `${generatedRuntime}
|
|
5406
|
+
|
|
5407
|
+
${appMain}
|
|
5408
|
+
|
|
5409
|
+
void main(window, document, window.AnQstGenerated);
|
|
5410
|
+
`;
|
|
5411
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(distWebRoot, "main.js"), withBuildStamp("dist/browser/main.js", bundleSource), "utf8");
|
|
5412
|
+
const sourceIndexHtml = node_fs_1.default.readFileSync(srcIndexPath, "utf8");
|
|
5413
|
+
const normalizedIndexHtml = normalizeVanillaJsIndexHtml(sourceIndexHtml);
|
|
5414
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(distWebRoot, "index.html"), withBuildStamp("dist/browser/index.html", normalizedIndexHtml), "utf8");
|
|
5415
|
+
return distWebRoot;
|
|
5416
|
+
}
|
|
5244
5417
|
function listFilesRecursively(rootDir) {
|
|
5245
5418
|
const output = [];
|
|
5246
5419
|
const queue = [rootDir];
|
package/dist/src/parser.js
CHANGED
|
@@ -24,6 +24,22 @@ function qNameToText(name) {
|
|
|
24
24
|
return name.text;
|
|
25
25
|
return `${qNameToText(name.left)}.${name.right.text}`;
|
|
26
26
|
}
|
|
27
|
+
function textToEntityName(text) {
|
|
28
|
+
const parts = text.split(".");
|
|
29
|
+
let current = typescript_1.default.factory.createIdentifier(parts[0] ?? text);
|
|
30
|
+
for (const part of parts.slice(1)) {
|
|
31
|
+
current = typescript_1.default.factory.createQualifiedName(current, typescript_1.default.factory.createIdentifier(part));
|
|
32
|
+
}
|
|
33
|
+
return current;
|
|
34
|
+
}
|
|
35
|
+
function textToExpressionName(text) {
|
|
36
|
+
const parts = text.split(".");
|
|
37
|
+
let current = typescript_1.default.factory.createIdentifier(parts[0] ?? text);
|
|
38
|
+
for (const part of parts.slice(1)) {
|
|
39
|
+
current = typescript_1.default.factory.createPropertyAccessExpression(current, typescript_1.default.factory.createIdentifier(part));
|
|
40
|
+
}
|
|
41
|
+
return current;
|
|
42
|
+
}
|
|
27
43
|
function collectReferencedTypeNames(node) {
|
|
28
44
|
const refs = new Set();
|
|
29
45
|
const visit = (n) => {
|
|
@@ -33,6 +49,9 @@ function collectReferencedTypeNames(node) {
|
|
|
33
49
|
else if (typescript_1.default.isExpressionWithTypeArguments(n) && typescript_1.default.isIdentifier(n.expression)) {
|
|
34
50
|
refs.add(n.expression.text);
|
|
35
51
|
}
|
|
52
|
+
else if (typescript_1.default.isTypeQueryNode(n)) {
|
|
53
|
+
refs.add(qNameToText(n.exprName));
|
|
54
|
+
}
|
|
36
55
|
typescript_1.default.forEachChild(n, visit);
|
|
37
56
|
};
|
|
38
57
|
visit(node);
|
|
@@ -294,10 +313,118 @@ function requiresLocalImportResolution(moduleName) {
|
|
|
294
313
|
return false;
|
|
295
314
|
return moduleName.includes("/");
|
|
296
315
|
}
|
|
297
|
-
function
|
|
316
|
+
function collectTopLevelTypeDecls(source) {
|
|
317
|
+
const out = new Map();
|
|
318
|
+
for (const stmt of source.statements) {
|
|
319
|
+
if ((typescript_1.default.isInterfaceDeclaration(stmt) || typescript_1.default.isTypeAliasDeclaration(stmt)) && stmt.name) {
|
|
320
|
+
out.set(stmt.name.text, stmt);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
function collectReachableImportedTypeNames(topLevelDecls, rootNames) {
|
|
326
|
+
const queue = [...rootNames];
|
|
327
|
+
const seen = new Set();
|
|
328
|
+
const ordered = [];
|
|
329
|
+
while (queue.length > 0) {
|
|
330
|
+
const current = queue.shift();
|
|
331
|
+
if (seen.has(current))
|
|
332
|
+
continue;
|
|
333
|
+
seen.add(current);
|
|
334
|
+
const node = topLevelDecls.get(current);
|
|
335
|
+
if (!node)
|
|
336
|
+
continue;
|
|
337
|
+
ordered.push(current);
|
|
338
|
+
const decl = {
|
|
339
|
+
referencedTypeNames: collectReferencedTypeNames(node)
|
|
340
|
+
};
|
|
341
|
+
for (const ref of decl.referencedTypeNames) {
|
|
342
|
+
if (topLevelDecls.has(ref) && !seen.has(ref)) {
|
|
343
|
+
queue.push(ref);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return ordered;
|
|
348
|
+
}
|
|
349
|
+
function allocateSyntheticImportedTypeName(sourceName, usedNames) {
|
|
350
|
+
const cleaned = sourceName
|
|
351
|
+
.replace(/[^A-Za-z0-9_]/g, "_")
|
|
352
|
+
.replace(/_+/g, "_")
|
|
353
|
+
.replace(/^_+|_+$/g, "");
|
|
354
|
+
const base = `AnQstImported_${cleaned || "Type"}`;
|
|
355
|
+
let candidate = base;
|
|
356
|
+
let i = 2;
|
|
357
|
+
while (usedNames.has(candidate)) {
|
|
358
|
+
candidate = `${base}_${i}`;
|
|
359
|
+
i += 1;
|
|
360
|
+
}
|
|
361
|
+
usedNames.add(candidate);
|
|
362
|
+
return candidate;
|
|
363
|
+
}
|
|
364
|
+
function rewriteImportedTypeDecl(importedSource, node, finalName, nameMap) {
|
|
365
|
+
const renamed = typescript_1.default.isInterfaceDeclaration(node)
|
|
366
|
+
? typescript_1.default.factory.updateInterfaceDeclaration(node, node.modifiers, typescript_1.default.factory.createIdentifier(finalName), node.typeParameters, node.heritageClauses, node.members)
|
|
367
|
+
: typescript_1.default.factory.updateTypeAliasDeclaration(node, node.modifiers, typescript_1.default.factory.createIdentifier(finalName), node.typeParameters, node.type);
|
|
368
|
+
const transformed = typescript_1.default.transform(renamed, [(context) => {
|
|
369
|
+
const visitor = (child) => {
|
|
370
|
+
if (typescript_1.default.isTypeReferenceNode(child)) {
|
|
371
|
+
const mapped = nameMap.get(qNameToText(child.typeName));
|
|
372
|
+
if (mapped) {
|
|
373
|
+
return typescript_1.default.factory.updateTypeReferenceNode(child, textToEntityName(mapped), child.typeArguments);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else if (typescript_1.default.isExpressionWithTypeArguments(child)) {
|
|
377
|
+
const exprText = typescript_1.default.isIdentifier(child.expression) || typescript_1.default.isPropertyAccessExpression(child.expression)
|
|
378
|
+
? child.expression.getText(importedSource)
|
|
379
|
+
: null;
|
|
380
|
+
const mapped = exprText ? nameMap.get(exprText) : null;
|
|
381
|
+
if (mapped) {
|
|
382
|
+
return typescript_1.default.factory.updateExpressionWithTypeArguments(child, textToExpressionName(mapped), child.typeArguments);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else if (typescript_1.default.isTypeQueryNode(child)) {
|
|
386
|
+
const mapped = nameMap.get(qNameToText(child.exprName));
|
|
387
|
+
if (mapped) {
|
|
388
|
+
return typescript_1.default.factory.updateTypeQueryNode(child, textToEntityName(mapped), child.typeArguments);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return typescript_1.default.visitEachChild(child, visitor, context);
|
|
392
|
+
};
|
|
393
|
+
return (root) => typescript_1.default.visitNode(root, visitor);
|
|
394
|
+
}]);
|
|
395
|
+
const rewritten = transformed.transformed[0];
|
|
396
|
+
transformed.dispose();
|
|
397
|
+
const printer = typescript_1.default.createPrinter({ newLine: typescript_1.default.NewLineKind.LineFeed });
|
|
398
|
+
const rewrittenSource = typescript_1.default.createSourceFile("__anqst_imported_decl.ts", "", typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TS);
|
|
399
|
+
const nodeText = printer.printNode(typescript_1.default.EmitHint.Unspecified, rewritten, rewrittenSource);
|
|
400
|
+
return {
|
|
401
|
+
name: finalName,
|
|
402
|
+
kind: typescript_1.default.isInterfaceDeclaration(rewritten) ? "interface" : "type",
|
|
403
|
+
nodeText,
|
|
404
|
+
referencedTypeNames: collectReferencedTypeNames(rewritten),
|
|
405
|
+
loc: locFromNode(importedSource, node)
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function createImportedAliasDecl(aliasName, targetName, loc) {
|
|
409
|
+
const nodeText = `type ${aliasName} = ${targetName};`;
|
|
410
|
+
const source = typescript_1.default.createSourceFile("__anqst_import_alias.ts", nodeText, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TS);
|
|
411
|
+
const stmt = source.statements.find(typescript_1.default.isTypeAliasDeclaration);
|
|
412
|
+
if (!stmt) {
|
|
413
|
+
throw new Error(`Unable to synthesize imported alias declaration for ${aliasName}.`);
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
name: aliasName,
|
|
417
|
+
kind: "type",
|
|
418
|
+
nodeText,
|
|
419
|
+
referencedTypeNames: collectReferencedTypeNames(stmt),
|
|
420
|
+
loc
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function parseImportedTypeDecls(specFilePath, source, reservedTypeNames = new Set()) {
|
|
298
424
|
const importedTypeDecls = new Map();
|
|
299
425
|
const importedTypeSymbols = new Set();
|
|
300
426
|
const specImports = [];
|
|
427
|
+
const usedImportedNames = new Set(reservedTypeNames);
|
|
301
428
|
for (const stmt of source.statements) {
|
|
302
429
|
if (!typescript_1.default.isImportDeclaration(stmt) || !stmt.importClause || !typescript_1.default.isStringLiteral(stmt.moduleSpecifier))
|
|
303
430
|
continue;
|
|
@@ -335,10 +462,38 @@ function parseImportedTypeDecls(specFilePath, source) {
|
|
|
335
462
|
}
|
|
336
463
|
const text = node_fs_1.default.readFileSync(resolved, "utf8");
|
|
337
464
|
const importedSource = typescript_1.default.createSourceFile(resolved, text, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TS);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
465
|
+
const topLevelDecls = collectTopLevelTypeDecls(importedSource);
|
|
466
|
+
const directAliasesBySourceName = new Map();
|
|
467
|
+
for (const namedImport of importModel.namedImports) {
|
|
468
|
+
if (!topLevelDecls.has(namedImport.importedName))
|
|
469
|
+
continue;
|
|
470
|
+
const aliases = directAliasesBySourceName.get(namedImport.importedName) ?? [];
|
|
471
|
+
aliases.push(namedImport.localName);
|
|
472
|
+
directAliasesBySourceName.set(namedImport.importedName, aliases);
|
|
473
|
+
}
|
|
474
|
+
const reachableSourceNames = collectReachableImportedTypeNames(topLevelDecls, directAliasesBySourceName.keys());
|
|
475
|
+
if (reachableSourceNames.length === 0)
|
|
476
|
+
continue;
|
|
477
|
+
const canonicalNameBySourceName = new Map();
|
|
478
|
+
for (const sourceName of reachableSourceNames) {
|
|
479
|
+
const directAliases = directAliasesBySourceName.get(sourceName);
|
|
480
|
+
if (directAliases && directAliases.length > 0) {
|
|
481
|
+
canonicalNameBySourceName.set(sourceName, directAliases[0]);
|
|
482
|
+
usedImportedNames.add(directAliases[0]);
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
canonicalNameBySourceName.set(sourceName, allocateSyntheticImportedTypeName(sourceName, usedImportedNames));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
for (const sourceName of reachableSourceNames) {
|
|
489
|
+
const node = topLevelDecls.get(sourceName);
|
|
490
|
+
const finalName = canonicalNameBySourceName.get(sourceName);
|
|
491
|
+
if (!node || !finalName)
|
|
492
|
+
continue;
|
|
493
|
+
importedTypeDecls.set(finalName, rewriteImportedTypeDecl(importedSource, node, finalName, canonicalNameBySourceName));
|
|
494
|
+
const directAliases = directAliasesBySourceName.get(sourceName) ?? [];
|
|
495
|
+
for (const alias of directAliases.slice(1)) {
|
|
496
|
+
importedTypeDecls.set(alias, createImportedAliasDecl(alias, finalName, locFromNode(importedSource, node)));
|
|
342
497
|
}
|
|
343
498
|
}
|
|
344
499
|
}
|
|
@@ -398,7 +553,7 @@ function parseSpecFileAst(specFilePath) {
|
|
|
398
553
|
namespaceTypeDecls.push(parseTypeDecl(source, stmt));
|
|
399
554
|
}
|
|
400
555
|
}
|
|
401
|
-
const importInfo = parseImportedTypeDecls(specFilePath, source);
|
|
556
|
+
const importInfo = parseImportedTypeDecls(specFilePath, source, new Set(namespaceTypeDecls.map((decl) => decl.name)));
|
|
402
557
|
return {
|
|
403
558
|
filePath: specFilePath,
|
|
404
559
|
widgetName: ns.name.text,
|
package/dist/src/verify.js
CHANGED
|
@@ -135,8 +135,10 @@ function collectReachableTypeNames(spec) {
|
|
|
135
135
|
const allDecls = new Map();
|
|
136
136
|
for (const d of spec.namespaceTypeDecls)
|
|
137
137
|
allDecls.set(d.name, d);
|
|
138
|
-
for (const [name, d] of spec.importedTypeDecls)
|
|
139
|
-
allDecls.
|
|
138
|
+
for (const [name, d] of spec.importedTypeDecls) {
|
|
139
|
+
if (!allDecls.has(name))
|
|
140
|
+
allDecls.set(name, d);
|
|
141
|
+
}
|
|
140
142
|
const queue = [];
|
|
141
143
|
const seen = new Set();
|
|
142
144
|
for (const d of spec.namespaceTypeDecls)
|