@techspokes/typescript-wsdl-client 1.0.7 → 1.1.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.
Files changed (38) hide show
  1. package/README.md +3 -0
  2. package/dist/app/generateApp.d.ts.map +1 -1
  3. package/dist/app/generateApp.js +4 -3
  4. package/dist/client/generateClient.d.ts +1 -0
  5. package/dist/client/generateClient.d.ts.map +1 -1
  6. package/dist/client/generateClient.js +6 -15
  7. package/dist/client/generateOperations.d.ts +1 -0
  8. package/dist/client/generateOperations.d.ts.map +1 -1
  9. package/dist/client/generateOperations.js +3 -8
  10. package/dist/client/generateTypes.d.ts +1 -0
  11. package/dist/client/generateTypes.d.ts.map +1 -1
  12. package/dist/client/generateTypes.js +3 -8
  13. package/dist/client/generateUtils.d.ts +1 -0
  14. package/dist/client/generateUtils.d.ts.map +1 -1
  15. package/dist/client/generateUtils.js +3 -8
  16. package/dist/gateway/generators.d.ts.map +1 -1
  17. package/dist/gateway/generators.js +9 -8
  18. package/dist/generation/preamble.d.ts +17 -0
  19. package/dist/generation/preamble.d.ts.map +1 -0
  20. package/dist/generation/preamble.js +73 -0
  21. package/dist/generation/writeGeneratedSource.d.ts +11 -0
  22. package/dist/generation/writeGeneratedSource.d.ts.map +1 -0
  23. package/dist/generation/writeGeneratedSource.js +24 -0
  24. package/dist/test/generateTests.d.ts.map +1 -1
  25. package/dist/test/generateTests.js +12 -11
  26. package/docs/api-reference.md +2 -0
  27. package/docs/architecture.md +10 -0
  28. package/docs/cli-reference.md +2 -0
  29. package/docs/file-naming-and-path-organization.md +6 -0
  30. package/docs/generated-code.md +100 -0
  31. package/docs/output-anatomy.md +4 -0
  32. package/docs/releases/v1.1.0.md +42 -0
  33. package/docs/testing.md +2 -0
  34. package/docs/troubleshooting.md +8 -0
  35. package/package.json +8 -3
  36. package/src/generation/preambles/app/typescript.preamble +3 -0
  37. package/src/generation/preambles/test/typescript.preamble +3 -0
  38. package/src/generation/preambles/typescript.preamble +3 -0
package/README.md CHANGED
@@ -55,6 +55,8 @@ npx wsdl-tsc client \
55
55
 
56
56
  This produces `types.ts`, `client.ts`, `operations.ts`, and `utils.ts` in the output directory.
57
57
 
58
+ Generated TypeScript includes an ESLint suppression preamble. See [generated-code linting guidance](docs/generated-code.md#generated-preambles-and-eslint) for strict policies, path exclusions, and editable scaffold ownership.
59
+
58
60
  ### Generate an OpenAPI spec
59
61
 
60
62
  ```bash
@@ -226,6 +228,7 @@ See [CLI Reference](docs/cli-reference.md) for all flags and examples.
226
228
  | [Production Guide](docs/production.md) | CI/CD, validation, logging |
227
229
  | [Troubleshooting](docs/troubleshooting.md) | Common issues and debugging |
228
230
  | [Release Notes](docs/releases/README.md) | Per-version GitHub release notes used by the draft release workflow |
231
+ | [v1.1.0 Release Notes](docs/releases/v1.1.0.md) | Automatic generated TypeScript preambles and consumer lint guidance |
229
232
 
230
233
  ### Extend
231
234
 
@@ -1 +1 @@
1
- {"version":3,"file":"generateApp.d.ts","sourceRoot":"","sources":["../../src/app/generateApp.ts"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAuqBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDzE"}
1
+ {"version":3,"file":"generateApp.d.ts","sourceRoot":"","sources":["../../src/app/generateApp.ts"],"names":[],"mappings":"AAiCA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAuqBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDzE"}
@@ -22,6 +22,7 @@
22
22
  * - Skip-if-exists protection for scaffold files (override with force option)
23
23
  */
24
24
  import fs from "node:fs";
25
+ import { writeGeneratedSource } from "../generation/writeGeneratedSource.js";
25
26
  import path from "node:path";
26
27
  import { deriveClientName } from "../util/tools.js";
27
28
  import { info, success } from "../util/cli.js";
@@ -306,7 +307,7 @@ main().catch((err) => {
306
307
  process.exit(1);
307
308
  });
308
309
  `;
309
- fs.writeFileSync(filePath, content, "utf-8");
310
+ writeGeneratedSource(filePath, content, "app");
310
311
  }
311
312
  /**
312
313
  * Generates config.ts file
@@ -397,7 +398,7 @@ export function loadConfig(): AppConfig {
397
398
  };
398
399
  }
399
400
  `;
400
- fs.writeFileSync(filePath, content, "utf-8");
401
+ writeGeneratedSource(filePath, content, "app");
401
402
  }
402
403
  function loadOptionalSecurityConfig(filePath) {
403
404
  return filePath ? loadSecurityConfigFile(filePath) : undefined;
@@ -503,7 +504,7 @@ function generateSecurityFile(appDir, opts, force) {
503
504
  return;
504
505
  const upstreamJson = JSON.stringify(securityCfg?.upstream ?? {}, null, 2);
505
506
  const content = loadRuntimeTemplate("appSecurity.tpl.txt").replace("__UPSTREAM_JSON__", upstreamJson);
506
- fs.writeFileSync(filePath, content, "utf-8");
507
+ writeGeneratedSource(filePath, content, "app");
507
508
  }
508
509
  /**
509
510
  * Generates package.json file (skipped if already exists)
@@ -15,6 +15,7 @@ import type { CompiledCatalog } from "../compiler/schemaCompiler.js";
15
15
  *
16
16
  * @param {string} outFile - Path to the output TypeScript file
17
17
  * @param {CompiledCatalog} compiled - The compiled WSDL catalog
18
+ * @throws {Error} If a packaged preamble cannot be loaded or a TypeScript file cannot be written.
18
19
  */
19
20
  export declare function generateClient(outFile: string, compiled: CompiledCatalog): void;
20
21
  //# sourceMappingURL=generateClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generateClient.d.ts","sourceRoot":"","sources":["../../src/client/generateClient.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,+BAA+B,CAAC;AAKnE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,QAsaxE"}
1
+ {"version":3,"file":"generateClient.d.ts","sourceRoot":"","sources":["../../src/client/generateClient.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,+BAA+B,CAAC;AAKnE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,QA8ZxE"}
@@ -12,10 +12,10 @@
12
12
  * - Support for custom attribute handling
13
13
  * - Consistent error handling and client configuration
14
14
  */
15
- import fs from "node:fs";
15
+ import { writeGeneratedSource } from "../generation/writeGeneratedSource.js";
16
16
  import path from "node:path";
17
17
  import { deriveClientName, pascal, pascalToSnakeCase } from "../util/tools.js";
18
- import { error, warn } from "../util/cli.js";
18
+ import { warn } from "../util/cli.js";
19
19
  import { loadRuntimeSource } from "../util/runtimeSource.js";
20
20
  /**
21
21
  * Generates a TypeScript SOAP client class from a compiled WSDL catalog
@@ -33,6 +33,7 @@ import { loadRuntimeSource } from "../util/runtimeSource.js";
33
33
  *
34
34
  * @param {string} outFile - Path to the output TypeScript file
35
35
  * @param {CompiledCatalog} compiled - The compiled WSDL catalog
36
+ * @throws {Error} If a packaged preamble cannot be loaded or a TypeScript file cannot be written.
36
37
  */
37
38
  export function generateClient(outFile, compiled) {
38
39
  const isValidIdent = (name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
@@ -433,22 +434,12 @@ ${methodsBody}
433
434
  }${streamMethodsBlock}
434
435
  }
435
436
  `;
436
- try {
437
- fs.writeFileSync(outFile, classTemplate.replace(`// noinspection JSAnnotator\n\n`, ''), "utf8");
438
- }
439
- catch (e) {
440
- error(`Failed to write client to ${outFile}`);
441
- }
437
+ writeGeneratedSource(outFile, classTemplate.replace(`// noinspection JSAnnotator\n\n`, ''), "client");
442
438
  // If any operation opted into streaming, drop the runtime XML parser
443
439
  // alongside the client so the generated class can import it without
444
440
  // depending on a `@techspokes/typescript-wsdl-client/runtime/...` subpath.
445
441
  if (anyStream) {
446
- try {
447
- const streamXmlOut = path.join(path.dirname(outFile), "streamXml.ts");
448
- fs.writeFileSync(streamXmlOut, loadRuntimeSource("streamXml.ts"), "utf-8");
449
- }
450
- catch (e) {
451
- error(`Failed to emit streamXml.ts next to ${outFile}: ${e instanceof Error ? e.message : String(e)}`);
452
- }
442
+ const streamXmlOut = path.join(path.dirname(outFile), "streamXml.ts");
443
+ writeGeneratedSource(streamXmlOut, loadRuntimeSource("streamXml.ts"), "client/stream");
453
444
  }
454
445
  }
@@ -8,6 +8,7 @@ import type { CompiledCatalog } from "../compiler/schemaCompiler.js";
8
8
  *
9
9
  * @param outFile - Path to the output TypeScript file
10
10
  * @param compiled - The compiled WSDL catalog
11
+ * @throws {Error} If a packaged preamble cannot be loaded or the TypeScript file cannot be written.
11
12
  */
12
13
  export declare function generateOperations(outFile: string, compiled: CompiledCatalog): void;
13
14
  //# sourceMappingURL=generateOperations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generateOperations.d.ts","sourceRoot":"","sources":["../../src/client/generateOperations.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAKrE;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CA8FnF"}
1
+ {"version":3,"file":"generateOperations.d.ts","sourceRoot":"","sources":["../../src/client/generateOperations.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAIrE;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CA0FnF"}
@@ -5,9 +5,8 @@
5
5
  * This standalone interface enables mocking and testing without importing
6
6
  * the concrete SOAP client class or its runtime dependencies.
7
7
  */
8
- import fs from "node:fs";
8
+ import { writeGeneratedSource } from "../generation/writeGeneratedSource.js";
9
9
  import { deriveClientName, pascal } from "../util/tools.js";
10
- import { error } from "../util/cli.js";
11
10
  import { loadRuntimeSource } from "../util/runtimeSource.js";
12
11
  /**
13
12
  * Generates an operations.ts file with a fully-typed interface for all SOAP operations
@@ -18,6 +17,7 @@ import { loadRuntimeSource } from "../util/runtimeSource.js";
18
17
  *
19
18
  * @param outFile - Path to the output TypeScript file
20
19
  * @param compiled - The compiled WSDL catalog
20
+ * @throws {Error} If a packaged preamble cannot be loaded or the TypeScript file cannot be written.
21
21
  */
22
22
  export function generateOperations(outFile, compiled) {
23
23
  const ext = compiled.options.imports ?? "bare";
@@ -95,10 +95,5 @@ ${typeImport}${streamHelper}/**
95
95
  */
96
96
  export interface ${clientName}Operations {
97
97
  ${methods.join("\n")}}\n`;
98
- try {
99
- fs.writeFileSync(outFile, content, "utf8");
100
- }
101
- catch (e) {
102
- error(`Failed to write operations interface to ${outFile}`);
103
- }
98
+ writeGeneratedSource(outFile, content, "client");
104
99
  }
@@ -17,6 +17,7 @@ import type { CompiledCatalog } from "../compiler/schemaCompiler.js";
17
17
  *
18
18
  * @param {string} outFile - Path to the output TypeScript file
19
19
  * @param {CompiledCatalog} compiled - The compiled WSDL catalog
20
+ * @throws {Error} If a packaged preamble cannot be loaded or the TypeScript file cannot be written.
20
21
  */
21
22
  export declare function generateTypes(outFile: string, compiled: CompiledCatalog): void;
22
23
  //# sourceMappingURL=generateTypes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generateTypes.d.ts","sourceRoot":"","sources":["../../src/client/generateTypes.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,eAAe,EAA0D,MAAM,+BAA+B,CAAC;AAI5H;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,QAsSvE"}
1
+ {"version":3,"file":"generateTypes.d.ts","sourceRoot":"","sources":["../../src/client/generateTypes.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAC,eAAe,EAA0D,MAAM,+BAA+B,CAAC;AAG5H;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,QAkSvE"}
@@ -12,8 +12,7 @@
12
12
  * - Consistent property ordering and interface structuring
13
13
  * - Optional/required markers based on XML schema requirements
14
14
  */
15
- import fs from "node:fs";
16
- import { error } from "../util/cli.js";
15
+ import { writeGeneratedSource } from "../generation/writeGeneratedSource.js";
17
16
  import { hasAttributeWildcards, wildcardAttributeBagName } from "../util/attributeWildcards.js";
18
17
  /**
19
18
  * Generates TypeScript interfaces and type aliases from a compiled WSDL catalog
@@ -33,6 +32,7 @@ import { hasAttributeWildcards, wildcardAttributeBagName } from "../util/attribu
33
32
  *
34
33
  * @param {string} outFile - Path to the output TypeScript file
35
34
  * @param {CompiledCatalog} compiled - The compiled WSDL catalog
35
+ * @throws {Error} If a packaged preamble cannot be loaded or the TypeScript file cannot be written.
36
36
  */
37
37
  export function generateTypes(outFile, compiled) {
38
38
  const lines = [];
@@ -308,10 +308,5 @@ export function generateTypes(outFile, compiled) {
308
308
  lines.push("}");
309
309
  lines.push("");
310
310
  }
311
- try {
312
- fs.writeFileSync(outFile, lines.join("\n"), "utf8");
313
- }
314
- catch (e) {
315
- error(`Failed to write types to ${outFile}: ${e instanceof Error ? e.message : String(e)}`);
316
- }
311
+ writeGeneratedSource(outFile, lines.join("\n"), "client");
317
312
  }
@@ -16,6 +16,7 @@ import type { CompiledCatalog } from "../compiler/schemaCompiler.js";
16
16
  * @param {string} outFile - Path to the output TypeScript file
17
17
  * @param {CompiledCatalog} compiled - The compiled WSDL catalog containing metadata
18
18
  * @throws {Error} If metadata is missing or has an invalid structure
19
+ * @throws {Error} If a packaged preamble cannot be loaded or the TypeScript file cannot be written.
19
20
  */
20
21
  export declare function generateUtils(outFile: string, compiled: CompiledCatalog): void;
21
22
  //# sourceMappingURL=generateUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generateUtils.d.ts","sourceRoot":"","sources":["../../src/client/generateUtils.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,+BAA+B,CAAC;AAInE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,QAgCvE"}
1
+ {"version":3,"file":"generateUtils.d.ts","sourceRoot":"","sources":["../../src/client/generateUtils.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,+BAA+B,CAAC;AAGnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,QA4BvE"}
@@ -10,9 +10,8 @@
10
10
  * - A DataTypes interface defining the structure of the metadata
11
11
  * - A constant containing the actual metadata mappings extracted from the compiled catalog
12
12
  */
13
- import fs from "node:fs";
13
+ import { writeGeneratedSource } from "../generation/writeGeneratedSource.js";
14
14
  import { deriveClientName, pascalToSnakeCase } from "../util/tools.js";
15
- import { error } from "../util/cli.js";
16
15
  /**
17
16
  * Generates utility types and constants for XML serialization/deserialization
18
17
  *
@@ -30,6 +29,7 @@ import { error } from "../util/cli.js";
30
29
  * @param {string} outFile - Path to the output TypeScript file
31
30
  * @param {CompiledCatalog} compiled - The compiled WSDL catalog containing metadata
32
31
  * @throws {Error} If metadata is missing or has an invalid structure
32
+ * @throws {Error} If a packaged preamble cannot be loaded or the TypeScript file cannot be written.
33
33
  */
34
34
  export function generateUtils(outFile, compiled) {
35
35
  const clientName = deriveClientName(compiled);
@@ -54,10 +54,5 @@ export interface ${clientName}DataTypes {
54
54
  }
55
55
 
56
56
  export const ${clientConstant}_DATA_TYPES: ${clientName}DataTypes = ${metas} as const;\n`;
57
- try {
58
- fs.writeFileSync(outFile, dataTypes, "utf8");
59
- }
60
- catch (e) {
61
- error(`Failed to write utils to ${outFile}: ${e instanceof Error ? e.message : String(e)}`);
62
- }
57
+ writeGeneratedSource(outFile, dataTypes, "client");
63
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generators.d.ts","sourceRoot":"","sources":["../../src/gateway/generators.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generators.d.ts","sourceRoot":"","sources":["../../src/gateway/generators.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,UAAU,EAAE,KAAK,eAAe,EAAyE,MAAM,cAAc,CAAC;AAG3I;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA6BxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6IAA6I;IAC7I,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,MAAM,CAAC,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,QAAQ,GAAG,YAAY,CAAC;QAChC,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACtC,0BAA0B,EAAE,MAAM,EAAE,EACpC,iBAAiB,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,GAAG,EACvH,UAAU,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,MAAM,EACnC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GACnC,iBAAiB,EAAE,CAyJrB;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,IAAI,CAsBN;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,iBAAiB,EAAE,EAC/B,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAChC,IAAI,CA6DN;AA4BD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,GAAG,EACb,IAAI,CAAC,EAAE;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAC,GAC5B,IAAI,CAiQN;AA6ED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAChC,IAAI,CAuFN;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAChC,IAAI,CA6BN;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,iBAAiB,EAAE,EAC/B,WAAW,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,EACjC,UAAU,EAAE,UAAU,EACtB,OAAO,CAAC,EAAE,GAAG,GACZ,IAAI,CA6HN"}
@@ -11,6 +11,7 @@
11
11
  * All emitters follow deterministic generation rules for diff-friendly output.
12
12
  */
13
13
  import fs from "node:fs";
14
+ import { writeGeneratedSource } from "../generation/writeGeneratedSource.js";
14
15
  import path from "node:path";
15
16
  import { flattenAllOf, measureSchemaRefComplexity, rewriteSchemaRefs, slugName, } from "./helpers.js";
16
17
  import { detectArrayWrappers as detectArrayWrappersShared } from "../util/catalogMeta.js";
@@ -292,7 +293,7 @@ export function emitSchemasModule(outDir, modelsDir, versionSlug, serviceSlug) {
292
293
  schemasTs += ` fastify.addSchema(s as any);\n`;
293
294
  schemasTs += ` }\n`;
294
295
  schemasTs += `}\n`;
295
- fs.writeFileSync(path.join(outDir, "schemas.ts"), schemasTs, "utf8");
296
+ writeGeneratedSource(path.join(outDir, "schemas.ts"), schemasTs, "gateway");
296
297
  }
297
298
  /**
298
299
  * Emits individual route files and routes.ts aggregator module
@@ -360,7 +361,7 @@ import type { FastifyInstance } from "fastify";
360
361
  routeTs += ` }\n`;
361
362
  routeTs += ` });\n`;
362
363
  routeTs += `}\n`;
363
- fs.writeFileSync(path.join(routesDir, `${routeFileBase}.ts`), routeTs, "utf8");
364
+ writeGeneratedSource(path.join(routesDir, `${routeFileBase}.ts`), routeTs, "gateway/routes");
364
365
  });
365
366
  const routeFnName = `registerRoutes_${slugName(versionSlug)}_${slugName(serviceSlug)}`;
366
367
  routesTs += `\nexport async function ${routeFnName}(fastify: FastifyInstance) {\n`;
@@ -369,7 +370,7 @@ import type { FastifyInstance } from "fastify";
369
370
  routesTs += ` await ${fnName}(fastify);\n`;
370
371
  });
371
372
  routesTs += `}\n`;
372
- fs.writeFileSync(path.join(outDir, "routes.ts"), routesTs, "utf8");
373
+ writeGeneratedSource(path.join(outDir, "routes.ts"), routesTs, "gateway/routes");
373
374
  }
374
375
  /**
375
376
  * Emits runtime.ts module with envelope builders and error handling utilities
@@ -647,7 +648,7 @@ export function createGatewayErrorHandler_${vSlug}_${sSlug}() {
647
648
  }
648
649
  `;
649
650
  const streamSection = opts?.hasStreams ? buildStreamRuntimeSection() : "";
650
- fs.writeFileSync(path.join(outDir, "runtime.ts"), runtimeTs + unwrapSection + streamSection, "utf8");
651
+ writeGeneratedSource(path.join(outDir, "runtime.ts"), runtimeTs + unwrapSection + streamSection, "gateway/runtime");
651
652
  }
652
653
  /**
653
654
  * Returns the streaming helpers block for runtime.ts.
@@ -822,7 +823,7 @@ export default fp(${sSlug}GatewayPlugin, {
822
823
  // Named export for convenience
823
824
  export { ${sSlug}GatewayPlugin };
824
825
  `;
825
- fs.writeFileSync(path.join(outDir, "plugin.ts"), pluginTs, "utf8");
826
+ writeGeneratedSource(path.join(outDir, "plugin.ts"), pluginTs, "gateway");
826
827
  }
827
828
  /**
828
829
  * Emits a type-check fixture that verifies plugin-client type compatibility
@@ -861,7 +862,7 @@ function _assertClientCompatible(client: ${clientMeta.className}): void {
861
862
  }
862
863
  void _assertClientCompatible;
863
864
  `;
864
- fs.writeFileSync(path.join(outDir, "_typecheck.ts"), content, "utf8");
865
+ writeGeneratedSource(path.join(outDir, "_typecheck.ts"), content, "gateway");
865
866
  }
866
867
  /**
867
868
  * Emits individual route files with full handler implementations
@@ -973,7 +974,7 @@ ${handlerBody}
973
974
  });
974
975
  }
975
976
  `;
976
- fs.writeFileSync(path.join(routesDir, `${routeFileBase}.ts`), routeTs, "utf8");
977
+ writeGeneratedSource(path.join(routesDir, `${routeFileBase}.ts`), routeTs, "gateway/routes");
977
978
  });
978
979
  // Generate routes.ts aggregator module
979
980
  const routeFnName = `registerRoutes_${vSlug}_${sSlug}`;
@@ -991,5 +992,5 @@ export async function ${routeFnName}(fastify: FastifyInstance): Promise<void> {
991
992
  }).join("\n");
992
993
  routesTs += ` // Register all routes\n${routeCalls}\n`;
993
994
  routesTs += `}\n`;
994
- fs.writeFileSync(path.join(outDir, "routes.ts"), routesTs, "utf8");
995
+ writeGeneratedSource(path.join(outDir, "routes.ts"), routesTs, "gateway/routes");
995
996
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Creates an internal resolver with an isolated cache for immutable resources.
3
+ * @param root - Absolute resource root; production uses only the module-relative packaged root.
4
+ * @returns Resolver selecting the nearest nonempty preamble, never concatenating resources.
5
+ * @throws If a scope is unsafe or a required/selected resource cannot be read.
6
+ */
7
+ export declare function createPreambleResolver(root?: string): (artifact: string) => string;
8
+ export declare const resolvePreamble: (artifact: string) => string;
9
+ /**
10
+ * Prefixes a fresh complete source body, preserving its shebang and remaining bytes.
11
+ * @param body - Generator body, not an existing output file.
12
+ * @param preamble - Validated comment-only resource.
13
+ * @returns BOM-free source with one supplied preamble and a separating blank line.
14
+ * @constraints Embedded fragments must not pass through this composer separately.
15
+ */
16
+ export declare function composeGeneratedSource(body: string, preamble: string): string;
17
+ //# sourceMappingURL=preamble.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preamble.d.ts","sourceRoot":"","sources":["../../src/generation/preamble.ts"],"names":[],"mappings":"AAWA;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,MAAqB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAmChG;AAED,eAAO,MAAM,eAAe,aArCoD,MAAM,KAAK,MAqCpC,CAAC;AAExD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO7E"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Resolves trusted packaged TypeScript preambles by logical artifact scope.
3
+ * @since 1.1.0
4
+ * @constraints Resource content is validated by package tooling, without a runtime TypeScript dependency.
5
+ */
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ const packagedRoot = fileURLToPath(new URL("../../src/generation/preambles/", import.meta.url));
10
+ /**
11
+ * Creates an internal resolver with an isolated cache for immutable resources.
12
+ * @param root - Absolute resource root; production uses only the module-relative packaged root.
13
+ * @returns Resolver selecting the nearest nonempty preamble, never concatenating resources.
14
+ * @throws If a scope is unsafe or a required/selected resource cannot be read.
15
+ */
16
+ export function createPreambleResolver(root = packagedRoot) {
17
+ const absoluteRoot = path.resolve(root);
18
+ const cache = new Map();
19
+ function readResource(file, optional) {
20
+ try {
21
+ if (!fs.statSync(file).isFile())
22
+ throw new Error("Resource is not a regular file");
23
+ const content = fs.readFileSync(file, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
24
+ if (!content.trim())
25
+ throw new Error("Resource is empty");
26
+ return content;
27
+ }
28
+ catch (cause) {
29
+ if (optional && cause.code === "ENOENT")
30
+ return undefined;
31
+ throw new Error(`Could not load TypeScript preamble ${file}: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
32
+ }
33
+ }
34
+ return (artifact) => {
35
+ if (!/^[a-z][a-z0-9-]*(\/[a-z][a-z0-9-]*)*$/.test(artifact)) {
36
+ throw new Error(`Invalid preamble artifact scope: ${artifact}`);
37
+ }
38
+ const cached = cache.get(artifact);
39
+ if (cached !== undefined)
40
+ return cached;
41
+ const rootFile = path.join(absoluteRoot, "typescript.preamble");
42
+ const fallback = cache.get("") ?? readResource(rootFile, false);
43
+ cache.set("", fallback);
44
+ const segments = artifact.split("/");
45
+ while (segments.length > 0) {
46
+ const selected = readResource(path.join(absoluteRoot, ...segments, "typescript.preamble"), true);
47
+ if (selected !== undefined) {
48
+ cache.set(artifact, selected);
49
+ return selected;
50
+ }
51
+ segments.pop();
52
+ }
53
+ cache.set(artifact, fallback);
54
+ return fallback;
55
+ };
56
+ }
57
+ export const resolvePreamble = createPreambleResolver();
58
+ /**
59
+ * Prefixes a fresh complete source body, preserving its shebang and remaining bytes.
60
+ * @param body - Generator body, not an existing output file.
61
+ * @param preamble - Validated comment-only resource.
62
+ * @returns BOM-free source with one supplied preamble and a separating blank line.
63
+ * @constraints Embedded fragments must not pass through this composer separately.
64
+ */
65
+ export function composeGeneratedSource(body, preamble) {
66
+ const source = body.replace(/^\uFEFF/, "");
67
+ const header = preamble.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n").replace(/\n+$/, "") + "\n\n";
68
+ if (!source.startsWith("#!"))
69
+ return header + source;
70
+ const shebang = source.match(/^#![^\r\n]*(?:\r\n|\r|\n|$)/)[0];
71
+ const separator = /[\r\n]$/.test(shebang) ? "" : "\n";
72
+ return shebang + separator + header + source.slice(shebang.length);
73
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Writes a generated TypeScript file after resolving its packaged preamble.
3
+ * @param filePath - Destination whose parent directory already exists.
4
+ * @param content - Fresh complete source body.
5
+ * @param artifact - Logical scope, independent of the consumer output directory.
6
+ * @returns Nothing.
7
+ * @throws If resource resolution, composition, or writing fails.
8
+ * @constraints Call only after any scaffold skip/force guard.
9
+ */
10
+ export declare function writeGeneratedSource(filePath: string, content: string, artifact: string): void;
11
+ //# sourceMappingURL=writeGeneratedSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeGeneratedSource.d.ts","sourceRoot":"","sources":["../../src/generation/writeGeneratedSource.ts"],"names":[],"mappings":"AAOA;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAO9F"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Applies output policy once at the complete TypeScript file boundary.
3
+ * @since 1.1.0
4
+ */
5
+ import fs from "node:fs";
6
+ import { composeGeneratedSource, resolvePreamble } from "./preamble.js";
7
+ /**
8
+ * Writes a generated TypeScript file after resolving its packaged preamble.
9
+ * @param filePath - Destination whose parent directory already exists.
10
+ * @param content - Fresh complete source body.
11
+ * @param artifact - Logical scope, independent of the consumer output directory.
12
+ * @returns Nothing.
13
+ * @throws If resource resolution, composition, or writing fails.
14
+ * @constraints Call only after any scaffold skip/force guard.
15
+ */
16
+ export function writeGeneratedSource(filePath, content, artifact) {
17
+ try {
18
+ const source = composeGeneratedSource(content, resolvePreamble(artifact));
19
+ fs.writeFileSync(filePath, source, "utf8");
20
+ }
21
+ catch (cause) {
22
+ throw new Error(`Failed to generate ${filePath} (${artifact} preamble): ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
23
+ }
24
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"generateTests.d.ts","sourceRoot":"","sources":["../../src/test/generateTests.ts"],"names":[],"mappings":"AAiCA;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAuBD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiK7E"}
1
+ {"version":3,"file":"generateTests.d.ts","sourceRoot":"","sources":["../../src/test/generateTests.ts"],"names":[],"mappings":"AAkCA;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAuBD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiK7E"}
@@ -12,6 +12,7 @@
12
12
  * 5. For each file: skip-if-exists check, then write
13
13
  */
14
14
  import fs from "node:fs";
15
+ import { writeGeneratedSource } from "../generation/writeGeneratedSource.js";
15
16
  import path from "node:path";
16
17
  import { info, success } from "../util/cli.js";
17
18
  import { resolveClientMeta, resolveOperationMeta, slugName } from "../gateway/helpers.js";
@@ -33,11 +34,11 @@ function shouldWriteTestFile(filePath, force) {
33
34
  /**
34
35
  * Writes a test file if it should be written (skip-if-exists with force override).
35
36
  */
36
- function writeTestFile(filePath, content, force) {
37
+ function writeTestFile(filePath, content, force, artifact = "test") {
37
38
  if (!shouldWriteTestFile(filePath, force))
38
39
  return;
39
40
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
40
- fs.writeFileSync(filePath, content, "utf-8");
41
+ writeGeneratedSource(filePath, content, artifact);
41
42
  }
42
43
  /**
43
44
  * Generates a complete Vitest test suite for the generated gateway artifacts.
@@ -117,25 +118,25 @@ export async function generateTests(opts) {
117
118
  // Emit vitest.config.ts
118
119
  writeTestFile(path.join(testDir, "vitest.config.ts"), emitVitestConfig(), force);
119
120
  // Emit helpers/mock-client.ts
120
- writeTestFile(path.join(testDir, "helpers", "mock-client.ts"), emitMockClientHelper(testDir, clientDir, importsMode, clientMeta, operations, mocks), force);
121
+ writeTestFile(path.join(testDir, "helpers", "mock-client.ts"), emitMockClientHelper(testDir, clientDir, importsMode, clientMeta, operations, mocks), force, "test/helpers");
121
122
  // Emit helpers/test-app.ts
122
- writeTestFile(path.join(testDir, "helpers", "test-app.ts"), emitTestAppHelper(testDir, gatewayDir, importsMode, clientMeta), force);
123
+ writeTestFile(path.join(testDir, "helpers", "test-app.ts"), emitTestAppHelper(testDir, gatewayDir, importsMode, clientMeta), force, "test/helpers");
123
124
  // Emit gateway/routes.test.ts
124
- writeTestFile(path.join(testDir, "gateway", "routes.test.ts"), emitRoutesTest(testDir, importsMode, operations, mocks), force);
125
+ writeTestFile(path.join(testDir, "gateway", "routes.test.ts"), emitRoutesTest(testDir, importsMode, operations, mocks), force, "test/gateway");
125
126
  // Emit gateway/errors.test.ts
126
- writeTestFile(path.join(testDir, "gateway", "errors.test.ts"), emitErrorsTest(testDir, importsMode, operations, mocks), force);
127
+ writeTestFile(path.join(testDir, "gateway", "errors.test.ts"), emitErrorsTest(testDir, importsMode, operations, mocks), force, "test/gateway");
127
128
  // Emit gateway/envelope.test.ts
128
- writeTestFile(path.join(testDir, "gateway", "envelope.test.ts"), emitEnvelopeTest(testDir, importsMode, operations, mocks), force);
129
+ writeTestFile(path.join(testDir, "gateway", "envelope.test.ts"), emitEnvelopeTest(testDir, importsMode, operations, mocks), force, "test/gateway");
129
130
  // Emit gateway/validation.test.ts
130
- writeTestFile(path.join(testDir, "gateway", "validation.test.ts"), emitValidationTest(testDir, importsMode, operations, mocks, catalog), force);
131
+ writeTestFile(path.join(testDir, "gateway", "validation.test.ts"), emitValidationTest(testDir, importsMode, operations, mocks, catalog), force, "test/gateway");
131
132
  // Emit runtime/classify-error.test.ts
132
- writeTestFile(path.join(testDir, "runtime", "classify-error.test.ts"), emitClassifyErrorTest(testDir, gatewayDir, importsMode), force);
133
+ writeTestFile(path.join(testDir, "runtime", "classify-error.test.ts"), emitClassifyErrorTest(testDir, gatewayDir, importsMode), force, "test/runtime");
133
134
  // Emit runtime/envelope-builders.test.ts
134
- writeTestFile(path.join(testDir, "runtime", "envelope-builders.test.ts"), emitEnvelopeBuildersTest(testDir, gatewayDir, importsMode), force);
135
+ writeTestFile(path.join(testDir, "runtime", "envelope-builders.test.ts"), emitEnvelopeBuildersTest(testDir, gatewayDir, importsMode), force, "test/runtime");
135
136
  // Emit runtime/unwrap.test.ts (conditional: only when ArrayOf* wrappers exist)
136
137
  const unwrapContent = emitUnwrapTest(testDir, gatewayDir, importsMode, catalog);
137
138
  if (unwrapContent) {
138
- writeTestFile(path.join(testDir, "runtime", "unwrap.test.ts"), unwrapContent, force);
139
+ writeTestFile(path.join(testDir, "runtime", "unwrap.test.ts"), unwrapContent, force, "test/runtime");
139
140
  }
140
141
  success(`Test suite generated in ${testDir}`);
141
142
  }
@@ -4,6 +4,8 @@ All CLI commands are available as TypeScript functions. This document covers eac
4
4
 
5
5
  See the main [README](../README.md) for installation, CLI usage, and project overview.
6
6
 
7
+ All file-producing APIs automatically apply [TypeScript preambles](generated-code.md#generated-preambles-and-eslint). Signatures and options are unchanged; existing scaffold skip/force behavior still applies. Resource and TypeScript file-write failures propagate to callers.
8
+
7
9
  ## compileWsdlToProject
8
10
 
9
11
  Generate a TypeScript SOAP client from WSDL.
@@ -4,6 +4,16 @@ Internal architecture of the wsdl-tsc code generator for contributors.
4
4
 
5
5
  See [CONTRIBUTING](../CONTRIBUTING.md) for development setup and [README](../README.md) for user documentation.
6
6
 
7
+ ## Generated Source Policy
8
+
9
+ Complete TypeScript writes pass through `src/generation/writeGeneratedSource.ts` after any scaffold ownership guard. Runtime/template loaders remain fragment loaders; provenance and documentation are preserved when the final body receives its preamble.
10
+
11
+ `src/generation/preamble.ts` resolves trusted raw resources relative to this package's module location, never the consumer working directory. Logical scopes use nearest-resource fallback: `gateway/routes/typescript.preamble`, then `gateway/typescript.preamble`, then the required root `typescript.preamble`. One resource wins; resources are not concatenated. App and test overrides describe editable scaffolds.
12
+
13
+ Successful resource resolutions are cached per resolver and logical scope for the process lifetime. A missing optional resource falls back, but a broken selected resource or missing required root fails generation. The writer preserves shebang placement and body text while normalizing preamble line endings and removing leading BOMs.
14
+
15
+ Package validation uses the development TypeScript compiler to reject non-comment content and compiler-control directives. The generator runtime does not load that compiler. Raw `.preamble` files are included in npm packages, and `package:smoke` verifies installed generation outside the checkout during full CI. No custom resource roots or new public options are exposed.
16
+
7
17
  ## Pipeline Overview
8
18
 
9
19
  ```text
@@ -8,6 +8,8 @@ Generation paths are explicit. Examples use the git-ignored `.generated/` direct
8
8
 
9
9
  The tool provides six commands for different integration scenarios.
10
10
 
11
+ TypeScript outputs automatically receive [generated preambles](generated-code.md#generated-preambles-and-eslint), including app/test scaffolds when actually written. Command syntax is unchanged and there is no preamble flag. `compile` and `openapi` data outputs remain unchanged.
12
+
11
13
  | Command | Purpose | Typical Use Case |
12
14
  |---------|---------|------------------|
13
15
  | pipeline | Full pipeline: client + OpenAPI + gateway + app | CI/CD automation, complete stack generation |
@@ -2,6 +2,12 @@
2
2
 
3
3
  Contributor guide for naming files and organizing fixture paths. See the root [README.md](../README.md) for the authoritative documentation index.
4
4
 
5
+ ## Preamble Resources
6
+
7
+ Raw comment-only headers live under `src/generation/preambles/` with the filename `typescript.preamble`. Subdirectories express logical artifact scopes rather than consumer output paths. See [generated source policy](architecture.md#generated-source-policy) for fallback and packaging rules.
8
+
9
+ Package-smoke tarballs and logs belong in `tmp/preflight/package/`; the isolated consumer installation uses a verified temporary directory outside the checkout to prove that package resources resolve independently.
10
+
5
11
  ## Purpose
6
12
 
7
13
  Paths should classify files before a reader opens them. The left side of a path carries the broadest concept; each segment to the right narrows the classification until the filename states the exact artifact purpose.
@@ -4,6 +4,106 @@ Guide to using the TypeScript SOAP clients and types generated by wsdl-tsc.
4
4
 
5
5
  See [README](../README.md) for quick start and [Concepts](concepts.md) for type modeling details.
6
6
 
7
+ ## Generated Preambles and ESLint
8
+
9
+ Every newly written TypeScript client, gateway, app, and generated-test file starts with `/* eslint-disable */`. This includes conditional streaming/security helpers, stub routes, and gateway typecheck fixtures. CLI and programmatic generation use the same defaults with no new flags or required arguments.
10
+
11
+ Generator-owned client and gateway files receive this preamble:
12
+
13
+ ```typescript
14
+ /* eslint-disable */
15
+ // Generated by @techspokes/typescript-wsdl-client.
16
+ // Do not edit manually. Regenerate with wsdl-tsc.
17
+ ```
18
+
19
+ Editable app and test scaffolds receive distinct ownership guidance:
20
+
21
+ ```typescript
22
+ /* eslint-disable */
23
+ // Scaffold generated by @techspokes/typescript-wsdl-client.
24
+ // Customize this file. Remove eslint-disable when adopting it as maintained source.
25
+ ```
26
+
27
+ Existing scaffolds are not rewritten unless their existing `--force-init` or `--force-test` option explicitly requests regeneration. Remove the directive when adopting a scaffold as maintained source, and keep that source outside globally ignored generated directories. JSON, YAML, Markdown, configuration data, and environment files do not receive preambles.
28
+
29
+ ### Suppression Versus Exclusion
30
+
31
+ The directive suppresses ESLint rule reports when inline configuration is allowed; ESLint still parses the file. It cannot fix syntax errors, missing TypeScript parsers, or parser project-configuration errors. TypeScript compilation and checking remain enabled.
32
+
33
+ `noInlineConfig: true` or `--no-inline-config` prevents the suppression from taking effect. An otherwise clean file may produce an unused-disable warning or error under `reportUnusedDisableDirectives`. Keep corporate lint policies for maintained source and globally ignore disposable generated paths when those policies conflict with the directive.
34
+
35
+ ### Ignore Generated Directories
36
+
37
+ For projects using `eslint`, `@eslint/js`, and `typescript-eslint`, this flat configuration excludes generated client and gateway directories:
38
+
39
+ ```javascript
40
+ // eslint.config.mjs
41
+ import js from "@eslint/js";
42
+ import { defineConfig, globalIgnores } from "eslint/config";
43
+ import tseslint from "typescript-eslint";
44
+
45
+ export default defineConfig([
46
+ globalIgnores([".generated/client/**", ".generated/gateway/**"]),
47
+ js.configs.recommended,
48
+ tseslint.configs.recommended,
49
+ ]);
50
+ ```
51
+
52
+ Add the ignore entry to an existing configuration while retaining its presets, plugins, parser, and rules. Use `.generated/**` only when that entire directory is disposable; adopted app/test scaffolds should remain linted. Monorepos can scope generated paths explicitly, for example `packages/*/.generated/client/**`.
53
+
54
+ An ignores-only entry also works as a separate item in the configuration array:
55
+
56
+ ```javascript
57
+ const generatedIgnores = {
58
+ ignores: [".generated/client/**", ".generated/gateway/**"],
59
+ };
60
+ ```
61
+
62
+ Do not add `files`, `rules`, or `languageOptions` to this entry and expect it to remain a global exclusion. Modern flat configuration does not automatically load `.eslintignore`; the generator does not create or rewrite consumer ESLint configuration.
63
+
64
+ ### Mixed Source Directories
65
+
66
+ When generated output shares a directory with maintained code, add an explicit ignore entry instead of broad patterns such as `**/client.ts`:
67
+
68
+ ```javascript
69
+ import { globalIgnores } from "eslint/config";
70
+
71
+ const generatedIgnores = globalIgnores([
72
+ "src/integrations/weather/client.ts",
73
+ "src/integrations/weather/types.ts",
74
+ "src/integrations/weather/operations.ts",
75
+ "src/integrations/weather/utils.ts",
76
+ "src/integrations/weather/gateway/**",
77
+ ]);
78
+ ```
79
+
80
+ Include any conditional generated helpers, such as `streamXml.ts`, in your explicit paths. Dedicated generated directories make this easier to maintain.
81
+
82
+ ### Strict Policies and Hooks
83
+
84
+ Global ignores can coexist with this separate policy entry for maintained source:
85
+
86
+ ```javascript
87
+ const inlineConfigPolicy = {
88
+ linterOptions: {
89
+ noInlineConfig: true,
90
+ reportUnusedDisableDirectives: "error",
91
+ },
92
+ };
93
+ ```
94
+
95
+ For a single invocation, use `npx eslint . --ignore-pattern ".generated/client/**" --ignore-pattern ".generated/gateway/**"`. Normal configuration patterns are relative to the configuration file; CLI patterns and patterns from an alternate `--config` resolve against the working directory.
96
+
97
+ Hooks that pass ignored files explicitly may produce warnings. With the ignore configuration already installed, use:
98
+
99
+ ```bash
100
+ npx eslint --max-warnings 0 --no-warn-ignored .generated/client/client.ts src/app.ts
101
+ ```
102
+
103
+ `--no-warn-ignored` does not create exclusions or hide lint failures in `src/app.ts`. It does not suppress unused-disable diagnostics on files ESLint actually lints.
104
+
105
+ See ESLint's [ignore documentation](https://eslint.org/docs/latest/use/configure/ignore) and [configuration documentation](https://eslint.org/docs/latest/use/configure/configuration-files) for pattern and policy details.
106
+
7
107
  ## Client Construction
8
108
 
9
109
  ```typescript
@@ -2,6 +2,10 @@
2
2
 
3
3
  This document describes each file produced by the generator, what it contains, and how to work with it. For CLI flags that control output, see [CLI Reference](cli-reference.md).
4
4
 
5
+ ## TypeScript File Preambles
6
+
7
+ All complete emitted TypeScript files receive one [generated preamble](generated-code.md#generated-preambles-and-eslint), including optional helpers and `_typecheck.ts`. App and generated-test scaffolds use editable-source wording and retain their existing skip/force behavior. Data outputs and existing consumer source remain unchanged.
8
+
5
9
  ## Generation Flow
6
10
 
7
11
  The pipeline follows this sequence: WSDL source is parsed and compiled into a catalog, then each emitter reads the catalog and produces its output.
@@ -0,0 +1,42 @@
1
+ # TypeScript WSDL Client v1.1.0
2
+
3
+ ## Generated TypeScript with explicit lint ownership
4
+
5
+ This minor release adds automatic ESLint suppression preambles to newly emitted TypeScript and documents path exclusions for consumers with restrictive lint policies.
6
+
7
+ ## What This Improves
8
+
9
+ Regenerated client and gateway files carry their ESLint directive automatically, eliminating repeated manual edits. App and generated-test scaffolds use distinct wording that explains when to remove suppression as those files become maintained source.
10
+
11
+ Preambles are packaged resources selected by logical artifact scope and applied at the final file-write boundary. Resource validation and installed-package consumer tests ensure the behavior works outside the repository checkout, including conditional helpers and programmatic generation.
12
+
13
+ ## Highlights
14
+
15
+ - Add one preamble to every complete generated TypeScript file, including streaming helpers and stub routes.
16
+ - Preserve existing app/test files unless their existing force option requests regeneration.
17
+ - Propagate TypeScript generation failures instead of logging write failures as successful generation.
18
+ - Document consumer global ignores, strict inline policies, parser limitations, and hook behavior.
19
+ - Verify real ESLint suppression alongside maintained-source controls and installed-package tests.
20
+
21
+ ## Upgrade Notes
22
+
23
+ CLI syntax, public API signatures, catalog semantics, and runtime behavior are unchanged. Regeneration adds a comment preamble to TypeScript output; existing editable scaffolds retain their current contents unless explicitly regenerated.
24
+
25
+ `/* eslint-disable */` suppresses rule reports only when inline configuration is allowed. It does not bypass parsing or TypeScript checking, and clean files may produce unused-disable diagnostics. Use the [consumer ESLint guidance](../generated-code.md#generated-preambles-and-eslint) to exclude generated paths while keeping maintained-source policies intact.
26
+
27
+ Remove the suppression when adopting an app or test scaffold as maintained source, and keep adopted files outside ignored generated directories. No runtime or schema migration is required.
28
+
29
+ ## Validation
30
+
31
+ - Generated-file, resource-validation, and ESLint tests passed on Node 24 and Node 26.
32
+ - Full CI, generated-artifact conformance, and pipeline smoke validation passed.
33
+ - Installed tarball generation, consumer typechecking, and generated tests passed outside the checkout.
34
+ - NPM package contents and comment-only resources were validated.
35
+ - The agent skill artifact was validated and packaged with the new consumer guidance.
36
+ - Release preflight passed against the target tag.
37
+
38
+ ## Notes
39
+
40
+ Release tag: `v1.1.0`.
41
+
42
+ Implements [issue #142](https://github.com/TechSpokes/typescript-wsdl-client/issues/142).
package/docs/testing.md CHANGED
@@ -4,6 +4,8 @@ Guide to running and writing tests for wsdl-tsc development and for testing gene
4
4
 
5
5
  See [README](../README.md) for quick start and [CONTRIBUTING](../CONTRIBUTING.md) for development setup.
6
6
 
7
+ Generated test files receive an [editable scaffold preamble](generated-code.md#generated-preambles-and-eslint). Remove its ESLint suppression when adopting the tests as maintained source; existing files are preserved unless `--force-test` is used. Full local CI includes `package:smoke`, which installs a tarball outside the checkout and validates generation, consumer compilation, and generated tests.
8
+
7
9
  ## Test Architecture
8
10
 
9
11
  The project uses three layers of testing:
@@ -4,6 +4,14 @@ Common issues and debugging guidance for wsdl-tsc.
4
4
 
5
5
  See [README](../README.md) for quick start and [CLI Reference](cli-reference.md) for command details.
6
6
 
7
+ ## Generated Files and ESLint
8
+
9
+ If the generated directive appears ineffective, check `noInlineConfig` and `--no-inline-config`. Unused-disable diagnostics can occur on files with no rule violations. Parser or TypeScript project errors are not rule reports and cannot be suppressed by the header.
10
+
11
+ Use the [generated-code ESLint guidance](generated-code.md#generated-preambles-and-eslint) for global exclusions that preserve maintained-source policies, explicit-file hook warnings, and scaffold adoption. Do not disable TypeScript checking or weaken lint policy globally to accommodate generated output.
12
+
13
+ A missing, empty, or unreadable packaged preamble fails generation with the resource and destination path. Reinstall the intact package; do not edit `node_modules` or patch generated files to work around a damaged installation.
14
+
7
15
  ## Common Issues
8
16
 
9
17
  | Symptom | Resolution |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techspokes/typescript-wsdl-client",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "description": "Turn legacy WSDL/SOAP services into typed TypeScript clients, OpenAPI 3.1 specs, and production-ready Fastify REST gateways. Built for enterprise SOAP modernization.",
5
5
  "keywords": [
6
6
  "wsdl",
@@ -57,7 +57,8 @@
57
57
  "LICENSE",
58
58
  "llms.txt",
59
59
  "src/runtime/*.ts",
60
- "src/runtime/*.tpl.txt"
60
+ "src/runtime/*.tpl.txt",
61
+ "src/generation/preambles/**/*.preamble"
61
62
  ],
62
63
  "scripts": {
63
64
  "dev": "tsx src/cli.ts",
@@ -95,22 +96,26 @@
95
96
  "skill:validate": "node scripts/validate-agent-skill.mjs",
96
97
  "skill:package": "node scripts/build-agent-skill.mjs",
97
98
  "package:validate": "node scripts/validate-npm-package.mjs",
99
+ "package:smoke": "node scripts/smoke-npm-package.mjs",
98
100
  "release:preflight": "node scripts/release-preflight.mjs",
99
101
  "release:publish-check": "npm run clean && npm run build && npm run typecheck && npm run skill:validate && npm run package:validate",
100
- "ci": "npm run clean && npm run build && npm run typecheck && npm run skill:validate && npm run package:validate && npm run docs:validate && vitest run && npm run smoke:pipeline",
102
+ "ci": "npm run clean && npm run build && npm run typecheck && npm run skill:validate && npm run package:validate && npm run docs:validate && vitest run && npm run smoke:pipeline && npm run package:smoke",
101
103
  "ci:github": "npm run clean && npm run build && npm run typecheck && npm run skill:validate && npm run package:validate && npm run docs:validate && npm run test:github && npm run smoke:pipeline",
102
104
  "ci:github:node26": "npm run clean && npm run build && npm run typecheck && npm run test:unit && npm run smoke:pipeline",
103
105
  "examples:regenerate": "tsx src/cli.ts pipeline --wsdl-source examples/minimal/weather.wsdl --client-dir examples/generated-output/client --openapi-file examples/generated-output/openapi.json --gateway-dir examples/generated-output/gateway --gateway-service-name weather --gateway-version-prefix v1 --openapi-format json"
104
106
  },
105
107
  "devDependencies": {
108
+ "@eslint/js": "^10.0.1",
106
109
  "@types/js-yaml": "^4.0.9",
107
110
  "@types/node": "^26.6.2",
108
111
  "@types/yargs": "^17.0.35",
112
+ "eslint": "^10.11.0",
109
113
  "fastify": "^5.12.5",
110
114
  "fastify-plugin": "^6.0.0",
111
115
  "rimraf": "^6.1.3",
112
116
  "tsx": "^4.23.15",
113
117
  "typescript": "^6.0.3",
118
+ "typescript-eslint": "^8.70.1",
114
119
  "vitest": "^5.0.1"
115
120
  },
116
121
  "dependencies": {
@@ -0,0 +1,3 @@
1
+ /* eslint-disable */
2
+ // Scaffold generated by @techspokes/typescript-wsdl-client.
3
+ // Customize this file. Remove eslint-disable when adopting it as maintained source.
@@ -0,0 +1,3 @@
1
+ /* eslint-disable */
2
+ // Scaffold generated by @techspokes/typescript-wsdl-client.
3
+ // Customize this file. Remove eslint-disable when adopting it as maintained source.
@@ -0,0 +1,3 @@
1
+ /* eslint-disable */
2
+ // Generated by @techspokes/typescript-wsdl-client.
3
+ // Do not edit manually. Regenerate with wsdl-tsc.