@aligent/nx-openapi 2.0.1 → 2.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.
package/README.md CHANGED
@@ -24,6 +24,11 @@ Run `nx g @aligent/nx-openapi:client` with optional flags:
24
24
  - `--configPath` path to the redocly config file responsible for authentication when fetching a schema remotely. For more information: https://openapi-ts.dev/cli#auth.
25
25
  - `--skipValidate` If passed, this will skip schema pre-validation. Only do this if you have good reason to - not validating the schema beforehand may produce unpredictable results (either things may not generate at all or they may generate something that is incorrect).
26
26
  - `--override` Override the schema (and the generated type) of an existing client.
27
+ - `--authMethod` Authentication method for the generated client middleware. Defaults to `api-key`. Available options:
28
+ - `api-key` - API key authentication using SSM Parameter Store
29
+ - `oauth1.0a` - OAuth 1.0a authentication
30
+ - `basic` - Basic authentication (username/password)
31
+ - `oauth2.0` - OAuth 2.0 token-based authentication
27
32
 
28
33
  **:rotating_light: Do not edit the files in the `/generated` folder after generating a client. These files are generated using the OpenAPI Schema and editing them may put you at risk of no longer conforming to the specifications of the API you are using! :rotating_light:**
29
34
 
@@ -73,4 +78,9 @@ A `types` file that will contain several typescript interfaces depending on the
73
78
  - `operations`: The defined operations that can be performed on a given path. [More information.](https://swagger.io/docs/specification/v3_0/paths-and-operations/#operations)
74
79
  - `components`: A way of avoid duplication when paths or operations contain the same structure in their responses. Components define a structure that is used multiple time throughout the API. [More information.](https://swagger.io/docs/specification/v3_0/components/)
75
80
 
76
- A `client` file that will contain some commented, boilerplate code to help you get started.
81
+ A `client` file that will contain boilerplate code to help you get started. The client includes authentication middleware configured based on the `--authMethod` flag:
82
+
83
+ - **api-key**: Configured to fetch credentials from AWS SSM Parameter Store
84
+ - **oauth1.0a**: Configured with HMAC-SHA256 algorithm and placeholder credentials
85
+ - **basic**: Configured with placeholder username/password credentials
86
+ - **oauth2.0**: Configured with placeholder token and Bearer token type
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aligent/nx-openapi",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "type": "commonjs",
5
5
  "main": "./src/index.js",
6
6
  "typings": "./src/index.d.ts",
@@ -10,9 +10,10 @@
10
10
  "directory": "packages/nx-openapi"
11
11
  },
12
12
  "dependencies": {
13
- "@nx/devkit": "22.4.1",
13
+ "@nx/devkit": "22.4.2",
14
14
  "@redocly/openapi-core": "^1.34.2",
15
15
  "openapi-typescript": "7.7.3",
16
+ "ts-morph": "^27.0.2",
16
17
  "typescript": "^5.9.3"
17
18
  },
18
19
  "generators": "./generators.json",
@@ -6,18 +6,15 @@
6
6
  * - For more information about openapi-fetch middlewares: https://openapi-ts.dev/openapi-fetch/middleware-auth#middleware
7
7
  */
8
8
  import {
9
- apiKeyAuthMiddleware,
10
- fetchSsmParams,
11
9
  retryMiddleware,
12
10
  } from '@aligent/microservice-util-lib';
13
11
  import createClient, { Client, ClientOptions } from 'openapi-fetch';
14
12
  import { paths } from './generated-types';
15
13
 
16
14
  export class <%= className %> {
17
- private credential: string | null = null;
18
15
  public readonly client: Client<paths, `${string}/${string}`>;
19
16
 
20
- constructor(options: ClientOptions, credentialPath: string) {
17
+ constructor(options: ClientOptions) {
21
18
  this.client = createClient<paths>(options);
22
19
 
23
20
  /**
@@ -25,23 +22,6 @@ export class <%= className %> {
25
22
  * - For requests, onRequest() will be called in the order registered
26
23
  * - For responses, onResponse() will be called in reverse order.
27
24
  */
28
- this.client.use(
29
- apiKeyAuthMiddleware({
30
- header: 'Authorization',
31
- value: async () => {
32
- if (!this.credential) {
33
- const param = await fetchSsmParams(credentialPath);
34
- if (!param?.Value) {
35
- throw new Error('Unable to fetch API client credential');
36
- }
37
-
38
- this.credential = param.Value;
39
- }
40
-
41
- return `Bearer ${this.credential}`;
42
- },
43
- })
44
- );
45
25
 
46
26
  this.client.use(
47
27
  retryMiddleware({
@@ -4,11 +4,12 @@ exports.clientGenerator = clientGenerator;
4
4
  const devkit_1 = require("@nx/devkit");
5
5
  const generate_openapi_types_1 = require("../../helpers/generate-openapi-types");
6
6
  const utilities_1 = require("../../helpers/utilities");
7
+ const auth_configurations_1 = require("./helpers/auth-configurations");
7
8
  const VALID_EXTENSIONS = ['yaml', 'yml', 'json'];
8
9
  // We also use this as the project root for all generated clients
9
10
  const PROJECT_NAME = 'clients';
10
11
  async function clientGenerator(tree, options) {
11
- const { name, schemaPath, importPath = `@clients`, skipValidate, override } = options;
12
+ const { name, schemaPath, importPath = `@clients`, skipValidate, override, authMethod = 'api-key', } = options;
12
13
  const ext = schemaPath.split('.').pop() || '';
13
14
  if (!VALID_EXTENSIONS.includes(ext)) {
14
15
  throw new Error(`Invalid schema file extension: ${ext}`);
@@ -43,9 +44,13 @@ async function clientGenerator(tree, options) {
43
44
  * Each time we add new API client, we actually add a new class into `clients` project (if it exists).
44
45
  * This add a new example client class to `apiClientDest` folder
45
46
  */
47
+ const className = (0, utilities_1.toClassName)(name);
46
48
  (0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, './client-specific-files'), apiClientDest, {
47
- className: (0, utilities_1.toClassName)(name),
49
+ className,
48
50
  });
51
+ // Apply auth method configuration using ts-morph
52
+ const clientFilePath = (0, devkit_1.joinPathFragments)(apiClientDest, 'client.ts');
53
+ (0, auth_configurations_1.applyAuthMethodConfiguration)(tree, clientFilePath, authMethod, className);
49
54
  /**
50
55
  * The `clients` project expose all the API clients via `src/index.ts` file.
51
56
  * As a result, we need to append new client to the list of exporting;
@@ -0,0 +1,24 @@
1
+ import { Tree } from '@nx/devkit';
2
+ interface MiddlewareConfig {
3
+ properties: string;
4
+ }
5
+ interface AuthMethodConfig {
6
+ middlewareName: string;
7
+ extraImports: string[];
8
+ classProperty: string | null;
9
+ constructorParam: string | null;
10
+ middlewareConfig: MiddlewareConfig;
11
+ }
12
+ export declare const AUTH_CONFIGS: Record<string, AuthMethodConfig>;
13
+ /**
14
+ * Applies auth method configuration to a generated client file using ts-morph.
15
+ * This modifies the file in the Nx Tree to add auth-specific imports, properties,
16
+ * constructor parameters, and middleware configuration.
17
+ *
18
+ * @param tree - The Nx virtual file system tree
19
+ * @param filePath - Path to the client.ts file in the tree
20
+ * @param authMethod - The auth method to apply
21
+ * @param className - The name of the client class
22
+ */
23
+ export declare function applyAuthMethodConfiguration(tree: Tree, filePath: string, authMethod: string, className: string): void;
24
+ export {};
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AUTH_CONFIGS = void 0;
4
+ exports.applyAuthMethodConfiguration = applyAuthMethodConfiguration;
5
+ const ts_morph_1 = require("ts-morph");
6
+ exports.AUTH_CONFIGS = {
7
+ 'api-key': {
8
+ middlewareName: 'apiKeyAuthMiddleware',
9
+ extraImports: ['fetchSsmParams'],
10
+ classProperty: 'private credential: string | null = null;',
11
+ constructorParam: 'credentialPath: string',
12
+ middlewareConfig: {
13
+ properties: `header: 'X-Api-Key',
14
+ value: async () => {
15
+ if (!this.credential) {
16
+ const param = await fetchSsmParams(credentialPath);
17
+ if (!param?.Value) {
18
+ throw new Error('Unable to fetch API client credential');
19
+ }
20
+
21
+ this.credential = param.Value;
22
+ }
23
+
24
+ return this.credential;
25
+ },`,
26
+ },
27
+ },
28
+ 'oauth1.0a': {
29
+ middlewareName: 'oAuth10aAuthMiddleware',
30
+ extraImports: [],
31
+ classProperty: null,
32
+ constructorParam: null,
33
+ middlewareConfig: {
34
+ properties: `algorithm: 'HMAC-SHA256',
35
+ credentials: async () => ({
36
+ // TODO: Provide your OAuth 1.0a credentials
37
+ consumerKey: 'your-consumer-key',
38
+ consumerSecret: 'your-consumer-secret',
39
+ token: 'your-token',
40
+ tokenSecret: 'your-token-secret',
41
+ }),`,
42
+ },
43
+ },
44
+ basic: {
45
+ middlewareName: 'basicAuthMiddleware',
46
+ extraImports: [],
47
+ classProperty: null,
48
+ constructorParam: null,
49
+ middlewareConfig: {
50
+ properties: `credentials: async () => ({
51
+ // TODO: Provide your basic auth credentials
52
+ username: 'your-username',
53
+ password: 'your-password',
54
+ }),`,
55
+ },
56
+ },
57
+ 'oauth2.0': {
58
+ middlewareName: 'oAuth20AuthMiddleware',
59
+ extraImports: [],
60
+ classProperty: null,
61
+ constructorParam: null,
62
+ middlewareConfig: {
63
+ properties: `token: async () => {
64
+ // TODO: Send API call to get your OAuth 2.0 access token
65
+ return 'your-access-token';
66
+ }`,
67
+ },
68
+ },
69
+ };
70
+ /**
71
+ * Applies auth method configuration to a generated client file using ts-morph.
72
+ * This modifies the file in the Nx Tree to add auth-specific imports, properties,
73
+ * constructor parameters, and middleware configuration.
74
+ *
75
+ * @param tree - The Nx virtual file system tree
76
+ * @param filePath - Path to the client.ts file in the tree
77
+ * @param authMethod - The auth method to apply
78
+ * @param className - The name of the client class
79
+ */
80
+ function applyAuthMethodConfiguration(tree, filePath, authMethod, className) {
81
+ const config = exports.AUTH_CONFIGS[authMethod];
82
+ if (!config) {
83
+ throw new Error(`Unknown auth method: ${authMethod}`);
84
+ }
85
+ const fileContent = tree.read(filePath, 'utf-8');
86
+ if (!fileContent) {
87
+ throw new Error(`Unable to read file: ${filePath}`);
88
+ }
89
+ const project = new ts_morph_1.Project({
90
+ useInMemoryFileSystem: true,
91
+ manipulationSettings: {
92
+ indentationText: ts_morph_1.IndentationText.FourSpaces,
93
+ quoteKind: ts_morph_1.QuoteKind.Single,
94
+ },
95
+ });
96
+ const sourceFile = project.createSourceFile(filePath, fileContent);
97
+ // Add imports to the @aligent/microservice-util-lib import declaration
98
+ const utilLibImport = sourceFile.getImportDeclaration(decl => decl.getModuleSpecifierValue() === '@aligent/microservice-util-lib');
99
+ if (utilLibImport) {
100
+ for (const importName of config.extraImports) {
101
+ utilLibImport.addNamedImport(importName);
102
+ }
103
+ utilLibImport.addNamedImport(config.middlewareName);
104
+ }
105
+ // Get the client class
106
+ const clientClass = sourceFile.getClass(className);
107
+ if (!clientClass) {
108
+ throw new Error(`Unable to find class: ${className}`);
109
+ }
110
+ // Add class property if configured (insert before the 'client' property)
111
+ if (config.classProperty) {
112
+ const clientProperty = clientClass.getProperty('client');
113
+ if (clientProperty) {
114
+ const propertyIndex = clientProperty.getChildIndex();
115
+ clientClass.insertProperty(propertyIndex, {
116
+ name: 'credential',
117
+ type: 'string | null',
118
+ initializer: 'null',
119
+ scope: ts_morph_1.Scope.Private,
120
+ });
121
+ }
122
+ }
123
+ // Modify constructor to add parameter and middleware
124
+ const constructor = clientClass.getConstructors()[0];
125
+ if (!constructor) {
126
+ throw new Error(`Unable to find constructor in class: ${className}`);
127
+ }
128
+ // Add constructor parameter if configured
129
+ if (config.constructorParam) {
130
+ const parts = config.constructorParam.split(':');
131
+ const paramName = parts[0]?.trim() ?? '';
132
+ const paramType = parts[1]?.trim() ?? '';
133
+ constructor.addParameter({
134
+ name: paramName,
135
+ type: paramType,
136
+ });
137
+ }
138
+ // Build the middleware call
139
+ const middlewareCall = `this.client.use(
140
+ ${config.middlewareName}({
141
+ ${config.middlewareConfig.properties}
142
+ })
143
+ );`;
144
+ // Find the retryMiddleware use statement and insert before it
145
+ const statements = constructor.getStatements();
146
+ const retryStatementIndex = statements.findIndex(statement => /this\.client\.use\(\s*retryMiddleware/.test(statement.getText()));
147
+ if (retryStatementIndex !== -1) {
148
+ constructor.insertStatements(retryStatementIndex, middlewareCall);
149
+ }
150
+ // Write the modified content back to the tree
151
+ tree.write(filePath, sourceFile.getFullText());
152
+ }
@@ -5,4 +5,5 @@ export interface ClientGeneratorSchema {
5
5
  importPath?: string;
6
6
  skipValidate: boolean;
7
7
  override: boolean;
8
+ authMethod?: string;
8
9
  }
@@ -40,6 +40,12 @@
40
40
  "type": "boolean",
41
41
  "description": "Override existing files during generation",
42
42
  "default": false
43
+ },
44
+ "authMethod": {
45
+ "type": "string",
46
+ "description": "Authentication method to use for the generated client middleware",
47
+ "enum": ["api-key", "oauth1.0a", "basic", "oauth2.0"],
48
+ "default": "api-key"
43
49
  }
44
50
  },
45
51
  "required": ["name", "schemaPath"]