From df137242ca3e783fbaadce583e131a1d9cc9f8c0 Mon Sep 17 00:00:00 2001 From: Vladyslav Novikov Date: Wed, 18 Mar 2026 12:52:08 +0200 Subject: [PATCH 1/8] feat: add shareability filtering to export strategy --- src/strategies/export-version.strategy.ts | 44 ++++++++++- src/types/external/config.ts | 2 + src/types/external/documents.ts | 19 ++++- test/export.test.ts | 91 +++++++++++++++++++++++ test/helpers/registry/local.ts | 6 ++ 5 files changed, 155 insertions(+), 7 deletions(-) diff --git a/src/strategies/export-version.strategy.ts b/src/strategies/export-version.strategy.ts index 0b41c196..48787521 100644 --- a/src/strategies/export-version.strategy.ts +++ b/src/strategies/export-version.strategy.ts @@ -14,7 +14,16 @@ * limitations under the License. */ -import { BuilderStrategy, BuildResult, BuildTypeContexts, ExportDocument, ExportVersionBuildConfig } from '../types' +import { + BuilderStrategy, + BuildResult, + BuildTypeContexts, + ExportDocument, + ExportVersionBuildConfig, + ResolvedVersionDocument, + SHAREABILITY_STATUS_UNKNOWN, + ShareabilityStatus, +} from '../types' import { getDocumentTitle, getSplittedVersionKey } from '../utils' import { createCommonStaticExportDocuments, @@ -60,11 +69,12 @@ async function exportToHTML(config: ExportVersionBuildConfig, buildResult: Build const [version] = getSplittedVersionKey(versionWithRevision) const { name: packageName } = await packageResolver(packageId) const { documents } = await versionDocumentsResolver(versionWithRevision, packageId) ?? { documents: [] } + const documentsFilteredByShareabilityStatus = filterDocumentsByShareabilityStatus(documents, config) const generatedHtmlExportDocuments: ExportDocument[] = [] - const restDocuments = documents.filter(isRestDocument) + const restDocuments = documentsFilteredByShareabilityStatus.filter(isRestDocument) const shouldAddIndexPage = restDocuments.length > 0 - const transformedDocuments = await Promise.all(documents.map(async document => { + const transformedDocuments = await Promise.all(documentsFilteredByShareabilityStatus.map(async document => { const { createExportDocument } = apiBuilders.find(({ types }) => types.includes(document.type)) || unknownApiBuilder const file = await rawDocumentResolver(versionWithRevision, packageId, document.slug) return await createExportDocument?.(file.name, await file.text(), format, packageName, version, templateResolver, allowedOasExtensions, generatedHtmlExportDocuments, shouldAddIndexPage) ?? createUnknownExportDocument(file.name, file) @@ -93,8 +103,9 @@ async function defaultExport(config: ExportVersionBuildConfig, buildResult: Buil const [version] = getSplittedVersionKey(versionWithRevision) const { name: packageName } = await packageResolver(packageId) const { documents } = await versionDocumentsResolver(versionWithRevision, packageId) ?? { documents: [] } + const documentsFilteredByShareabilityStatus = filterDocumentsByShareabilityStatus(documents, config) - const transformedDocuments = await Promise.all(documents.map(async document => { + const transformedDocuments = await Promise.all(documentsFilteredByShareabilityStatus.map(async document => { const { createExportDocument } = apiBuilders.find(({ types }) => types.includes(document.type)) || unknownApiBuilder const file = await rawDocumentResolver(versionWithRevision, packageId, document.slug) return await createExportDocument?.(file.name, await file.text(), format, packageName, version, templateResolver, allowedOasExtensions) ?? createUnknownExportDocument(file.name, file) @@ -102,3 +113,28 @@ async function defaultExport(config: ExportVersionBuildConfig, buildResult: Buil buildResult.exportDocuments.push(...transformedDocuments) } + +function filterDocumentsByShareabilityStatus( + documents: ReadonlyArray, + config: ExportVersionBuildConfig, +): ReadonlyArray { + const { allowedShareabilityStatuses } = config + + // DEBUG ONLY - remove after verifying export works correctly + console.log('[export-version] filterByShareability called:', { + allowedShareabilityStatuses: allowedShareabilityStatuses, + totalDocs: documents.length, + statuses: documents.map(d => ({ slug: d.slug, shareabilityStatus: d.shareabilityStatus })), + }) + + if (!allowedShareabilityStatuses || allowedShareabilityStatuses.length === 0) { + return documents + } + const allowedSet = new Set(allowedShareabilityStatuses) + const filtered = documents.filter(doc => allowedSet.has(doc.shareabilityStatus ?? SHAREABILITY_STATUS_UNKNOWN)) + + // DEBUG ONLY - remove after verifying export works correctly + console.log('[export-version] filtered to', filtered.length, 'shareable documents') + + return filtered +} diff --git a/src/types/external/config.ts b/src/types/external/config.ts index 7a279a62..833122c2 100644 --- a/src/types/external/config.ts +++ b/src/types/external/config.ts @@ -24,6 +24,7 @@ import { VERSION_STATUS, } from '../../consts' import { OpenApiExtensionKey } from '@netcracker/qubership-apihub-api-unifier' +import { ShareabilityStatus } from './documents' export type BuildType = KeyOfConstType export type VersionStatus = KeyOfConstType @@ -104,6 +105,7 @@ export interface ExportVersionBuildConfig extends BuildConfigBase { version: VersionId format: ExportFormat allowedOasExtensions?: OpenApiExtensionKey[] + readonly allowedShareabilityStatuses?: readonly ShareabilityStatus[] } export interface ExportRestDocumentBuildConfig extends BuildConfigBase { diff --git a/src/types/external/documents.ts b/src/types/external/documents.ts index a92b32fd..32b93a7a 100644 --- a/src/types/external/documents.ts +++ b/src/types/external/documents.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { FileId, OperationsApiType, PackageId, TemplatePath, VersionId } from './types' -import { ResolvedReferenceMap } from './references' -import { FileFormat } from '../internal' import { ApihubApiCompatibilityKind } from '../../consts' +import { FileFormat } from '../internal' +import { ResolvedReferenceMap } from './references' +import { FileId, OperationsApiType, PackageId, TemplatePath, VersionId } from './types' export type ResolvedDocument = { fileId: string @@ -44,6 +44,7 @@ export type ResolvedVersionDocuments = { export type ResolvedVersionDocument = ResolvedDocument & { packageRef?: string apiKind?: ApihubApiCompatibilityKind + shareabilityStatus?: ShareabilityStatus } export type Labels = string[] @@ -76,3 +77,15 @@ export type RawDocumentResolver = ( export type FileResolver = (fileId: FileId) => Promise export type TemplateResolver = (templatePath: TemplatePath) => Promise + +export const SHAREABILITY_STATUS_SHAREABLE = 'shareable' as const +export const SHAREABILITY_STATUS_NON_SHAREABLE = 'non-shareable' as const +export const SHAREABILITY_STATUS_UNKNOWN = 'unknown' as const + +export const SHAREABILITY_STATUSES = [ + SHAREABILITY_STATUS_SHAREABLE, + SHAREABILITY_STATUS_NON_SHAREABLE, + SHAREABILITY_STATUS_UNKNOWN, +] as const + +export type ShareabilityStatus = (typeof SHAREABILITY_STATUSES)[number] diff --git a/test/export.test.ts b/test/export.test.ts index f7d33722..0cc673b8 100644 --- a/test/export.test.ts +++ b/test/export.test.ts @@ -16,11 +16,15 @@ import { Editor, exportDocumentMatcher, exportDocumentsMatcher, loadFileAsString, LocalRegistry } from './helpers' import { + SHAREABILITY_STATUSES, BUILD_TYPE, ExportRestOperationsGroupBuildConfig, FILE_FORMAT_HTML, FILE_FORMAT_JSON, FILE_FORMAT_YAML, + SHAREABILITY_STATUS_SHAREABLE, + SHAREABILITY_STATUS_NON_SHAREABLE, + SHAREABILITY_STATUS_UNKNOWN, TRANSFORMATION_KIND_MERGED, TRANSFORMATION_KIND_REDUCED, } from '../src' @@ -397,3 +401,90 @@ describe('Export test', () => { expect(await result.exportDocuments[0].data.text()).toEqual(expectedResult) }) }) + +describe('Export shareability filtering', () => { + let shareabilityPkg: LocalRegistry + let shareabilityEditor: Editor + const SHAREABILITY_VERSION = 'shareability-version@1' + + beforeAll(async () => { + shareabilityPkg = LocalRegistry.openPackage('export') + await shareabilityPkg.publish(shareabilityPkg.packageId, { version: SHAREABILITY_VERSION }) + + shareabilityPkg.setDocumentShareability('1', SHAREABILITY_STATUS_SHAREABLE) + shareabilityPkg.setDocumentShareability('2', SHAREABILITY_STATUS_NON_SHAREABLE) + + shareabilityEditor = await Editor.openProject(shareabilityPkg.packageId, shareabilityPkg) + }) + + test('should export all documents when allowedShareabilityStatuses includes all statuses', async () => { + const result = await shareabilityEditor.run({ + packageId: shareabilityPkg.packageId, + buildType: BUILD_TYPE.EXPORT_VERSION, + version: SHAREABILITY_VERSION, + format: FILE_FORMAT_YAML, + allowedShareabilityStatuses: SHAREABILITY_STATUSES, + }) + const exportedFilenames = result.exportDocuments.map(d => d.filename) + expect(exportedFilenames).toContain('1.yaml') + expect(exportedFilenames).toContain('2.yaml') + }) + + test('should export all documents when allowedShareabilityStatuses is not provided', async () => { + const result = await shareabilityEditor.run({ + packageId: shareabilityPkg.packageId, + buildType: BUILD_TYPE.EXPORT_VERSION, + version: SHAREABILITY_VERSION, + format: FILE_FORMAT_YAML, + }) + const exportedFilenames = result.exportDocuments.map(d => d.filename) + expect(exportedFilenames).toContain('1.yaml') + expect(exportedFilenames).toContain('2.yaml') + }) + + const shareabilityOnlyCases = [ + { + label: 'non-shareable', + version: 'shareability-non-shareable@1', + statuses: [ + ['1', SHAREABILITY_STATUS_SHAREABLE], + ['2', SHAREABILITY_STATUS_NON_SHAREABLE], + ] as const, + }, + { + label: 'unknown', + version: 'shareability-unknown@1', + statuses: [ + ['1', SHAREABILITY_STATUS_SHAREABLE], + ['2', SHAREABILITY_STATUS_UNKNOWN], + ] as const, + }, + ] as const + + test.each(shareabilityOnlyCases)( + 'should export only shareable documents when allowedShareabilityStatuses is [shareable] and second doc is $label', + async ({ version, statuses }) => { + const pkg = LocalRegistry.openPackage('export') + await pkg.publish(pkg.packageId, { version }) + statuses.forEach(([slug, status]) => pkg.setDocumentShareability(slug, status)) + + const caseEditor = await Editor.openProject(pkg.packageId, pkg) + + const exportFormats = [FILE_FORMAT_YAML, FILE_FORMAT_HTML] as const + for (const format of exportFormats) { + const result = await caseEditor.run({ + packageId: pkg.packageId, + buildType: BUILD_TYPE.EXPORT_VERSION, + version, + format, + allowedShareabilityStatuses: [SHAREABILITY_STATUS_SHAREABLE], + }) + + const exportedFilenames = result.exportDocuments.map(d => d.filename) + + expect(exportedFilenames).toContain(`1.${format}`) + expect(exportedFilenames).not.toContain(`2.${format}`) + } + }, + ) +}) diff --git a/test/helpers/registry/local.ts b/test/helpers/registry/local.ts index 084a3429..727fef2e 100644 --- a/test/helpers/registry/local.ts +++ b/test/helpers/registry/local.ts @@ -130,12 +130,17 @@ export class LocalRegistry implements IRegistry { groupToOperationIdsMap: Record = {} projectsDir: string = DEFAULT_PROJECTS_PATH + shareabilityOverrides: Map = new Map() constructor(public packageId: string, groupOperationIds: Record = {}, projectsDir: string = DEFAULT_PROJECTS_PATH) { this.groupToOperationIdsMap = groupOperationIds this.projectsDir = projectsDir } + setDocumentShareability(slug: string, status: string): void { + this.shareabilityOverrides.set(slug, status) + } + static openPackage(packageId: string, groupOperationIds: Record = {}, projectsDir: string = DEFAULT_PROJECTS_PATH): LocalRegistry { return new LocalRegistry(packageId, groupOperationIds, projectsDir) } @@ -334,6 +339,7 @@ export class LocalRegistry implements IRegistry { description: document.description, data: this.resolveDocumentData(document), ...takeIfDefined({ packageRef: refId }), + ...takeIfDefined({ shareabilityStatus: this.shareabilityOverrides.get(document.slug) }), })) } From fedd3ecac8778b3ab866ee6698ca277bdbad749f Mon Sep 17 00:00:00 2001 From: Vladyslav Novikov Date: Fri, 27 Mar 2026 10:32:31 +0200 Subject: [PATCH 2/8] fix: use original file extension for non rest documents --- src/strategies/export-version.strategy.ts | 31 ++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/strategies/export-version.strategy.ts b/src/strategies/export-version.strategy.ts index 48787521..1bb9eac0 100644 --- a/src/strategies/export-version.strategy.ts +++ b/src/strategies/export-version.strategy.ts @@ -24,7 +24,7 @@ import { SHAREABILITY_STATUS_UNKNOWN, ShareabilityStatus, } from '../types' -import { getDocumentTitle, getSplittedVersionKey } from '../utils' +import { getDocumentTitle, getFileExtension, getSplittedVersionKey } from '../utils' import { createCommonStaticExportDocuments, createSingleFileExportName, @@ -36,12 +36,13 @@ import { FILE_FORMAT_HTML, FILE_FORMAT_JSON } from '../consts' export class ExportVersionStrategy implements BuilderStrategy { async execute(config: ExportVersionBuildConfig, buildResult: BuildResult, contexts: BuildTypeContexts): Promise { + let isSingleNonRestDocument = false switch (config.format) { case FILE_FORMAT_HTML: - await exportToHTML(config, buildResult, contexts) + isSingleNonRestDocument = await exportToHTML(config, buildResult, contexts) break default: - await defaultExport(config, buildResult, contexts) + isSingleNonRestDocument = await defaultExport(config, buildResult, contexts) break } @@ -52,12 +53,22 @@ export class ExportVersionStrategy implements BuilderStrategy { return buildResult } - buildResult.exportFileName = createSingleFileExportName(packageId, version, getDocumentTitle(buildResult.exportDocuments[0].filename), format) + const singleExportDocument = buildResult.exportDocuments[0] + const singleExportDocumentExtension = isSingleNonRestDocument + ? getFileExtension(singleExportDocument.filename) + : format + + buildResult.exportFileName = createSingleFileExportName( + packageId, + version, + getDocumentTitle(singleExportDocument.filename), + singleExportDocumentExtension, + ) return buildResult } } -async function exportToHTML(config: ExportVersionBuildConfig, buildResult: BuildResult, contexts: BuildTypeContexts): Promise { +async function exportToHTML(config: ExportVersionBuildConfig, buildResult: BuildResult, contexts: BuildTypeContexts): Promise { const { versionDocumentsResolver, rawDocumentResolver, @@ -89,9 +100,11 @@ async function exportToHTML(config: ExportVersionBuildConfig, buildResult: Build const readme = await buildResult.exportDocuments.find(({ filename }) => filename.toLowerCase() === 'readme.md')?.data.text() buildResult.exportDocuments.push(createUnknownExportDocument('index.html', await generateIndexHtmlPage(packageName, version, generatedHtmlExportDocuments, templateResolver, readme))) } + + return isSingleNonRestDocument(documentsFilteredByShareabilityStatus) } -async function defaultExport(config: ExportVersionBuildConfig, buildResult: BuildResult, contexts: BuildTypeContexts): Promise { +async function defaultExport(config: ExportVersionBuildConfig, buildResult: BuildResult, contexts: BuildTypeContexts): Promise { const { versionDocumentsResolver, rawDocumentResolver, @@ -112,6 +125,12 @@ async function defaultExport(config: ExportVersionBuildConfig, buildResult: Buil })) buildResult.exportDocuments.push(...transformedDocuments) + + return isSingleNonRestDocument(documentsFilteredByShareabilityStatus) +} + +function isSingleNonRestDocument(documents: ReadonlyArray): boolean { + return documents.length === 1 && !isRestDocument(documents[0]) } function filterDocumentsByShareabilityStatus( From 7c5a2bd908e123b4f1659225a7f92d0cab851e79 Mon Sep 17 00:00:00 2001 From: Vladyslav Novikov Date: Fri, 27 Mar 2026 12:14:35 +0200 Subject: [PATCH 3/8] refactor: remove debug logs --- src/strategies/export-version.strategy.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/strategies/export-version.strategy.ts b/src/strategies/export-version.strategy.ts index 1bb9eac0..faeedee9 100644 --- a/src/strategies/export-version.strategy.ts +++ b/src/strategies/export-version.strategy.ts @@ -139,21 +139,10 @@ function filterDocumentsByShareabilityStatus( ): ReadonlyArray { const { allowedShareabilityStatuses } = config - // DEBUG ONLY - remove after verifying export works correctly - console.log('[export-version] filterByShareability called:', { - allowedShareabilityStatuses: allowedShareabilityStatuses, - totalDocs: documents.length, - statuses: documents.map(d => ({ slug: d.slug, shareabilityStatus: d.shareabilityStatus })), - }) - if (!allowedShareabilityStatuses || allowedShareabilityStatuses.length === 0) { return documents } const allowedSet = new Set(allowedShareabilityStatuses) - const filtered = documents.filter(doc => allowedSet.has(doc.shareabilityStatus ?? SHAREABILITY_STATUS_UNKNOWN)) - - // DEBUG ONLY - remove after verifying export works correctly - console.log('[export-version] filtered to', filtered.length, 'shareable documents') - return filtered + return documents.filter(doc => allowedSet.has(doc.shareabilityStatus ?? SHAREABILITY_STATUS_UNKNOWN)) } From 1b2575fe65e168138d3a2cb668ea2066577dbf5d Mon Sep 17 00:00:00 2001 From: Vladyslav Novikov Date: Fri, 27 Mar 2026 13:12:11 +0200 Subject: [PATCH 4/8] refactor: collect documents before calling for a specific export --- src/strategies/export-version.strategy.ts | 51 ++++++++++++----------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/strategies/export-version.strategy.ts b/src/strategies/export-version.strategy.ts index faeedee9..e86fa482 100644 --- a/src/strategies/export-version.strategy.ts +++ b/src/strategies/export-version.strategy.ts @@ -36,17 +36,24 @@ import { FILE_FORMAT_HTML, FILE_FORMAT_JSON } from '../consts' export class ExportVersionStrategy implements BuilderStrategy { async execute(config: ExportVersionBuildConfig, buildResult: BuildResult, contexts: BuildTypeContexts): Promise { - let isSingleNonRestDocument = false + const { versionDocumentsResolver } = contexts.builderContext(config) + const { packageId, version: versionWithRevision } = config + const { documents } = await versionDocumentsResolver(versionWithRevision, packageId) ?? { documents: [] } + + const documentsToExport = filterDocumentsByShareabilityStatus(documents, config) + + const isSingleNonRestDocument = documents.length === 1 && !isRestDocument(documents[0]) + switch (config.format) { case FILE_FORMAT_HTML: - isSingleNonRestDocument = await exportToHTML(config, buildResult, contexts) + await exportToHTML(config, buildResult, contexts, documentsToExport) break default: - isSingleNonRestDocument = await defaultExport(config, buildResult, contexts) + await defaultExport(config, buildResult, contexts, documentsToExport) break } - const { packageId, version: versionWithRevision, format = FILE_FORMAT_JSON } = config + const { format = FILE_FORMAT_JSON } = config const [version] = getSplittedVersionKey(versionWithRevision) if (buildResult.exportDocuments.length > 1) { buildResult.exportFileName = `${packageId}_${version}.zip` @@ -68,24 +75,26 @@ export class ExportVersionStrategy implements BuilderStrategy { } } -async function exportToHTML(config: ExportVersionBuildConfig, buildResult: BuildResult, contexts: BuildTypeContexts): Promise { +async function exportToHTML( + config: ExportVersionBuildConfig, + buildResult: BuildResult, + contexts: BuildTypeContexts, + documentsToExport: ReadonlyArray, +): Promise { const { - versionDocumentsResolver, rawDocumentResolver, templateResolver, - packageResolver, apiBuilders, + packageResolver, } = contexts.builderContext(config) const { packageId, version: versionWithRevision, format, allowedOasExtensions } = config const [version] = getSplittedVersionKey(versionWithRevision) const { name: packageName } = await packageResolver(packageId) - const { documents } = await versionDocumentsResolver(versionWithRevision, packageId) ?? { documents: [] } - const documentsFilteredByShareabilityStatus = filterDocumentsByShareabilityStatus(documents, config) const generatedHtmlExportDocuments: ExportDocument[] = [] - const restDocuments = documentsFilteredByShareabilityStatus.filter(isRestDocument) + const restDocuments = documentsToExport.filter(isRestDocument) const shouldAddIndexPage = restDocuments.length > 0 - const transformedDocuments = await Promise.all(documentsFilteredByShareabilityStatus.map(async document => { + const transformedDocuments = await Promise.all(documentsToExport.map(async document => { const { createExportDocument } = apiBuilders.find(({ types }) => types.includes(document.type)) || unknownApiBuilder const file = await rawDocumentResolver(versionWithRevision, packageId, document.slug) return await createExportDocument?.(file.name, await file.text(), format, packageName, version, templateResolver, allowedOasExtensions, generatedHtmlExportDocuments, shouldAddIndexPage) ?? createUnknownExportDocument(file.name, file) @@ -100,13 +109,15 @@ async function exportToHTML(config: ExportVersionBuildConfig, buildResult: Build const readme = await buildResult.exportDocuments.find(({ filename }) => filename.toLowerCase() === 'readme.md')?.data.text() buildResult.exportDocuments.push(createUnknownExportDocument('index.html', await generateIndexHtmlPage(packageName, version, generatedHtmlExportDocuments, templateResolver, readme))) } - - return isSingleNonRestDocument(documentsFilteredByShareabilityStatus) } -async function defaultExport(config: ExportVersionBuildConfig, buildResult: BuildResult, contexts: BuildTypeContexts): Promise { +async function defaultExport( + config: ExportVersionBuildConfig, + buildResult: BuildResult, + contexts: BuildTypeContexts, + documentsToExport: ReadonlyArray, +): Promise { const { - versionDocumentsResolver, rawDocumentResolver, templateResolver, packageResolver, @@ -115,22 +126,14 @@ async function defaultExport(config: ExportVersionBuildConfig, buildResult: Buil const { packageId, version: versionWithRevision, format, allowedOasExtensions } = config const [version] = getSplittedVersionKey(versionWithRevision) const { name: packageName } = await packageResolver(packageId) - const { documents } = await versionDocumentsResolver(versionWithRevision, packageId) ?? { documents: [] } - const documentsFilteredByShareabilityStatus = filterDocumentsByShareabilityStatus(documents, config) - const transformedDocuments = await Promise.all(documentsFilteredByShareabilityStatus.map(async document => { + const transformedDocuments = await Promise.all(documentsToExport.map(async document => { const { createExportDocument } = apiBuilders.find(({ types }) => types.includes(document.type)) || unknownApiBuilder const file = await rawDocumentResolver(versionWithRevision, packageId, document.slug) return await createExportDocument?.(file.name, await file.text(), format, packageName, version, templateResolver, allowedOasExtensions) ?? createUnknownExportDocument(file.name, file) })) buildResult.exportDocuments.push(...transformedDocuments) - - return isSingleNonRestDocument(documentsFilteredByShareabilityStatus) -} - -function isSingleNonRestDocument(documents: ReadonlyArray): boolean { - return documents.length === 1 && !isRestDocument(documents[0]) } function filterDocumentsByShareabilityStatus( From dc0ea446c4efad1c9a23d68c99640a08db30c662 Mon Sep 17 00:00:00 2001 From: Vladyslav Novikov Date: Sat, 28 Mar 2026 12:01:19 +0200 Subject: [PATCH 5/8] fix: correct logic for single non-REST document check --- src/strategies/export-version.strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategies/export-version.strategy.ts b/src/strategies/export-version.strategy.ts index e86fa482..a9cd5931 100644 --- a/src/strategies/export-version.strategy.ts +++ b/src/strategies/export-version.strategy.ts @@ -42,7 +42,7 @@ export class ExportVersionStrategy implements BuilderStrategy { const documentsToExport = filterDocumentsByShareabilityStatus(documents, config) - const isSingleNonRestDocument = documents.length === 1 && !isRestDocument(documents[0]) + const isSingleNonRestDocument = documentsToExport.length === 1 && !isRestDocument(documentsToExport[0]) switch (config.format) { case FILE_FORMAT_HTML: From 844d2c66b504115f84f8f2aec870634be5b1880e Mon Sep 17 00:00:00 2001 From: Vladyslav Novikov Date: Mon, 30 Mar 2026 16:24:26 +0300 Subject: [PATCH 6/8] feat: add tests for exporting GraphQL and non-REST documents and improve shareability tests --- test/export-asyncapi.test.ts | 87 +++++++++++++++ test/export-graphql.test.ts | 28 +++++ test/export-non-rest.test.ts | 45 ++++++++ test/export-shareability.test.ts | 102 ++++++++++++++++++ test/export.test.ts | 91 ---------------- test/projects/asyncapi-export/config.json | 20 ++++ .../asyncapi-export/valid-async-json.json | 49 +++++++++ .../asyncapi-export/valid-async-yaml.yaml | 30 ++++++ test/projects/shareability/1.yaml | 35 ++++++ test/projects/shareability/2.yaml | 31 ++++++ test/projects/shareability/3.yaml | 16 +++ test/projects/shareability/config.json | 26 +++++ 12 files changed, 469 insertions(+), 91 deletions(-) create mode 100644 test/export-asyncapi.test.ts create mode 100644 test/export-graphql.test.ts create mode 100644 test/export-non-rest.test.ts create mode 100644 test/export-shareability.test.ts create mode 100644 test/projects/asyncapi-export/config.json create mode 100644 test/projects/asyncapi-export/valid-async-json.json create mode 100644 test/projects/asyncapi-export/valid-async-yaml.yaml create mode 100644 test/projects/shareability/1.yaml create mode 100644 test/projects/shareability/2.yaml create mode 100644 test/projects/shareability/3.yaml create mode 100644 test/projects/shareability/config.json diff --git a/test/export-asyncapi.test.ts b/test/export-asyncapi.test.ts new file mode 100644 index 00000000..c77da13b --- /dev/null +++ b/test/export-asyncapi.test.ts @@ -0,0 +1,87 @@ +import { BUILD_TYPE, FILE_FORMAT_HTML, FILE_FORMAT_JSON, FILE_FORMAT_YAML } from '../src' +import { Editor, exportDocumentMatcher, exportDocumentsMatcher, LocalRegistry } from './helpers' + +describe('Export AsyncAPI version test', () => { + const ASYNCAPI_EXPORT_PACKAGE = 'asyncapi-export' + const SINGLE_ASYNC_YAML_VERSION = 'single-async-yaml-version@1' + const SINGLE_ASYNC_JSON_VERSION = 'single-async-json-version@1' + const DUAL_ASYNC_VERSION = 'dual-async-version@1' + let asyncEditor: Editor + + const getExportedAsyncapiVersion = async (document: { data: Blob }): Promise => { + return (JSON.parse(await document.data.text()) as { asyncapi?: string }).asyncapi + } + + beforeAll(async () => { + const asyncPkg = LocalRegistry.openPackage(ASYNCAPI_EXPORT_PACKAGE) + await asyncPkg.publish(asyncPkg.packageId, { + version: SINGLE_ASYNC_YAML_VERSION, + files: [{ fileId: 'valid-async-yaml.yaml' }], + }) + await asyncPkg.publish(asyncPkg.packageId, { + version: SINGLE_ASYNC_JSON_VERSION, + files: [{ fileId: 'valid-async-json.json' }], + }) + await asyncPkg.publish(asyncPkg.packageId, { + version: DUAL_ASYNC_VERSION, + files: [ + { fileId: 'valid-async-yaml.yaml' }, + { fileId: 'valid-async-json.json' }, + ], + }) + asyncEditor = await Editor.openProject(asyncPkg.packageId, asyncPkg) + }) + + const versionExportFormats = [FILE_FORMAT_JSON, FILE_FORMAT_YAML, FILE_FORMAT_HTML] as const + type VersionExportFormat = typeof versionExportFormats[number] + + test.each(versionExportFormats)( + 'should keep AsyncAPI JSON output for single YAML source regardless of requested export version format %p', + async (format: VersionExportFormat) => { + const result = await asyncEditor.run({ + version: SINGLE_ASYNC_YAML_VERSION, + buildType: BUILD_TYPE.EXPORT_VERSION, + format, + }) + expect(result.exportFileName).toEqual('asyncapi-export_single-async-yaml-version_valid-async-yaml.json') + expect(result).toEqual(exportDocumentsMatcher([ + exportDocumentMatcher('valid-async-yaml.json'), + ])) + expect(await getExportedAsyncapiVersion(result.exportDocuments[0])).toEqual('3.0.0') + }, + ) + + test.each(versionExportFormats)( + 'should keep AsyncAPI JSON output for single JSON source regardless of requested export version format %p', + async (format: VersionExportFormat) => { + const result = await asyncEditor.run({ + version: SINGLE_ASYNC_JSON_VERSION, + buildType: BUILD_TYPE.EXPORT_VERSION, + format, + }) + expect(result.exportFileName).toEqual('asyncapi-export_single-async-json-version_valid-async-json.json') + expect(result).toEqual(exportDocumentsMatcher([ + exportDocumentMatcher('valid-async-json.json'), + ])) + expect(await getExportedAsyncapiVersion(result.exportDocuments[0])).toEqual('3.0.0') + }, + ) + + test('should keep original extensions when exporting two async files', async () => { + const result = await asyncEditor.run({ + version: DUAL_ASYNC_VERSION, + buildType: BUILD_TYPE.EXPORT_VERSION, + format: FILE_FORMAT_JSON, + }) + expect(result.exportFileName).toEqual('asyncapi-export_dual-async-version.zip') + expect(result).toEqual(exportDocumentsMatcher([ + exportDocumentMatcher('valid-async-yaml.json'), + exportDocumentMatcher('valid-async-json.json'), + ])) + + const yamlExport = result.exportDocuments.find((document) => document.filename === 'valid-async-yaml.json')! + const jsonExport = result.exportDocuments.find((document) => document.filename === 'valid-async-json.json')! + expect(await getExportedAsyncapiVersion(yamlExport)).toEqual('3.0.0') + expect(await getExportedAsyncapiVersion(jsonExport)).toEqual('3.0.0') + }) +}) diff --git a/test/export-graphql.test.ts b/test/export-graphql.test.ts new file mode 100644 index 00000000..56ebc0fc --- /dev/null +++ b/test/export-graphql.test.ts @@ -0,0 +1,28 @@ +import { BUILD_TYPE, FILE_FORMAT_GRAPHQL } from '../src' +import { Editor, exportDocumentMatcher, exportDocumentsMatcher, LocalRegistry } from './helpers' + +describe('Export GraphQL version test', () => { + const GRAPHQL_SINGLE_VERSION = 'single-graphql-version@1' + let graphqlEditor: Editor + + beforeAll(async () => { + const graphqlPkg = LocalRegistry.openPackage('graphql-export') + await graphqlPkg.publish(graphqlPkg.packageId, { + version: GRAPHQL_SINGLE_VERSION, + files: [{ fileId: 'schema1.graphql' }], + }) + graphqlEditor = await Editor.openProject(graphqlPkg.packageId, graphqlPkg) + }) + + test('should export single graphql file with original extension', async () => { + const result = await graphqlEditor.run({ + version: GRAPHQL_SINGLE_VERSION, + buildType: BUILD_TYPE.EXPORT_VERSION, + format: FILE_FORMAT_GRAPHQL, + }) + expect(result.exportFileName).toEqual('graphql-export_single-graphql-version_schema1.graphql') + expect(result).toEqual(exportDocumentsMatcher([ + exportDocumentMatcher('schema1.graphql'), + ])) + }) +}) diff --git a/test/export-non-rest.test.ts b/test/export-non-rest.test.ts new file mode 100644 index 00000000..7e9da231 --- /dev/null +++ b/test/export-non-rest.test.ts @@ -0,0 +1,45 @@ +import { BUILD_TYPE, FILE_FORMAT_JSON, FILE_FORMAT_YAML } from '../src' +import { Editor, exportDocumentMatcher, exportDocumentsMatcher, LocalRegistry } from './helpers' + +describe('Export non-rest documents version test', () => { + const NON_REST_MARKDOWN_VERSION = 'single-markdown-version@1' + const NON_REST_PNG_VERSION = 'single-png-version@1' + let nonRestEditor: Editor + + beforeAll(async () => { + const pkg = LocalRegistry.openPackage('export') + await pkg.publish(pkg.packageId, { + version: NON_REST_MARKDOWN_VERSION, + files: [{ fileId: 'README.md' }], + }) + await pkg.publish(pkg.packageId, { + version: NON_REST_PNG_VERSION, + files: [{ fileId: 'Test.png' }], + }) + nonRestEditor = await Editor.openProject(pkg.packageId, pkg) + }) + + test('should export single markdown file with original extension', async () => { + const result = await nonRestEditor.run({ + version: NON_REST_MARKDOWN_VERSION, + buildType: BUILD_TYPE.EXPORT_VERSION, + format: FILE_FORMAT_JSON, + }) + expect(result.exportFileName).toEqual('export_single-markdown-version_README.md') + expect(result).toEqual(exportDocumentsMatcher([ + exportDocumentMatcher('README.md'), + ])) + }) + + test('should export single png file with original extension', async () => { + const result = await nonRestEditor.run({ + version: NON_REST_PNG_VERSION, + buildType: BUILD_TYPE.EXPORT_VERSION, + format: FILE_FORMAT_YAML, + }) + expect(result.exportFileName).toEqual('export_single-png-version_Test.png') + expect(result).toEqual(exportDocumentsMatcher([ + exportDocumentMatcher('Test.png'), + ])) + }) +}) diff --git a/test/export-shareability.test.ts b/test/export-shareability.test.ts new file mode 100644 index 00000000..e937d12c --- /dev/null +++ b/test/export-shareability.test.ts @@ -0,0 +1,102 @@ +import { + BUILD_TYPE, + FILE_FORMAT_HTML, + FILE_FORMAT_JSON, + FILE_FORMAT_YAML, + SHAREABILITY_STATUS_NON_SHAREABLE, + SHAREABILITY_STATUS_SHAREABLE, + SHAREABILITY_STATUS_UNKNOWN, + SHAREABILITY_STATUSES, +} from '../src' +import { Editor, LocalRegistry } from './helpers' + +describe('Export shareability filtering', () => { + let shareabilityPkg: LocalRegistry + let shareabilityEditor: Editor + const SHAREABILITY_PACKAGE_ID = 'shareability' + const SHAREABILITY_VERSION = 'shareability-version@1' + + beforeAll(async () => { + shareabilityPkg = LocalRegistry.openPackage(SHAREABILITY_PACKAGE_ID) + await shareabilityPkg.publish(shareabilityPkg.packageId, { version: SHAREABILITY_VERSION }) + + shareabilityPkg.setDocumentShareability('1', SHAREABILITY_STATUS_SHAREABLE) + shareabilityPkg.setDocumentShareability('2', SHAREABILITY_STATUS_NON_SHAREABLE) + shareabilityPkg.setDocumentShareability('3', SHAREABILITY_STATUS_UNKNOWN) + + shareabilityEditor = await Editor.openProject(shareabilityPkg.packageId, shareabilityPkg) + }) + + test('should export all documents when allowedShareabilityStatuses includes all statuses', async () => { + const result = await shareabilityEditor.run({ + packageId: shareabilityPkg.packageId, + buildType: BUILD_TYPE.EXPORT_VERSION, + version: SHAREABILITY_VERSION, + format: FILE_FORMAT_YAML, + allowedShareabilityStatuses: SHAREABILITY_STATUSES, + }) + const exportedFilenames = result.exportDocuments.map(d => d.filename) + expect(exportedFilenames).toContain('1.yaml') + expect(exportedFilenames).toContain('2.yaml') + expect(exportedFilenames).toContain('3.yaml') + }) + + test('should export all documents when allowedShareabilityStatuses is not provided', async () => { + const result = await shareabilityEditor.run({ + packageId: shareabilityPkg.packageId, + buildType: BUILD_TYPE.EXPORT_VERSION, + version: SHAREABILITY_VERSION, + format: FILE_FORMAT_YAML, + }) + const exportedFilenames = result.exportDocuments.map(d => d.filename) + expect(exportedFilenames).toContain('1.yaml') + expect(exportedFilenames).toContain('2.yaml') + expect(exportedFilenames).toContain('3.yaml') + }) + + const shareabilityOnlyCases = [ + { + label: 'non-shareable', + version: 'shareability-non-shareable@1', + statuses: [ + ['1', SHAREABILITY_STATUS_SHAREABLE], + ['2', SHAREABILITY_STATUS_NON_SHAREABLE], + ] as const, + }, + { + label: 'unknown', + version: 'shareability-unknown@1', + statuses: [ + ['1', SHAREABILITY_STATUS_SHAREABLE], + ['2', SHAREABILITY_STATUS_UNKNOWN], + ] as const, + }, + ] as const + + test.each(shareabilityOnlyCases)( + 'should export only shareable documents when allowedShareabilityStatuses is [shareable] and second doc is $label', + async ({ version, statuses }) => { + const pkg = LocalRegistry.openPackage(SHAREABILITY_PACKAGE_ID) + await pkg.publish(pkg.packageId, { version }) + statuses.forEach(([slug, status]) => pkg.setDocumentShareability(slug, status)) + + const caseEditor = await Editor.openProject(pkg.packageId, pkg) + + const exportFormats = [FILE_FORMAT_YAML, FILE_FORMAT_HTML, FILE_FORMAT_JSON] as const + for (const format of exportFormats) { + const result = await caseEditor.run({ + packageId: pkg.packageId, + buildType: BUILD_TYPE.EXPORT_VERSION, + version, + format, + allowedShareabilityStatuses: [SHAREABILITY_STATUS_SHAREABLE], + }) + + const exportedFilenames = result.exportDocuments.map(d => d.filename) + + expect(exportedFilenames).toContain(`1.${format}`) + expect(exportedFilenames).not.toContain(`2.${format}`) + } + }, + ) +}) diff --git a/test/export.test.ts b/test/export.test.ts index 0cc673b8..f7d33722 100644 --- a/test/export.test.ts +++ b/test/export.test.ts @@ -16,15 +16,11 @@ import { Editor, exportDocumentMatcher, exportDocumentsMatcher, loadFileAsString, LocalRegistry } from './helpers' import { - SHAREABILITY_STATUSES, BUILD_TYPE, ExportRestOperationsGroupBuildConfig, FILE_FORMAT_HTML, FILE_FORMAT_JSON, FILE_FORMAT_YAML, - SHAREABILITY_STATUS_SHAREABLE, - SHAREABILITY_STATUS_NON_SHAREABLE, - SHAREABILITY_STATUS_UNKNOWN, TRANSFORMATION_KIND_MERGED, TRANSFORMATION_KIND_REDUCED, } from '../src' @@ -401,90 +397,3 @@ describe('Export test', () => { expect(await result.exportDocuments[0].data.text()).toEqual(expectedResult) }) }) - -describe('Export shareability filtering', () => { - let shareabilityPkg: LocalRegistry - let shareabilityEditor: Editor - const SHAREABILITY_VERSION = 'shareability-version@1' - - beforeAll(async () => { - shareabilityPkg = LocalRegistry.openPackage('export') - await shareabilityPkg.publish(shareabilityPkg.packageId, { version: SHAREABILITY_VERSION }) - - shareabilityPkg.setDocumentShareability('1', SHAREABILITY_STATUS_SHAREABLE) - shareabilityPkg.setDocumentShareability('2', SHAREABILITY_STATUS_NON_SHAREABLE) - - shareabilityEditor = await Editor.openProject(shareabilityPkg.packageId, shareabilityPkg) - }) - - test('should export all documents when allowedShareabilityStatuses includes all statuses', async () => { - const result = await shareabilityEditor.run({ - packageId: shareabilityPkg.packageId, - buildType: BUILD_TYPE.EXPORT_VERSION, - version: SHAREABILITY_VERSION, - format: FILE_FORMAT_YAML, - allowedShareabilityStatuses: SHAREABILITY_STATUSES, - }) - const exportedFilenames = result.exportDocuments.map(d => d.filename) - expect(exportedFilenames).toContain('1.yaml') - expect(exportedFilenames).toContain('2.yaml') - }) - - test('should export all documents when allowedShareabilityStatuses is not provided', async () => { - const result = await shareabilityEditor.run({ - packageId: shareabilityPkg.packageId, - buildType: BUILD_TYPE.EXPORT_VERSION, - version: SHAREABILITY_VERSION, - format: FILE_FORMAT_YAML, - }) - const exportedFilenames = result.exportDocuments.map(d => d.filename) - expect(exportedFilenames).toContain('1.yaml') - expect(exportedFilenames).toContain('2.yaml') - }) - - const shareabilityOnlyCases = [ - { - label: 'non-shareable', - version: 'shareability-non-shareable@1', - statuses: [ - ['1', SHAREABILITY_STATUS_SHAREABLE], - ['2', SHAREABILITY_STATUS_NON_SHAREABLE], - ] as const, - }, - { - label: 'unknown', - version: 'shareability-unknown@1', - statuses: [ - ['1', SHAREABILITY_STATUS_SHAREABLE], - ['2', SHAREABILITY_STATUS_UNKNOWN], - ] as const, - }, - ] as const - - test.each(shareabilityOnlyCases)( - 'should export only shareable documents when allowedShareabilityStatuses is [shareable] and second doc is $label', - async ({ version, statuses }) => { - const pkg = LocalRegistry.openPackage('export') - await pkg.publish(pkg.packageId, { version }) - statuses.forEach(([slug, status]) => pkg.setDocumentShareability(slug, status)) - - const caseEditor = await Editor.openProject(pkg.packageId, pkg) - - const exportFormats = [FILE_FORMAT_YAML, FILE_FORMAT_HTML] as const - for (const format of exportFormats) { - const result = await caseEditor.run({ - packageId: pkg.packageId, - buildType: BUILD_TYPE.EXPORT_VERSION, - version, - format, - allowedShareabilityStatuses: [SHAREABILITY_STATUS_SHAREABLE], - }) - - const exportedFilenames = result.exportDocuments.map(d => d.filename) - - expect(exportedFilenames).toContain(`1.${format}`) - expect(exportedFilenames).not.toContain(`2.${format}`) - } - }, - ) -}) diff --git a/test/projects/asyncapi-export/config.json b/test/projects/asyncapi-export/config.json new file mode 100644 index 00000000..6ac23304 --- /dev/null +++ b/test/projects/asyncapi-export/config.json @@ -0,0 +1,20 @@ +{ + "packageId": "asyncapi-export", + "packageName": "asyncapi-export pkg", + "apiType": "asyncapi", + "version": "single-async-yaml-version@1", + "files": [ + { + "fileId": "valid-async-yaml.yaml", + "publish": true, + "labels": [], + "commitId": "asyncapi-export-1" + }, + { + "fileId": "valid-async-json.json", + "publish": true, + "labels": [], + "commitId": "asyncapi-export-2" + } + ] +} \ No newline at end of file diff --git a/test/projects/asyncapi-export/valid-async-json.json b/test/projects/asyncapi-export/valid-async-json.json new file mode 100644 index 00000000..5576c0b0 --- /dev/null +++ b/test/projects/asyncapi-export/valid-async-json.json @@ -0,0 +1,49 @@ +{ + "asyncapi": "3.0.0", + "info": { + "title": "Valid AsyncAPI JSON Document", + "version": "1.0.0", + "description": "A completely valid AsyncAPI document for testing" + }, + "channels": { + "userSignup": { + "address": "user/signup", + "messages": { + "userSignedUp": { + "$ref": "#/components/messages/UserSignedUp" + } + } + } + }, + "operations": { + "onUserSignup": { + "action": "receive", + "channel": { + "$ref": "#/channels/userSignup" + }, + "summary": "User signup event" + } + }, + "components": { + "messages": { + "UserSignedUp": { + "payload": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "required": [ + "userId", + "email" + ] + } + } + } + } +} \ No newline at end of file diff --git a/test/projects/asyncapi-export/valid-async-yaml.yaml b/test/projects/asyncapi-export/valid-async-yaml.yaml new file mode 100644 index 00000000..a03c9b2f --- /dev/null +++ b/test/projects/asyncapi-export/valid-async-yaml.yaml @@ -0,0 +1,30 @@ +asyncapi: 3.0.0 +info: + title: Valid AsyncAPI Document + version: 1.0.0 + description: A completely valid AsyncAPI document for testing +channels: + userSignup: + address: user/signup + messages: + userSignedUp: + $ref: "#/components/messages/UserSignedUp" +operations: + onUserSignup: + action: receive + channel: + $ref: "#/channels/userSignup" + summary: User signup event +components: + messages: + UserSignedUp: + payload: + type: object + properties: + userId: + type: string + description: User ID + email: + type: string + format: email + description: User email address diff --git a/test/projects/shareability/1.yaml b/test/projects/shareability/1.yaml new file mode 100644 index 00000000..c5adc214 --- /dev/null +++ b/test/projects/shareability/1.yaml @@ -0,0 +1,35 @@ +openapi: "3.0.0" +info: + title: test + version: 0.1.0 +paths: + /path1: + summary: path item summary + description: path item description + servers: + - url: https://example1.com + description: This is a server description in path item + parameters: + - $ref: "#/components/parameters/usedParameter1" + get: + summary: operation summary + description: operation description + servers: + - url: https://example2.com + description: This is a server description in operation + parameters: + - $ref: "#/components/parameters/usedParameter2" + responses: + "200": + description: response description +components: + parameters: + usedParameter1: + name: usedParameter1 + in: query + usedParameter2: + name: usedParameter2 + in: query + unusedParameter: + name: unusedParameter + in: query diff --git a/test/projects/shareability/2.yaml b/test/projects/shareability/2.yaml new file mode 100644 index 00000000..1ffdd5e7 --- /dev/null +++ b/test/projects/shareability/2.yaml @@ -0,0 +1,31 @@ +openapi: "3.0.0" +info: + title: test + version: 0.1.0 +paths: + /path2: + parameters: + - $ref: "#/components/parameters/usedParameter1" + post: + parameters: + - $ref: "#/components/parameters/usedParameter2" + responses: + "200": + description: response description + get: + parameters: + - $ref: "#/components/parameters/usedParameter1" + responses: + "200": + description: get response description +components: + parameters: + usedParameter1: + name: usedParameter1 + in: query + usedParameter2: + name: usedParameter2 + in: query + unusedParameter: + name: unusedParameter + in: query diff --git a/test/projects/shareability/3.yaml b/test/projects/shareability/3.yaml new file mode 100644 index 00000000..35e70256 --- /dev/null +++ b/test/projects/shareability/3.yaml @@ -0,0 +1,16 @@ +openapi: "3.0.0" +info: + title: test + version: 0.1.0 +paths: + /path3: + get: + summary: another document + responses: + "200": + description: response description +components: + parameters: + sampleParameter: + name: sampleParameter + in: query diff --git a/test/projects/shareability/config.json b/test/projects/shareability/config.json new file mode 100644 index 00000000..4c6546a3 --- /dev/null +++ b/test/projects/shareability/config.json @@ -0,0 +1,26 @@ +{ + "packageId": "shareability", + "packageName": "shareability pkg", + "apiType": "rest", + "version": "regular-version@1", + "files": [ + { + "fileId": "1.yaml", + "publish": true, + "labels": [], + "commitId": "6c778b1f44200bd19944a6a8eac10a4e5a21a1cd" + }, + { + "fileId": "2.yaml", + "publish": true, + "labels": [], + "commitId": "6c778b1f44200bd19944a6a8eac10a4e5a21a3cd" + }, + { + "fileId": "3.yaml", + "publish": true, + "labels": [], + "commitId": "6c778b1f44200bd19944a6a8eac10a4e5d21a3cd" + } + ] +} \ No newline at end of file From 7cde4e927d45d301022e909b7eb05059cab50ab0 Mon Sep 17 00:00:00 2001 From: Vladyslav Novikov Date: Tue, 31 Mar 2026 10:26:14 +0300 Subject: [PATCH 7/8] fix: linting errors --- test/projects/asyncapi-export/config.json | 2 +- test/projects/asyncapi-export/valid-async-json.json | 2 +- test/projects/shareability/config.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/projects/asyncapi-export/config.json b/test/projects/asyncapi-export/config.json index 6ac23304..71210a3f 100644 --- a/test/projects/asyncapi-export/config.json +++ b/test/projects/asyncapi-export/config.json @@ -17,4 +17,4 @@ "commitId": "asyncapi-export-2" } ] -} \ No newline at end of file +} diff --git a/test/projects/asyncapi-export/valid-async-json.json b/test/projects/asyncapi-export/valid-async-json.json index 5576c0b0..c3b267dc 100644 --- a/test/projects/asyncapi-export/valid-async-json.json +++ b/test/projects/asyncapi-export/valid-async-json.json @@ -46,4 +46,4 @@ } } } -} \ No newline at end of file +} diff --git a/test/projects/shareability/config.json b/test/projects/shareability/config.json index 4c6546a3..bf9870bc 100644 --- a/test/projects/shareability/config.json +++ b/test/projects/shareability/config.json @@ -23,4 +23,4 @@ "commitId": "6c778b1f44200bd19944a6a8eac10a4e5d21a3cd" } ] -} \ No newline at end of file +} From 1cb29276beb93635f8bd5dee8660cc72e4b0f9b6 Mon Sep 17 00:00:00 2001 From: Vladyslav Novikov Date: Tue, 31 Mar 2026 11:31:04 +0300 Subject: [PATCH 8/8] refactor: extract transformedDocuments duplication logic into a separate function --- src/strategies/export-version.strategy.ts | 75 +++++++++++++++-------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/src/strategies/export-version.strategy.ts b/src/strategies/export-version.strategy.ts index a9cd5931..b4e040d8 100644 --- a/src/strategies/export-version.strategy.ts +++ b/src/strategies/export-version.strategy.ts @@ -42,8 +42,6 @@ export class ExportVersionStrategy implements BuilderStrategy { const documentsToExport = filterDocumentsByShareabilityStatus(documents, config) - const isSingleNonRestDocument = documentsToExport.length === 1 && !isRestDocument(documentsToExport[0]) - switch (config.format) { case FILE_FORMAT_HTML: await exportToHTML(config, buildResult, contexts, documentsToExport) @@ -60,6 +58,7 @@ export class ExportVersionStrategy implements BuilderStrategy { return buildResult } + const isSingleNonRestDocument = documentsToExport.length === 1 && !isRestDocument(documentsToExport[0]) const singleExportDocument = buildResult.exportDocuments[0] const singleExportDocumentExtension = isSingleNonRestDocument ? getFileExtension(singleExportDocument.filename) @@ -81,24 +80,18 @@ async function exportToHTML( contexts: BuildTypeContexts, documentsToExport: ReadonlyArray, ): Promise { - const { - rawDocumentResolver, - templateResolver, - apiBuilders, - packageResolver, - } = contexts.builderContext(config) - const { packageId, version: versionWithRevision, format, allowedOasExtensions } = config + const { packageResolver, templateResolver } = contexts.builderContext(config) + const { packageId, version: versionWithRevision } = config const [version] = getSplittedVersionKey(versionWithRevision) const { name: packageName } = await packageResolver(packageId) const generatedHtmlExportDocuments: ExportDocument[] = [] const restDocuments = documentsToExport.filter(isRestDocument) const shouldAddIndexPage = restDocuments.length > 0 - const transformedDocuments = await Promise.all(documentsToExport.map(async document => { - const { createExportDocument } = apiBuilders.find(({ types }) => types.includes(document.type)) || unknownApiBuilder - const file = await rawDocumentResolver(versionWithRevision, packageId, document.slug) - return await createExportDocument?.(file.name, await file.text(), format, packageName, version, templateResolver, allowedOasExtensions, generatedHtmlExportDocuments, shouldAddIndexPage) ?? createUnknownExportDocument(file.name, file) - })) + const transformedDocuments = await transformDocuments(config, contexts, documentsToExport, version, packageName, { + generatedHtmlExportDocuments, + shouldAddIndexPage, + }) buildResult.exportDocuments.push(...transformedDocuments) @@ -117,25 +110,55 @@ async function defaultExport( contexts: BuildTypeContexts, documentsToExport: ReadonlyArray, ): Promise { - const { - rawDocumentResolver, - templateResolver, - packageResolver, - apiBuilders, - } = contexts.builderContext(config) - const { packageId, version: versionWithRevision, format, allowedOasExtensions } = config + const { packageResolver } = contexts.builderContext(config) + const { packageId, version: versionWithRevision } = config const [version] = getSplittedVersionKey(versionWithRevision) const { name: packageName } = await packageResolver(packageId) - const transformedDocuments = await Promise.all(documentsToExport.map(async document => { - const { createExportDocument } = apiBuilders.find(({ types }) => types.includes(document.type)) || unknownApiBuilder - const file = await rawDocumentResolver(versionWithRevision, packageId, document.slug) - return await createExportDocument?.(file.name, await file.text(), format, packageName, version, templateResolver, allowedOasExtensions) ?? createUnknownExportDocument(file.name, file) - })) + const transformedDocuments = await transformDocuments(config, contexts, documentsToExport, version, packageName) buildResult.exportDocuments.push(...transformedDocuments) } +type HtmlExportOptions = { + generatedHtmlExportDocuments: ExportDocument[] + shouldAddIndexPage: boolean +} + +async function transformDocuments( + config: ExportVersionBuildConfig, + contexts: BuildTypeContexts, + documentsToExport: ReadonlyArray, + version: string, + packageName: string, + htmlExportOptions?: HtmlExportOptions, +): Promise { + const { rawDocumentResolver, templateResolver, apiBuilders } = contexts.builderContext(config) + const { version: versionWithRevision, packageId, format, allowedOasExtensions } = config + + return await Promise.all( + documentsToExport.map(async document => { + const { createExportDocument } = apiBuilders.find(({ types }) => types.includes(document.type)) || unknownApiBuilder + const file = await rawDocumentResolver(versionWithRevision, packageId, document.slug) + + return ( + (await createExportDocument?.( + file.name, + await file.text(), + format, + packageName, + version, + templateResolver, + allowedOasExtensions, + htmlExportOptions?.generatedHtmlExportDocuments, + htmlExportOptions?.shouldAddIndexPage, + )) ?? + createUnknownExportDocument(file.name, file) + ) + }), + ) +} + function filterDocumentsByShareabilityStatus( documents: ReadonlyArray, config: ExportVersionBuildConfig,