Skip to content

Commit 1a2764c

Browse files
committed
Refactor ACG tool for NPM
1 parent 9e86356 commit 1a2764c

File tree

13 files changed

+1737
-214
lines changed

13 files changed

+1737
-214
lines changed

.github/workflows/publish.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ jobs:
2727
Prerelease: ${{ github.event.inputs.prerelease || 'false' }}
2828
PushPackage: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/master') || github.event.inputs.push }}
2929
NpmTokenExists: ${{ secrets.NPM_TOKEN != '' }}
30+
Platforms: "linux-x64 linux-musl-x64 osx-x64 osx-arm64 win-x64"
3031

3132
steps:
3233
- uses: actions/checkout@v4
@@ -65,9 +66,31 @@ jobs:
6566
run: |
6667
ROOT=$(pwd)
6768
cd src/ApiCodeGenerator.Npm
69+
npm install
6870
npm version ${GitVersion_FullSemVer} --no-git-tag-version
6971
npm run pack-ci -- ${ROOT}/${PackageOutputDir}
7072
73+
- name: Pack Npm Binaries
74+
run: |
75+
ROOT=$(pwd)
76+
for PLATFORM in ${{ env.Platforms }}; do
77+
echo "Building for platform: $PLATFORM"
78+
dotnet publish src/ApiCodeGenerator.MSBuild \
79+
-c Release \
80+
-f net8.0 \
81+
-r $PLATFORM \
82+
--self-contained \
83+
-p:PublishTrimmed=false \
84+
-o ./bin/publish/$PLATFORM
85+
86+
# Create tgz archive with platform name only
87+
cd ./bin/publish/$PLATFORM
88+
tar -czf $ROOT/${PackageOutputDir}/${PLATFORM}.tgz *
89+
cd $ROOT
90+
91+
echo "Created archive: ${PLATFORM}.tgz"
92+
done
93+
7194
- name: Nuget Push
7295
if: env.PushPackage == 'true'
7396
working-directory: ${{ env.PackageOutputDir }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
bin/
77
obj/
88
binaries/
9+
coverage/
910
dist/
1011
node_modules/
1112

1213
*.user
1314
msbuild.binlog
1415
package-lock.json
16+
acg.js
17+
acg.d.ts

ApiCodeGenerator.code-workspace

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"folders": [
3+
{
4+
"path": "."
5+
},
6+
{
7+
"path": "src/ApiCodeGenerator.Npm"
8+
}
9+
],
10+
"settings": {
11+
"autoHide.hideSideBarsOnDebug": false,
12+
"autoHide.hidePanelOnDebug": false
13+
}
14+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
msbuild.binlog
22
dist/
3+
coverage/
4+
tsconfig.base.json
5+
tsconfig.json
6+
jest.config.js
7+
*.ts
8+
!*.d.ts

src/ApiCodeGenerator.Npm/README.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# API Code Generator CLI
2+
3+
## Overview
4+
5+
The API Code Generator (ACG) CLI is a command-line tool that generates API client code based on API specifications configured in `.nswag` files. This package provides an easy-to-use interface for automating code generation from OpenAPI/Swagger and AsyncAPI specifications.
6+
7+
## Installation
8+
9+
```bash
10+
npm install -g @mobiletelsystems/api-code-generator
11+
```
12+
13+
Or use locally in your project:
14+
15+
```bash
16+
npm install @mobiletelsystems/api-code-generator
17+
```
18+
19+
## Usage
20+
21+
### Basic Command
22+
23+
```bash
24+
acg [options]
25+
```
26+
27+
The CLI will automatically discover and process `.nswag` configuration files in the current working directory and its subdirectories.
28+
29+
## CLI Options
30+
31+
### `-h, --help`
32+
Display the help message showing all available options and examples.
33+
34+
```bash
35+
acg --help
36+
```
37+
38+
### `-v, --verbose`
39+
Enable verbose logging output. Useful for troubleshooting and understanding the code generation process in detail.
40+
41+
```bash
42+
acg --verbose
43+
```
44+
45+
### `--acg-binary-dir <path>`
46+
Specify the path to the directory containing `ApiCodeGenerator.MSBuild.dll`. Use this to point to a custom location where the generator binaries are installed.
47+
48+
```bash
49+
acg --acg-binary-dir /path/to/binaries
50+
```
51+
52+
### `--acg-binary-site <url>`
53+
Specify a custom URL to download the binaries from. By default, binaries are downloaded from the official repository.
54+
55+
```bash
56+
acg --acg-binary-site https://example.com/binaries
57+
```
58+
59+
### `--no-download-acg-binary`
60+
Disable automatic binary download. Use this flag if you want to use only locally available binaries without attempting to download updates.
61+
62+
```bash
63+
acg --no-download-acg-binary
64+
```
65+
66+
## Configuration and Environment Variables
67+
68+
The CLI supports multiple ways to configure its behavior, in order of precedence (highest to lowest):
69+
70+
1. **Command-line arguments** (highest priority)
71+
2. **Environment variables**
72+
3. **`.acgrc` configuration file**
73+
4. **Default values** (lowest priority)
74+
75+
### Environment Variables
76+
77+
- `ACG_BINARY_DIR` - Path to the directory containing ApiCodeGenerator.MSBuild.dll
78+
- `NO_DOWNLOAD_ACG_BINARY` - Disable automatic binary download (set to any value to enable)
79+
- `ACG_BINARY_SITE` - URL to download binaries from
80+
- `npm_config_acg_binary_dir` - NPM-style configuration for binary directory
81+
- `npm_config_no_download_acg_binary` - NPM-style configuration to disable downloads
82+
- `npm_config_acg_binary_site` - NPM-style configuration for binary site
83+
84+
### Configuration File
85+
86+
The CLI supports a `.acgrc` configuration file in the current working directory. This JSON file allows you to set default options without needing to specify them on the command line every time.
87+
88+
### Configuration Properties
89+
90+
**CLI Options (Binary Management):**
91+
- `acgBinaryDir` - Path to the directory containing ApiCodeGenerator.MSBuild.dll
92+
- `acgBinarySite` - URL to download binaries from
93+
- `noDownloadAcgBinary` - Disable automatic binary download (boolean)
94+
95+
**Code Generation Options:**
96+
- `nswagToolDir` - Path to the folder containing Nswag tools for different .NET versions
97+
- `apiDocumentDir` - Base directory for API documents (if not specified, uses the directory of the .nswag file)
98+
- `extensions` - Array of extension package names to load (e.g., `["acg-preprocessor-jsonpatch"]`)
99+
- `variables` - Global variables object (Record<string, string>) that are used in `.nswag` configuration files to customize code generation
100+
- `apiReferences` - Array of API reference configurations. Each reference must include:
101+
- `nswag` - Path to the `.nswag` file (relative path from project root)
102+
- `document` - Path to the API specification file (OpenAPI/AsyncAPI YAML or JSON) - **required if not auto-discovered**
103+
- `out` - Output file path for generated code - **required if not auto-discovered**
104+
- `variables` - Variables specific to this API reference (used in `.nswag` configuration, overrides global variables)
105+
106+
### Example `.acgrc`
107+
108+
```json
109+
{
110+
"acgBinaryDir": "/opt/acg/binaries",
111+
"acgBinarySite": "https://releases.example.com/acg",
112+
"noDownloadAcgBinary": false,
113+
"nswagToolDir": "/opt/nswag/binaries",
114+
"apiDocumentDir": "openapi",
115+
"extensions": [
116+
"acg-preprocessor-jsonpatch"
117+
],
118+
"variables": {
119+
"clName": "Salsa"
120+
},
121+
"apiReferences": [
122+
{
123+
"nswag": "migration_api.nswag",
124+
"document": "openapi/migration_api.yaml",
125+
"out": "migration_api.g.ts",
126+
"variables": {
127+
"clName": "MigrationClient"
128+
}
129+
},
130+
{
131+
"nswag": "user_api.nswag",
132+
"document": "openapi/user_api.yaml",
133+
"out": "user_api.g.ts"
134+
}
135+
]
136+
}
137+
```
138+
139+
### Configuration Notes
140+
141+
- **Property Naming**: All configuration properties use `camelCase` (e.g., `acgBinaryDir`, `noDownloadAcgBinary`, `apiDocumentDir`)
142+
- **Configuration Precedence**: Command-line arguments → Environment variables → `.acgrc` file → Default values
143+
- **Variables in .nswag Files**: Variables defined in `.acgrc` are passed to `.nswag` configuration files and can be referenced within them to customize code generation behavior
144+
- **Global vs. Local Variables**: Global variables are defined at the root level. Per-reference variables in `apiReferences` override global variables for that specific reference
145+
- **Auto-Discovery**: If `document` and `out` paths are not found in `apiReferences`, the CLI will attempt to auto-discover them:
146+
- **Document**: Searches in `apiDocumentDir` (or `.nswag` file directory if not specified) for matching `.json`, `.yaml`, or `.yml` files
147+
- **Output**: Defaults to replacing `.nswag` extension with `.g.ts` if not explicitly configured
148+
- **Extension Loading**: Extensions are NPM packages that export a list of DLL paths. Each extension in the `extensions` array should be an installed package name
149+
- **Binary Download**: The CLI attempts to use an installed .NET runtime (8.0 or higher) to execute the binaries. If no .NET runtime is detected, pre-built binaries (which include the .NET framework) are automatically downloaded from the `acgBinarySite`. Set `noDownloadAcgBinary` to `true` to disable automatic downloads (requires .NET runtime to be installed)
150+
151+
## Examples
152+
153+
### Generate code with verbose output
154+
```bash
155+
acg --verbose
156+
```
157+
158+
### Use custom binary directory
159+
```bash
160+
acg --acg-binary-dir ./vendor/acg-binaries
161+
```
162+
163+
### Disable automatic binary downloads
164+
```bash
165+
acg --no-download-acg-binary --acg-binary-dir /usr/local/acg
166+
```
167+
168+
### Combine multiple options
169+
```bash
170+
acg --verbose --acg-binary-dir ./binaries --no-download-acg-binary
171+
```
172+
173+
### Use environment variables
174+
```bash
175+
ACG_BINARY_DIR=/opt/acg/binaries acg --verbose
176+
```
177+
178+
### Configure via `.acgrc` and use environment overrides
179+
```bash
180+
# With .acgrc present:
181+
NO_DOWNLOAD_ACG_BINARY=1 acg # Overrides acgrc setting
182+
```
183+
184+
## How It Works
185+
186+
1. The CLI resolves configuration from multiple sources (CLI args, environment variables, `.acgrc` file)
187+
2. It detects the runtime identifier (OS and architecture)
188+
3. It checks for available .NET runtimes or downloads pre-built binaries if needed
189+
4. It discovers `.nswag` files in your project and merges them with `apiReferences` configuration
190+
5. For each API reference, it validates required properties (`document`, `out`, `nswag`)
191+
6. It auto-discovers missing `document` paths based on `apiDocumentDir` or the `.nswag` file location
192+
7. For each API reference with a valid document, it generates code using the appropriate Nswag tool
193+
8. It selects the best matching .NET runtime for the code generator and Nswag tool
194+
9. Generated code is output according to your configuration
195+
196+
## For More Information
197+
198+
Visit the project repository: https://github.com/MobileTeleSystems/ApiCodeGenerator
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env node
2+
"use strict"
3+
4+
const acg = require("./acg");
5+
const fs = require("fs");
6+
7+
const args = process.argv.slice(2);
8+
9+
function showHelp() {
10+
console.log(`API Code Generator CLI
11+
12+
Usage: acg [options]
13+
14+
Options:
15+
-h, --help Show this help message
16+
-v, --verbose Enable verbose logging
17+
--acg-binary-dir <path> Path to the directory containing ApiCodeGenerator.MSBuild.dll
18+
--acg-binary-site <url> URL to download binaries
19+
--no-download-acg-binary Disable automatic binary download
20+
21+
Examples:
22+
acg --help
23+
24+
Description:
25+
Generates API client code based on API specifications configured in .nswag files.
26+
27+
For more information, visit: https://github.com/MobileTeleSystems/ApiCodeGenerator`);
28+
}
29+
30+
// Check for help flag
31+
if (args.includes('-h') || args.includes('--help')) {
32+
showHelp();
33+
process.exit(0);
34+
}
35+
36+
const enableVerbose = args.indexOf("-v") > -1 || args.indexOf("--verbose");
37+
38+
/**
39+
* Get command line argument value
40+
* @param {string} name - Argument name
41+
* @returns {string | undefined } Argument value
42+
*/
43+
function getArgument(name) {
44+
var flags = args.slice(2),
45+
index = flags.lastIndexOf(name);
46+
47+
if (index === -1 || index + 1 >= flags.length) {
48+
return undefined;
49+
}
50+
51+
return flags[index + 1];
52+
}
53+
54+
/** @param {(Error & {status?: number}) | number} error */
55+
function handleError(error) {
56+
if (error) {
57+
if (typeof error === "number") {
58+
process.exit(error);
59+
} else {
60+
if (!(error instanceof acg.AcgError)) {
61+
console.error("Unexpected error");
62+
}
63+
console.error(error.message);
64+
enableVerbose && console.trace(error);
65+
}
66+
process.exit(error.status && typeof error.status === "number" ? error.status : 1);
67+
}
68+
}
69+
70+
let rc = {};
71+
if (fs.existsSync(".acgrc")) {
72+
var json = fs.readFileSync(".acgrc");
73+
if (json) {
74+
try {
75+
//@ts-ignore
76+
rc = JSON.parse(json)
77+
}
78+
catch (err) {
79+
console.error("Error loading .acgrc file");
80+
//@ts-ignore
81+
handleError(err);
82+
}
83+
}
84+
}
85+
86+
/** @type {import("./acg").Options} */
87+
const opt = {
88+
acgBinaryDir: getArgument("--acg-binary-dir")
89+
|| acg.getAcgBinaryDir(rc),
90+
acgBinarySite: getArgument("--acg-binary-site")
91+
|| acg.getAcgBinarySite(rc),
92+
noDownloadBinary: args.indexOf("--no-download-acg-binary") > -1
93+
|| acg.getNoDownloadBinary(rc)
94+
};
95+
96+
enableVerbose && acg.enableVerbose();
97+
98+
acg
99+
.run(process.cwd(), rc, opt)
100+
.catch(handleError);

0 commit comments

Comments
 (0)