nestjs-openapi-next 1.0.2 → 1.0.4

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.
@@ -0,0 +1,70 @@
1
+ <component name="ProjectCodeStyleConfiguration">
2
+ <code_scheme name="Project" version="173">
3
+ <HTMLCodeStyleSettings>
4
+ <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
5
+ </HTMLCodeStyleSettings>
6
+ <JSCodeStyleSettings version="0">
7
+ <option name="FORCE_SEMICOLON_STYLE" value="true" />
8
+ <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
9
+ <option name="FORCE_QUOTE_STYlE" value="true" />
10
+ <option name="ENFORCE_TRAILING_COMMA" value="Remove" />
11
+ <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
12
+ <option name="SPACES_WITHIN_IMPORTS" value="true" />
13
+ </JSCodeStyleSettings>
14
+ <JetCodeStyleSettings>
15
+ <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
16
+ </JetCodeStyleSettings>
17
+ <TypeScriptCodeStyleSettings version="0">
18
+ <option name="FORCE_SEMICOLON_STYLE" value="true" />
19
+ <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
20
+ <option name="FORCE_QUOTE_STYlE" value="true" />
21
+ <option name="ENFORCE_TRAILING_COMMA" value="Remove" />
22
+ <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
23
+ <option name="SPACES_WITHIN_IMPORTS" value="true" />
24
+ </TypeScriptCodeStyleSettings>
25
+ <VueCodeStyleSettings>
26
+ <option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
27
+ <option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
28
+ </VueCodeStyleSettings>
29
+ <codeStyleSettings language="HTML">
30
+ <option name="SOFT_MARGINS" value="80" />
31
+ <indentOptions>
32
+ <option name="INDENT_SIZE" value="2" />
33
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
34
+ <option name="TAB_SIZE" value="2" />
35
+ </indentOptions>
36
+ </codeStyleSettings>
37
+ <codeStyleSettings language="JAVA">
38
+ <indentOptions>
39
+ <option name="USE_TAB_CHARACTER" value="true" />
40
+ <option name="SMART_TABS" value="true" />
41
+ </indentOptions>
42
+ </codeStyleSettings>
43
+ <codeStyleSettings language="JavaScript">
44
+ <option name="SOFT_MARGINS" value="80" />
45
+ <indentOptions>
46
+ <option name="INDENT_SIZE" value="2" />
47
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
48
+ <option name="TAB_SIZE" value="2" />
49
+ </indentOptions>
50
+ </codeStyleSettings>
51
+ <codeStyleSettings language="TypeScript">
52
+ <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
53
+ <option name="SOFT_MARGINS" value="80" />
54
+ <indentOptions>
55
+ <option name="INDENT_SIZE" value="2" />
56
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
57
+ <option name="TAB_SIZE" value="2" />
58
+ </indentOptions>
59
+ </codeStyleSettings>
60
+ <codeStyleSettings language="Vue">
61
+ <option name="SOFT_MARGINS" value="80" />
62
+ <indentOptions>
63
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
64
+ </indentOptions>
65
+ </codeStyleSettings>
66
+ <codeStyleSettings language="kotlin">
67
+ <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
68
+ </codeStyleSettings>
69
+ </code_scheme>
70
+ </component>
@@ -0,0 +1,5 @@
1
+ <component name="ProjectCodeStyleConfiguration">
2
+ <state>
3
+ <option name="USE_PER_PROJECT_SETTINGS" value="true" />
4
+ </state>
5
+ </component>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="TypeScriptCompiler">
4
+ <option name="useServicePoweredTypesEnabledManually" value="true" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AgentMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Ask2AgentMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="EditMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,13 @@
1
+ .idea
2
+ .vscode
3
+ node_modules/
4
+ dist/
5
+ vendor/
6
+ cache/
7
+ .*/
8
+ *.min.*
9
+ *.test.*
10
+ *.spec.*
11
+ *.bundle.*
12
+ *.bundle-min.*
13
+ *.log
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
+ </profile>
6
+ </component>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/nestjs-openapi-next.iml" filepath="$PROJECT_DIR$/.idea/nestjs-openapi-next.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$" />
5
+ <orderEntry type="inheritedJdk" />
6
+ <orderEntry type="sourceFolder" forTests="false" />
7
+ </component>
8
+ </module>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="PrettierConfiguration">
4
+ <option name="myConfigurationMode" value="AUTOMATIC" />
5
+ </component>
6
+ </project>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
package/README.md CHANGED
@@ -100,7 +100,7 @@ This fork treats tag `summary` and `x-displayName` as equivalent display fields:
100
100
 
101
101
  #### Auto-derived (recommended)
102
102
 
103
- If you use tag `parent` relationships (via `@ApiTag()`), the root-level `x-tagGroups` will be derived automatically:
103
+ If you use tag `parent` relationships (via `@ApiTag()`), the root-level `x-tagGroups` will be derived automatically (both in the scanned output and in the final document), so downstream generators (e.g. `.tags`) can render grouped tags correctly:
104
104
 
105
105
  ```ts
106
106
  @ApiTag({ name: 'Customers' })
@@ -3,6 +3,7 @@ import { HttpServer } from '@nestjs/common/interfaces/http/http-server.interface
3
3
  import { OpenAPIObject, SwaggerCustomOptions, SwaggerDocumentOptions } from './interfaces';
4
4
  export declare class SwaggerModule {
5
5
  private static readonly metadataLoader;
6
+ private static collectOperationTagNames;
6
7
  private static mergeWebhooks;
7
8
  private static mergeTags;
8
9
  private static buildXTagGroups;
@@ -21,6 +21,8 @@ const normalize_rel_path_1 = require("./utils/normalize-rel-path");
21
21
  const resolve_path_util_1 = require("./utils/resolve-path.util");
22
22
  const validate_global_prefix_util_1 = require("./utils/validate-global-prefix.util");
23
23
  const validate_path_util_1 = require("./utils/validate-path.util");
24
+ const build_x_tag_groups_util_1 = require("./utils/build-x-tag-groups.util");
25
+ const collect_operation_tag_names_util_1 = require("./utils/collect-operation-tag-names.util");
24
26
  const NULL_TYPE_SCHEMA = { type: 'null' };
25
27
  function isReferenceObject(value) {
26
28
  return !!(value === null || value === void 0 ? void 0 : value.$ref);
@@ -320,13 +322,16 @@ function normalizeNullableForOas31(document) {
320
322
  }
321
323
  }
322
324
  class SwaggerModule {
325
+ static collectOperationTagNames(paths, webhooks) {
326
+ return (0, collect_operation_tag_names_util_1.collectOperationTagNames)(paths, webhooks);
327
+ }
323
328
  static mergeWebhooks(configWebhooks, scannedWebhooks) {
324
329
  if (!configWebhooks && !scannedWebhooks) {
325
330
  return undefined;
326
331
  }
327
332
  return (0, assign_two_levels_deep_1.assignTwoLevelsDeep)({}, configWebhooks || {}, scannedWebhooks || {});
328
333
  }
329
- static mergeTags(configTags, scannedTags) {
334
+ static mergeTags(configTags, scannedTags, operationTagNames) {
330
335
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
331
336
  const byName = new Map();
332
337
  for (const tag of configTags || []) {
@@ -353,71 +358,33 @@ class SwaggerModule {
353
358
  merged.kind = (_j = existing.kind) !== null && _j !== void 0 ? _j : tag.kind;
354
359
  byName.set(tag.name, merged);
355
360
  }
361
+ if (byName.size > 0) {
362
+ for (const name of operationTagNames || []) {
363
+ if (!name || byName.has(name)) {
364
+ continue;
365
+ }
366
+ byName.set(name, { name });
367
+ }
368
+ }
356
369
  const merged = [...byName.values()];
357
370
  return merged.length > 0 ? merged : undefined;
358
371
  }
359
- static buildXTagGroups(tags) {
360
- if (!tags || tags.length === 0) {
361
- return undefined;
362
- }
363
- const groupToTags = new Map();
364
- const groupToSeen = new Map();
365
- for (const tag of tags) {
366
- const parent = tag.parent;
367
- if (!parent) {
368
- continue;
369
- }
370
- if (!groupToTags.has(parent)) {
371
- groupToTags.set(parent, []);
372
- groupToSeen.set(parent, new Set());
373
- }
374
- let list = groupToTags.get(parent);
375
- if (!list) {
376
- list = [];
377
- groupToTags.set(parent, list);
378
- }
379
- let seen = groupToSeen.get(parent);
380
- if (!seen) {
381
- seen = new Set();
382
- groupToSeen.set(parent, seen);
383
- }
384
- if (!seen.has(tag.name)) {
385
- seen.add(tag.name);
386
- list.push(tag.name);
387
- }
388
- }
389
- if (groupToTags.size === 0) {
390
- return undefined;
391
- }
392
- const allTagNames = new Set(tags.map((t) => t.name));
393
- return [...groupToTags.entries()].map(([name, childTags]) => {
394
- const finalTags = [];
395
- const seen = new Set();
396
- if (allTagNames.has(name)) {
397
- seen.add(name);
398
- finalTags.push(name);
399
- }
400
- for (const t of childTags) {
401
- if (!seen.has(t)) {
402
- seen.add(t);
403
- finalTags.push(t);
404
- }
405
- }
406
- return { name, tags: finalTags };
407
- });
372
+ static buildXTagGroups(tags, operationTagNames) {
373
+ return (0, build_x_tag_groups_util_1.buildXTagGroups)(tags, operationTagNames);
408
374
  }
409
375
  static createDocument(app, config, options = {}) {
410
376
  const swaggerScanner = new swagger_scanner_1.SwaggerScanner();
411
377
  const document = swaggerScanner.scanApplication(app, options);
412
378
  document.components = (0, assign_two_levels_deep_1.assignTwoLevelsDeep)({}, config.components, document.components);
413
- const mergedTags = SwaggerModule.mergeTags(config.tags, document.tags);
379
+ const operationTagNames = SwaggerModule.collectOperationTagNames(document.paths, document.webhooks);
380
+ const mergedTags = SwaggerModule.mergeTags(config.tags, document.tags, operationTagNames);
414
381
  const mergedWebhooks = SwaggerModule.mergeWebhooks(config.webhooks, document.webhooks);
415
382
  const mergedDocument = Object.assign(Object.assign(Object.assign(Object.assign({ openapi: '3.0.0', paths: {} }, config), document), (mergedTags ? { tags: mergedTags } : {})), (mergedWebhooks ? { webhooks: mergedWebhooks } : {}));
416
383
  if (isOas31OrAbove(mergedDocument.openapi)) {
417
384
  normalizeNullableForOas31(mergedDocument);
418
385
  }
419
386
  if (mergedDocument['x-tagGroups'] === undefined) {
420
- const xTagGroups = SwaggerModule.buildXTagGroups(mergedDocument.tags);
387
+ const xTagGroups = SwaggerModule.buildXTagGroups(mergedDocument.tags, operationTagNames);
421
388
  if (xTagGroups) {
422
389
  mergedDocument['x-tagGroups'] = xTagGroups;
423
390
  }
@@ -11,6 +11,8 @@ const swagger_explorer_1 = require("./swagger-explorer");
11
11
  const swagger_transformer_1 = require("./swagger-transformer");
12
12
  const get_global_prefix_1 = require("./utils/get-global-prefix");
13
13
  const strip_last_slash_util_1 = require("./utils/strip-last-slash.util");
14
+ const build_x_tag_groups_util_1 = require("./utils/build-x-tag-groups.util");
15
+ const collect_operation_tag_names_util_1 = require("./utils/collect-operation-tag-names.util");
14
16
  class SwaggerScanner {
15
17
  constructor() {
16
18
  this.transformer = new swagger_transformer_1.SwaggerTransformer();
@@ -56,7 +58,10 @@ class SwaggerScanner {
56
58
  });
57
59
  const schemas = this.explorer.getSchemas();
58
60
  this.addExtraModels(schemas, extraModels);
59
- return Object.assign(Object.assign(Object.assign({}, this.transformer.normalizePaths((0, lodash_1.flatten)(denormalizedPaths))), (tagGroups.length > 0 ? { tags: tagGroups } : {})), { components: {
61
+ const normalized = this.transformer.normalizePaths((0, lodash_1.flatten)(denormalizedPaths));
62
+ const operationTagNames = (0, collect_operation_tag_names_util_1.collectOperationTagNames)(normalized.paths, normalized.webhooks);
63
+ const xTagGroups = (0, build_x_tag_groups_util_1.buildXTagGroups)(tagGroups, operationTagNames);
64
+ return Object.assign(Object.assign(Object.assign(Object.assign({}, normalized), (tagGroups.length > 0 ? { tags: tagGroups } : {})), (xTagGroups ? { 'x-tagGroups': xTagGroups } : {})), { components: {
60
65
  schemas: schemas
61
66
  } });
62
67
  }
@@ -0,0 +1,5 @@
1
+ import { TagObject } from '../interfaces/open-api-spec.interface';
2
+ export declare function buildXTagGroups(tags?: TagObject[], operationTagNames?: string[]): {
3
+ name: string;
4
+ tags: string[];
5
+ }[];
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildXTagGroups = buildXTagGroups;
4
+ function buildXTagGroups(tags, operationTagNames) {
5
+ const opNames = new Set((operationTagNames || [])
6
+ .filter((t) => typeof t === 'string')
7
+ .map((t) => t.trim())
8
+ .filter(Boolean));
9
+ if ((!tags || tags.length === 0) && opNames.size === 0) {
10
+ return undefined;
11
+ }
12
+ const groupToChildTags = new Map();
13
+ const groupToSeen = new Map();
14
+ const childToParent = new Map();
15
+ for (const tag of tags || []) {
16
+ const parent = tag.parent;
17
+ if (!parent) {
18
+ continue;
19
+ }
20
+ childToParent.set(tag.name, parent);
21
+ if (!groupToChildTags.has(parent)) {
22
+ groupToChildTags.set(parent, []);
23
+ groupToSeen.set(parent, new Set());
24
+ }
25
+ let list = groupToChildTags.get(parent);
26
+ if (!list) {
27
+ list = [];
28
+ groupToChildTags.set(parent, list);
29
+ }
30
+ let seen = groupToSeen.get(parent);
31
+ if (!seen) {
32
+ seen = new Set();
33
+ groupToSeen.set(parent, seen);
34
+ }
35
+ if (!seen.has(tag.name)) {
36
+ seen.add(tag.name);
37
+ list.push(tag.name);
38
+ }
39
+ }
40
+ for (const name of opNames) {
41
+ if (childToParent.has(name)) {
42
+ continue;
43
+ }
44
+ if (!groupToChildTags.has(name)) {
45
+ groupToChildTags.set(name, []);
46
+ groupToSeen.set(name, new Set());
47
+ }
48
+ }
49
+ if (groupToChildTags.size === 0) {
50
+ return undefined;
51
+ }
52
+ const allTagNames = new Set((tags || []).map((t) => t.name));
53
+ return [...groupToChildTags.entries()].map(([name, childTags]) => {
54
+ const finalTags = [];
55
+ const seen = new Set();
56
+ if (allTagNames.has(name) || opNames.has(name)) {
57
+ seen.add(name);
58
+ finalTags.push(name);
59
+ }
60
+ for (const t of childTags) {
61
+ if (!seen.has(t)) {
62
+ seen.add(t);
63
+ finalTags.push(t);
64
+ }
65
+ }
66
+ return { name, tags: finalTags };
67
+ });
68
+ }
@@ -0,0 +1,2 @@
1
+ import { OpenAPIObject } from '../interfaces/open-api-spec.interface';
2
+ export declare function collectOperationTagNames(paths?: OpenAPIObject['paths'], webhooks?: OpenAPIObject['webhooks']): string[];
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collectOperationTagNames = collectOperationTagNames;
4
+ const HTTP_METHODS = new Set([
5
+ 'get',
6
+ 'put',
7
+ 'post',
8
+ 'delete',
9
+ 'options',
10
+ 'head',
11
+ 'patch',
12
+ 'trace',
13
+ 'search',
14
+ 'query'
15
+ ]);
16
+ function collectOperationTagNames(paths, webhooks) {
17
+ const names = [];
18
+ const seen = new Set();
19
+ const collectFromItems = (items) => {
20
+ if (!items) {
21
+ return;
22
+ }
23
+ for (const pathItem of Object.values(items)) {
24
+ if (!pathItem || typeof pathItem !== 'object') {
25
+ continue;
26
+ }
27
+ for (const [key, operation] of Object.entries(pathItem)) {
28
+ if (!HTTP_METHODS.has(String(key).toLowerCase())) {
29
+ continue;
30
+ }
31
+ const tags = operation === null || operation === void 0 ? void 0 : operation.tags;
32
+ if (!Array.isArray(tags)) {
33
+ continue;
34
+ }
35
+ for (const t of tags) {
36
+ if (typeof t !== 'string') {
37
+ continue;
38
+ }
39
+ const trimmed = t.trim();
40
+ if (!trimmed || seen.has(trimmed)) {
41
+ continue;
42
+ }
43
+ seen.add(trimmed);
44
+ names.push(trimmed);
45
+ }
46
+ }
47
+ }
48
+ };
49
+ collectFromItems(paths);
50
+ collectFromItems(webhooks);
51
+ return names;
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-openapi-next",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A fork of @nestjs/swagger support OAS 3.2",
5
5
  "author": "undownding",
6
6
  "license": "MIT",
@@ -1,25 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "WebSearch",
5
- "WebFetch(domain:spec.openapis.org)",
6
- "WebFetch(domain:www.openapis.org)",
7
- "WebFetch(domain:blog.stoplight.io)",
8
- "WebFetch(domain:apisyouwonthate.com)",
9
- "WebFetch(domain:github.com)",
10
- "Bash(gh auth status:*)",
11
- "Bash(gh issue create:*)",
12
- "Bash(gh issue view:*)",
13
- "Bash(gh issue edit:*)",
14
- "Bash(npm test)",
15
- "Bash(npm install)",
16
- "Bash(npm test:*)",
17
- "Bash(gh pr view:*)",
18
- "Bash(git checkout:*)",
19
- "Bash(git add:*)",
20
- "Bash(git commit -m \"$\\(cat <<''EOF''\ndocs: update README with OAS 3.1/3.2 features\n\nAdd documentation for newly implemented OpenAPI 3.1/3.2 features:\n- JSON Schema Draft 2020-12 alignment\n- type as array support \\(replaces nullable\\)\n- LicenseObject.identifier\n- ServerObject.pathPrefix\n- InfoObject.tags\n- ReferenceObject summary/description override\n- exclusiveMinimum/exclusiveMaximum type change\n\nCloses #7\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
21
- "Bash(git push:*)",
22
- "Bash(gh pr create:*)"
23
- ]
24
- }
25
- }