sla-wizard-plugin-custom-baseurl 1.0.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 ADDED
@@ -0,0 +1,404 @@
1
+ # sla-wizard-plugin-custom-baseUrl
2
+
3
+ A plugin for [SLA Wizard](../sla-wizard) and [sla-wizard-nginx-confd](../sla-wizard-nginx-confd) that adds support for the `x-nginx-server-baseurl` OAS extension.
4
+
5
+ When a path declares `x-nginx-server-baseurl`, nginx will proxy that endpoint to the **custom backend URL** instead of the global `servers[0].url`. Endpoints without the extension continue to use the global default.
6
+
7
+ ```
8
+ Client → nginx: POST /models/chatgpt/v1/chat/completions
9
+ nginx → backend: POST http://localhost:8001/v1/chat/completions ← custom URL
10
+
11
+ Client → nginx: POST /models/qwen/v1/chat/completions
12
+ nginx → backend: POST http://localhost:8000/v1/chat/completions ← default URL
13
+ ```
14
+
15
+ The plugin also inherits full `x-nginx-strip` support (via `sla-wizard-plugin-nginx-strip`), so you can freely combine both extensions on the same OAS path.
16
+
17
+ ---
18
+
19
+ ## How it works
20
+
21
+ ### 1. Annotate your OAS with `x-nginx-server-baseurl`
22
+
23
+ Add the extension at the **path level** (not operation level):
24
+
25
+ ```yaml
26
+ # oas.yaml
27
+ servers:
28
+ - url: http://localhost:8000 # default for all endpoints
29
+
30
+ paths:
31
+ /models/qwen/v1/chat/completions:
32
+ post: ... # no extension → proxy_pass http://localhost:8000
33
+
34
+ /models/chatgpt/v1/chat/completions:
35
+ x-nginx-server-baseurl: http://localhost:8001 # overrides proxy_pass
36
+ post: ...
37
+
38
+ /models/claude/v1/chat/completions:
39
+ x-nginx-server-baseurl: http://localhost:8002
40
+ post: ...
41
+ ```
42
+
43
+ ### 2. Write your SLAs normally
44
+
45
+ ```yaml
46
+ # sla.yaml
47
+ plan:
48
+ name: normal
49
+ rates:
50
+ /models/chatgpt/v1/chat/completions:
51
+ post:
52
+ requests:
53
+ - max: 5
54
+ period: minute
55
+ ```
56
+
57
+ If you combine this plugin with `x-nginx-strip`, your SLAs can reference the backend (stripped) path and the plugin handles the expansion automatically.
58
+
59
+ ### 3. Run the plugin
60
+
61
+ The plugin generates nginx configuration where each location block uses the endpoint-specific `proxy_pass` target:
62
+
63
+ ```nginx
64
+ # Without x-nginx-server-baseurl (standard output)
65
+ location /sla-alice_normal_modelschatgptv1chatcompletions_POST {
66
+ rewrite /sla-alice_normal_modelschatgptv1chatcompletions_POST $uri_original break;
67
+ proxy_pass http://localhost:8000; ← same URL for every endpoint
68
+ ...
69
+ }
70
+
71
+ # With x-nginx-server-baseurl applied
72
+ location /sla-alice_normal_modelschatgptv1chatcompletions_POST {
73
+ rewrite /sla-alice_normal_modelschatgptv1chatcompletions_POST $uri_original break;
74
+ proxy_pass http://localhost:8001; ← custom URL for this endpoint only
75
+ ...
76
+ }
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Installation
82
+
83
+ ```bash
84
+ cd sla-wizard-plugin-custom-baseUrl
85
+ npm install
86
+ ```
87
+
88
+ The plugin depends on `sla-wizard-plugin-nginx-strip` (bundled as a local dependency).
89
+
90
+ ---
91
+
92
+ ## Usage — CLI
93
+
94
+ Register the plugin in a CLI wrapper and invoke the commands.
95
+
96
+ ### CLI wrapper (one-time setup)
97
+
98
+ ```js
99
+ // my-cli.js
100
+ const slaWizard = require("sla-wizard");
101
+ const customBaseUrlPlugin = require("sla-wizard-plugin-custom-baseUrl");
102
+
103
+ slaWizard.use(customBaseUrlPlugin);
104
+ slaWizard.program.parse(process.argv);
105
+ ```
106
+
107
+ ### `config-nginx-baseurl` — full config (nginx.conf + conf.d/)
108
+
109
+ Generates the complete nginx configuration split into a main `nginx.conf` and
110
+ per-user `conf.d/` files:
111
+
112
+ ```bash
113
+ node my-cli.js config-nginx-baseurl \
114
+ -o ./nginx-output \
115
+ --oas ./specs/oas.yaml \
116
+ --sla ./specs/slas \
117
+ --authName apikey \
118
+ --proxyPort 80
119
+ ```
120
+
121
+ Output:
122
+
123
+ ```
124
+ nginx-output/
125
+ ├── nginx.conf ← server block + URI routing rules
126
+ └── conf.d/
127
+ ├── sla-alice_us.conf
128
+ ├── sla-bob_us.conf
129
+ └── ...
130
+ ```
131
+
132
+ Each `conf.d/` file contains the rate-limiting zones, API-key map, and location
133
+ blocks for one user. Location blocks for endpoints with `x-nginx-server-baseurl`
134
+ will have the custom URL in their `proxy_pass` directive.
135
+
136
+ ### `add-to-baseurl-confd` — conf.d only (no nginx.conf overwrite)
137
+
138
+ Useful for adding a new user or plan without touching the main `nginx.conf`:
139
+
140
+ ```bash
141
+ node my-cli.js add-to-baseurl-confd \
142
+ -o ./nginx-output \
143
+ --oas ./specs/oas.yaml \
144
+ --sla ./specs/slas/sla_newuser.yaml
145
+ ```
146
+
147
+ ### CLI options
148
+
149
+ | Option | Description | Default |
150
+ |---|---|---|
151
+ | `-o, --outDir <dir>` | Output directory | **required** |
152
+ | `--oas <path>` | Path to OAS v3 file | `./specs/oas.yaml` |
153
+ | `--sla <path>` | Single SLA file, directory of SLAs, or URL | `./specs/sla.yaml` |
154
+ | `--authLocation <loc>` | Where to read the API key: `header`, `query`, `url` | `header` |
155
+ | `--authName <name>` | API key parameter name | `apikey` |
156
+ | `--proxyPort <port>` | Port nginx listens on | `80` |
157
+ | `--customTemplate <path>` | Custom nginx config template | — |
158
+
159
+ ---
160
+
161
+ ## Usage — Module (programmatic)
162
+
163
+ ### Setup
164
+
165
+ ```js
166
+ const slaWizard = require("sla-wizard");
167
+ const customBaseUrlPlugin = require("sla-wizard-plugin-custom-baseUrl");
168
+
169
+ slaWizard.use(customBaseUrlPlugin);
170
+ ```
171
+
172
+ `slaWizard.use` registers the plugin and exposes its functions directly on the
173
+ `slaWizard` object, injecting the sla-wizard context automatically.
174
+
175
+ ### `slaWizard.configNginxBaseUrl(options)` — full config
176
+
177
+ ```js
178
+ slaWizard.configNginxBaseUrl({
179
+ outDir: "./nginx-output",
180
+ oas: "./specs/oas.yaml",
181
+ sla: "./specs/slas", // file, directory, or URL
182
+ authLocation: "header", // optional, default: "header"
183
+ authName: "apikey", // optional, default: "apikey"
184
+ proxyPort: 80, // optional, default: 80
185
+ });
186
+ ```
187
+
188
+ ### `slaWizard.addToBaseUrlConfd(options)` — conf.d only
189
+
190
+ Generates (or updates) only the `conf.d/` files without creating or overwriting
191
+ `nginx.conf`. Ideal for incremental user management:
192
+
193
+ ```js
194
+ slaWizard.addToBaseUrlConfd({
195
+ outDir: "./nginx-output",
196
+ oas: "./specs/oas.yaml",
197
+ sla: "./specs/slas/sla_newuser.yaml",
198
+ });
199
+ ```
200
+
201
+ ### Using individual exports directly
202
+
203
+ The plugin also exports lower-level functions that work **without sla-wizard
204
+ context** — useful when integrating into custom pipelines:
205
+
206
+ ```js
207
+ const {
208
+ applyBaseUrlToConfig, // transform a raw config string
209
+ applyBaseUrlTransformations, // transform all .conf files in an output directory
210
+ } = require("sla-wizard-plugin-custom-baseUrl");
211
+ ```
212
+
213
+ #### `applyBaseUrlTransformations(outDir, oasPath)`
214
+
215
+ Reads the OAS from `oasPath`, then for every endpoint that declares
216
+ `x-nginx-server-baseurl`, rewrites every matching `proxy_pass` directive inside
217
+ `<outDir>/nginx.conf` and `<outDir>/conf.d/*.conf` — replacing the default
218
+ server URL with the endpoint-specific custom URL.
219
+
220
+ ```js
221
+ const { applyBaseUrlTransformations } = require("sla-wizard-plugin-custom-baseUrl");
222
+
223
+ // After generating nginx config into outDir with any other tool...
224
+ applyBaseUrlTransformations("./nginx-output", "./specs/oas.yaml");
225
+ ```
226
+
227
+ #### `applyBaseUrlToConfig(configContent, baseUrlMap, defaultUrl)`
228
+
229
+ Lower-level string transformation. Takes the raw config text, a map of
230
+ `sanitizedEndpoint → customUrl`, and the default URL to replace, and returns
231
+ the modified config string.
232
+
233
+ ```js
234
+ const { applyBaseUrlToConfig } = require("sla-wizard-plugin-custom-baseUrl");
235
+
236
+ const modified = applyBaseUrlToConfig(nginxConfString, {
237
+ modelschatgptv1chatcompletions: "http://localhost:8001",
238
+ modelsclaudev1chatcompletions: "http://localhost:8002",
239
+ }, "http://localhost:8000");
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Combining with `x-nginx-strip`
245
+
246
+ The plugin is built on top of `sla-wizard-plugin-nginx-strip`, so **both
247
+ extensions work together on the same OAS path**. When you annotate an endpoint
248
+ with both, nginx will:
249
+
250
+ 1. Receive the full public path (e.g. `/models/chatgpt/v1/chat/completions`)
251
+ 2. Forward the stripped backend path (e.g. `/v1/chat/completions`) — from `x-nginx-strip`
252
+ 3. Send the request to the custom backend URL (e.g. `http://localhost:8001`) — from `x-nginx-server-baseurl`
253
+
254
+ ```yaml
255
+ paths:
256
+ /models/chatgpt/v1/chat/completions:
257
+ x-nginx-strip: "/models/chatgpt" # strip this prefix before forwarding
258
+ x-nginx-server-baseurl: http://localhost:8001 # forward to this backend
259
+
260
+ /models/claude/v1/chat/completions:
261
+ x-nginx-strip: "/models/claude"
262
+ x-nginx-server-baseurl: http://localhost:8002
263
+ ```
264
+
265
+ When using `x-nginx-strip`, your SLAs can reference the backend (stripped) path
266
+ and the plugin expands them automatically:
267
+
268
+ ```yaml
269
+ plan:
270
+ rates:
271
+ /v1/chat/completions: # stripped path — expanded to all matching OAS paths
272
+ post:
273
+ requests:
274
+ - max: 10
275
+ period: minute
276
+ ```
277
+
278
+ ---
279
+
280
+ ## Complete example
281
+
282
+ ### OAS (`oas.yaml`)
283
+
284
+ ```yaml
285
+ openapi: 3.0.3
286
+ info:
287
+ title: LLM Gateway API
288
+ version: 1.0.0
289
+ servers:
290
+ - url: http://localhost:8000 # default backend
291
+
292
+ paths:
293
+ /models/qwen/v1/chat/completions:
294
+ post:
295
+ summary: Qwen completions
296
+ operationId: postQwenCompletion
297
+ responses:
298
+ "200":
299
+ description: OK
300
+
301
+ /models/chatgpt/v1/chat/completions:
302
+ x-nginx-server-baseurl: http://localhost:8001
303
+ post:
304
+ summary: ChatGPT completions
305
+ operationId: postChatGPTCompletion
306
+ responses:
307
+ "200":
308
+ description: OK
309
+
310
+ /models/claude/v1/chat/completions:
311
+ x-nginx-server-baseurl: http://localhost:8002
312
+ post:
313
+ summary: Claude completions
314
+ operationId: postClaudeCompletion
315
+ responses:
316
+ "200":
317
+ description: OK
318
+ ```
319
+
320
+ ### SLA (`sla.yaml`)
321
+
322
+ ```yaml
323
+ sla4oas: 1.0.0
324
+ context:
325
+ id: sla-alice
326
+ type: agreement
327
+ api:
328
+ $ref: ./oas.yaml
329
+ apikeys:
330
+ - my-secret-key-abc123
331
+ plan:
332
+ name: standard
333
+ rates:
334
+ /models/chatgpt/v1/chat/completions:
335
+ post:
336
+ requests:
337
+ - max: 10
338
+ period: minute
339
+ /models/claude/v1/chat/completions:
340
+ post:
341
+ requests:
342
+ - max: 10
343
+ period: minute
344
+ ```
345
+
346
+ ### Generate config (programmatic)
347
+
348
+ ```js
349
+ const slaWizard = require("sla-wizard");
350
+ const customBaseUrlPlugin = require("sla-wizard-plugin-custom-baseUrl");
351
+
352
+ slaWizard.use(customBaseUrlPlugin);
353
+
354
+ slaWizard.configNginxBaseUrl({
355
+ outDir: "./nginx-output",
356
+ oas: "./oas.yaml",
357
+ sla: "./sla.yaml",
358
+ });
359
+ ```
360
+
361
+ ### Generated `conf.d/sla-alice_standard.conf` (excerpt)
362
+
363
+ ```nginx
364
+ # Rate limiting zones
365
+ limit_req_zone $http_apikey zone=sla-alice_standard_modelschatgptv1chatcompletions_POST:10m rate=10r/m;
366
+ limit_req_zone $http_apikey zone=sla-alice_standard_modelsclaudev1chatcompletions_POST:10m rate=10r/m;
367
+
368
+ # Endpoint locations
369
+ location /sla-alice_standard_modelschatgptv1chatcompletions_POST {
370
+ rewrite /sla-alice_standard_modelschatgptv1chatcompletions_POST $uri_original break;
371
+ proxy_pass http://localhost:8001; ← chatgpt backend
372
+ limit_req zone=sla-alice_standard_modelschatgptv1chatcompletions_POST burst=9 nodelay;
373
+ }
374
+
375
+ location /sla-alice_standard_modelsclaudev1chatcompletions_POST {
376
+ rewrite /sla-alice_standard_modelsclaudev1chatcompletions_POST $uri_original break;
377
+ proxy_pass http://localhost:8002; ← claude backend
378
+ limit_req zone=sla-alice_standard_modelsclaudev1chatcompletions_POST burst=9 nodelay;
379
+ }
380
+ ```
381
+
382
+ The qwen endpoint has no `x-nginx-server-baseurl`, so its location block (in
383
+ `nginx.conf`) keeps `proxy_pass http://localhost:8000`.
384
+
385
+ ---
386
+
387
+ ## Running the tests
388
+
389
+ ```bash
390
+ npm test
391
+ ```
392
+
393
+ The test suite covers:
394
+
395
+ - **Unit** — `applyBaseUrlToConfig`: replacement, non-replacement, edge cases (empty input, no location blocks, block without `proxy_pass`, indentation preservation, partial-URL guard)
396
+ - **Unit** — `applyBaseUrlTransformations`: file I/O, no-op when no extension present, graceful handling of missing files/directories
397
+ - **Integration** — `configNginxBaseUrl` and `addToBaseUrlConfd`: correct output structure, proxy_pass values per endpoint, both strip and baseurl transforms coexisting, idempotency, single-file SLA input
398
+ - **CLI** — both commands, `--help` output, missing required argument, single-file SLA input, stdout success messages
399
+
400
+ ---
401
+
402
+ ## License
403
+
404
+ Apache License 2.0 — same as SLA Wizard.
package/index.js ADDED
@@ -0,0 +1,101 @@
1
+ const { configNginxBaseUrl, addToBaseUrlConfd } = require("./src/commands");
2
+ const {
3
+ applyBaseUrlToConfig,
4
+ applyBaseUrlTransformations,
5
+ } = require("./src/nginx-transform");
6
+
7
+ /**
8
+ * Plugin that generates nginx configuration with per-endpoint proxy_pass
9
+ * overrides driven by the x-nginx-server-baseurl OAS vendor extension.
10
+ *
11
+ * When a path in the OAS declares:
12
+ * x-nginx-server-baseurl: http://backend-host:port
13
+ *
14
+ * the generated nginx location blocks for that endpoint will use the specified
15
+ * URL in their proxy_pass directive instead of the global servers[0].url.
16
+ * Paths without the extension continue to use the global default.
17
+ *
18
+ * This plugin also inherits x-nginx-strip support (via sla-wizard-plugin-nginx-strip),
19
+ * so it works with OAS files that combine both extensions and SLAs that
20
+ * reference the stripped (backend) paths in their rate definitions.
21
+ *
22
+ * @param {Object} program - Commander program instance
23
+ * @param {Object} ctx - Context with utils and generate functions
24
+ */
25
+ function apply(program, ctx) {
26
+ program
27
+ .command("config-nginx-baseurl")
28
+ .description(
29
+ "Generate nginx configuration with x-nginx-server-baseurl per-endpoint proxy_pass support",
30
+ )
31
+ .requiredOption(
32
+ "-o, --outDir <outputDirectory>",
33
+ "Output directory for nginx.conf and conf.d/",
34
+ )
35
+ .option(
36
+ "--sla <slaPath>",
37
+ "One of: 1) single SLA, 2) folder of SLAs, 3) URL returning an array of SLA objects",
38
+ "./specs/sla.yaml",
39
+ )
40
+ .option("--oas <pathToOAS>", "Path to an OAS v3 file.", "./specs/oas.yaml")
41
+ .option(
42
+ "--customTemplate <customTemplate>",
43
+ "Custom proxy configuration template.",
44
+ )
45
+ .option(
46
+ "--authLocation <authLocation>",
47
+ "Where to look for the authentication parameter. Must be one of: header, query, url.",
48
+ "header",
49
+ )
50
+ .option(
51
+ "--authName <authName>",
52
+ 'Name of the authentication parameter, such as "token" or "apikey".',
53
+ "apikey",
54
+ )
55
+ .option("--proxyPort <proxyPort>", "Port on which the proxy is running", 80)
56
+ .action(function (options) {
57
+ configNginxBaseUrl(options, ctx);
58
+ });
59
+
60
+ program
61
+ .command("add-to-baseurl-confd")
62
+ .description(
63
+ "Generate conf.d files with x-nginx-server-baseurl proxy_pass overrides (no nginx.conf)",
64
+ )
65
+ .requiredOption(
66
+ "-o, --outDir <outputDirectory>",
67
+ "Output directory for conf.d/",
68
+ )
69
+ .option(
70
+ "--sla <slaPath>",
71
+ "One of: 1) single SLA, 2) folder of SLAs, 3) URL returning an array of SLA objects",
72
+ "./specs/sla.yaml",
73
+ )
74
+ .option("--oas <pathToOAS>", "Path to an OAS v3 file.", "./specs/oas.yaml")
75
+ .option(
76
+ "--customTemplate <customTemplate>",
77
+ "Custom proxy configuration template.",
78
+ )
79
+ .option(
80
+ "--authLocation <authLocation>",
81
+ "Where to look for the authentication parameter. Must be one of: header, query, url.",
82
+ "header",
83
+ )
84
+ .option(
85
+ "--authName <authName>",
86
+ 'Name of the authentication parameter, such as "token" or "apikey".',
87
+ "apikey",
88
+ )
89
+ .option("--proxyPort <proxyPort>", "Port on which the proxy is running", 80)
90
+ .action(function (options) {
91
+ addToBaseUrlConfd(options, ctx);
92
+ });
93
+ }
94
+
95
+ module.exports = {
96
+ apply,
97
+ configNginxBaseUrl,
98
+ addToBaseUrlConfd,
99
+ applyBaseUrlToConfig,
100
+ applyBaseUrlTransformations,
101
+ };
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "sla-wizard-plugin-custom-baseurl",
3
+ "version": "1.0.0",
4
+ "description": "Plugin that overrides the nginx proxy_pass target per endpoint using the x-nginx-server-baseurl OAS extension",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "mocha ./tests/tests.js"
8
+ },
9
+ "dependencies": {
10
+ "js-yaml": "^4.1.0",
11
+ "sla-wizard-plugin-nginx-strip": "^1.0.0"
12
+ },
13
+ "devDependencies": {
14
+ "chai": "^4.3.6",
15
+ "mocha": "^10.0.0",
16
+ "sla-wizard": "^1.1.0"
17
+ }
18
+ }
@@ -0,0 +1,32 @@
1
+ const nginxStrip = require("sla-wizard-plugin-nginx-strip");
2
+ const { applyBaseUrlTransformations } = require("./nginx-transform");
3
+
4
+ /**
5
+ * Generates full nginx config (nginx.conf + conf.d/) with both x-nginx-strip
6
+ * path stripping and x-nginx-server-baseurl per-endpoint proxy_pass overrides.
7
+ *
8
+ * Delegates to sla-wizard-plugin-nginx-strip for SLA path expansion and strip
9
+ * transforms, then applies the base URL transformation on top.
10
+ *
11
+ * @param {Object} options - Command options (outDir, oas, sla, …)
12
+ * @param {Object} ctx - sla-wizard context
13
+ */
14
+ function configNginxBaseUrl(options, ctx) {
15
+ nginxStrip.configNginxStrip(options, ctx);
16
+ applyBaseUrlTransformations(options.outDir, options.oas || "./specs/oas.yaml");
17
+ console.log("✓ x-nginx-server-baseurl transformations applied");
18
+ }
19
+
20
+ /**
21
+ * Generates only conf.d/ files with both strip and base URL transformations.
22
+ *
23
+ * @param {Object} options - Command options (outDir, oas, sla, …)
24
+ * @param {Object} ctx - sla-wizard context
25
+ */
26
+ function addToBaseUrlConfd(options, ctx) {
27
+ nginxStrip.addToStripConfd(options, ctx);
28
+ applyBaseUrlTransformations(options.outDir, options.oas || "./specs/oas.yaml");
29
+ console.log("✓ x-nginx-server-baseurl transformations applied");
30
+ }
31
+
32
+ module.exports = { configNginxBaseUrl, addToBaseUrlConfd };
@@ -0,0 +1,117 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const jsyaml = require("js-yaml");
4
+ const { sanitizeEndpoint } = require("./sanitize");
5
+
6
+ /**
7
+ * Replaces proxy_pass directives in location blocks that correspond to
8
+ * endpoints declaring x-nginx-server-baseurl in the OAS.
9
+ *
10
+ * Scans the config line by line:
11
+ * - On a `location` line, checks whether any sanitized endpoint key appears
12
+ * in the location name. If so, marks that endpoint as "active".
13
+ * - On a `proxy_pass` line inside an active location, replaces the default
14
+ * server URL with the endpoint-specific custom URL.
15
+ * - On a closing `}` line, resets the active endpoint tracker.
16
+ *
17
+ * Location blocks generated by sla-wizard are flat (no nested braces), so the
18
+ * single-level `}` detection is reliable.
19
+ *
20
+ * @param {string} configContent - nginx config file content
21
+ * @param {Object} baseUrlMap - Map of sanitizedEndpoint -> customBaseUrl
22
+ * @param {string} defaultUrl - The default proxy target (servers[0].url)
23
+ * @returns {string} Modified config content
24
+ */
25
+ function applyBaseUrlToConfig(configContent, baseUrlMap, defaultUrl) {
26
+ if (Object.keys(baseUrlMap).length === 0) return configContent;
27
+
28
+ const lines = configContent.split("\n");
29
+ let currentSanitized = null;
30
+ const result = [];
31
+
32
+ for (const line of lines) {
33
+ const trimmed = line.trim();
34
+
35
+ // Detect start of a location block
36
+ if (trimmed.startsWith("location")) {
37
+ currentSanitized = null;
38
+ for (const sanitized of Object.keys(baseUrlMap)) {
39
+ if (trimmed.includes(sanitized)) {
40
+ currentSanitized = sanitized;
41
+ break;
42
+ }
43
+ }
44
+ }
45
+
46
+ // Replace proxy_pass inside a matched location block
47
+ if (currentSanitized !== null && trimmed.startsWith("proxy_pass")) {
48
+ result.push(line.replace(defaultUrl, baseUrlMap[currentSanitized]));
49
+ } else {
50
+ result.push(line);
51
+ }
52
+
53
+ // Detect end of the location block (flat structure — no nested braces)
54
+ if (trimmed === "}" && currentSanitized !== null) {
55
+ currentSanitized = null;
56
+ }
57
+ }
58
+
59
+ return result.join("\n");
60
+ }
61
+
62
+ /**
63
+ * Reads the OAS file, finds endpoints with x-nginx-server-baseurl, and
64
+ * rewrites the generated nginx config files so that their proxy_pass
65
+ * directives use the endpoint-specific base URL instead of the global default.
66
+ *
67
+ * @param {string} outDir - Output directory containing nginx.conf and conf.d/
68
+ * @param {string} oasPath - Path to the OAS file
69
+ */
70
+ function applyBaseUrlTransformations(outDir, oasPath) {
71
+ const oasDoc = jsyaml.load(fs.readFileSync(oasPath, "utf8"));
72
+ const defaultUrl = oasDoc.servers[0].url;
73
+
74
+ // Build map: sanitizedEndpoint -> customBaseUrl
75
+ const baseUrlMap = {};
76
+ for (const endpoint in oasDoc.paths) {
77
+ const customUrl = oasDoc.paths[endpoint]["x-nginx-server-baseurl"];
78
+ if (customUrl) {
79
+ baseUrlMap[sanitizeEndpoint(endpoint)] = customUrl;
80
+ }
81
+ }
82
+
83
+ if (Object.keys(baseUrlMap).length === 0) return;
84
+
85
+ // Post-process nginx.conf
86
+ const nginxConfPath = path.join(outDir, "nginx.conf");
87
+ if (fs.existsSync(nginxConfPath)) {
88
+ fs.writeFileSync(
89
+ nginxConfPath,
90
+ applyBaseUrlToConfig(
91
+ fs.readFileSync(nginxConfPath, "utf8"),
92
+ baseUrlMap,
93
+ defaultUrl,
94
+ ),
95
+ );
96
+ }
97
+
98
+ // Post-process conf.d/*.conf
99
+ const confDDir = path.join(outDir, "conf.d");
100
+ if (fs.existsSync(confDDir)) {
101
+ fs.readdirSync(confDDir).forEach((file) => {
102
+ if (file.endsWith(".conf")) {
103
+ const filePath = path.join(confDDir, file);
104
+ fs.writeFileSync(
105
+ filePath,
106
+ applyBaseUrlToConfig(
107
+ fs.readFileSync(filePath, "utf8"),
108
+ baseUrlMap,
109
+ defaultUrl,
110
+ ),
111
+ );
112
+ }
113
+ });
114
+ }
115
+ }
116
+
117
+ module.exports = { applyBaseUrlToConfig, applyBaseUrlTransformations };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Mirrors sla-wizard's sanitizeEndpoint: keeps only [A-Za-z0-9-] and drops
3
+ * everything else (slashes, underscores, dots, …).
4
+ */
5
+ function sanitizeEndpoint(input) {
6
+ return input.replace(/[^A-Za-z0-9-]/g, "");
7
+ }
8
+
9
+ module.exports = { sanitizeEndpoint };