@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 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 && node_fs_1.default.existsSync(node_path_1.default.join(cwd, "angular.json"));
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.widgets.${parsed.widgetName}`);
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.widgets.${parsed.widgetName}`);
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
- detailLines.push(" - Embedded web assets refreshed from detected browser dist output");
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.set(decl.name, decl);
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.set(d.name, d);
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 && requiredSymbols.has(imp.defaultImport) ? imp.defaultImport : null;
584
- const named = imp.namedImports.filter((n) => requiredSymbols.has(n.localName));
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 renderVanillaBrowserTs(spec, codecCatalog) {
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 frontendServices = spec.services.length > 0
3380
- ? spec.services.map((s) => ` ${s.name}: ${s.name};`).join("\n")
3381
- : "";
3382
- const frontendServiceFactories = spec.services.length > 0
3383
- ? spec.services.map((s) => ` ${s.name}: new ${s.name}(bridge)`).join(",\n")
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
- diagnostics: AnQstBridgeDiagnostics;
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
- diagnostics: new AnQstBridgeDiagnostics(bridge),
4217
- services: {
4218
- ${frontendServiceFactories}
4219
- }
4331
+ ${frontendFactoryLines.join(",\n")}
4220
4332
  };
4221
4333
  }
4222
4334
 
4223
- (function bootstrapAnQstGenerated(global: typeof globalThis & { AnQstGenerated?: { widgets?: Record<string, unknown> } }) {
4335
+ (function bootstrapAnQstGenerated(global: typeof globalThis & { AnQstGenerated?: Record<string, unknown> }) {
4224
4336
  const root = global.AnQstGenerated ?? (global.AnQstGenerated = {});
4225
- const widgets = root.widgets ?? (root.widgets = {});
4226
- widgets["${spec.widgetName}"] = {
4337
+ root["${spec.widgetName}"] = {
4227
4338
  createFrontend
4228
4339
  };
4229
- })(window as typeof globalThis & { AnQstGenerated?: { widgets?: Record<string, unknown> } });
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 externalTypeImports = renderRequiredTypeImports(spec, `frontend/${(0, layout_1.generatedFrontendDirName)(spec.widgetName, "VanillaTS")}/index.d.ts`).trim();
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 servicesShape = spec.services.length > 0
4298
- ? spec.services.map((s) => ` ${s.name}: ${s.name};`).join("\n")
4299
- : "";
4300
- const sections = [externalTypeImports, localTypeDecls].filter((s) => s.length > 0);
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
- diagnostics: AnQstBridgeDiagnostics;
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
- widgets: AnQstGeneratedWidgets;
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 vanillaBrowserJs = transpileBrowserTsToJs(vanillaBrowserTs);
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];
@@ -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 parseImportedTypeDecls(specFilePath, source) {
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
- for (const importedStmt of importedSource.statements) {
339
- if (typescript_1.default.isInterfaceDeclaration(importedStmt) || typescript_1.default.isTypeAliasDeclaration(importedStmt)) {
340
- const decl = parseTypeDecl(importedSource, importedStmt);
341
- importedTypeDecls.set(decl.name, decl);
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,
@@ -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.set(name, d);
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dusted/anqst",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Opinionated backend generator for webapps.",
5
5
  "keywords": [
6
6
  "nodejs",