diff --git a/.changeset/silent-tools-beg.md b/.changeset/silent-tools-beg.md new file mode 100644 index 00000000..6bfb1e9d --- /dev/null +++ b/.changeset/silent-tools-beg.md @@ -0,0 +1,6 @@ +--- +'@sap-ai-sdk/gen-ai-hub': minor +'@sap-ai-sdk/core': minor +--- + +[New Functionality] Ensure that all external APIs are fully exported and any non-exported APIs are marked with `@internal`. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db982454..5212cbda 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,6 +45,8 @@ jobs: # - run: pnpm build # might be needed in the future - run: pnpm lint - run: pnpm compile:cjs + - run: pnpm check:public-api + name: Check public api dependabot: runs-on: ubuntu-latest diff --git a/package.json b/package.json index 749f7900..38a53e8b 100644 --- a/package.json +++ b/package.json @@ -23,24 +23,29 @@ "core": "pnpm -F=@sap-ai-sdk/core", "e2e-tests": "pnpm -F=@sap-ai-sdk/e2e-tests", "type-tests": "pnpm -F=@sap-ai-sdk/type-tests", - "smoke-tests": "pnpm -F=@sap-ai-sdk/smoke-tests" + "smoke-tests": "pnpm -F=@sap-ai-sdk/smoke-tests", + "check:public-api": "pnpm -r check:public-api" }, "devDependencies": { "@changesets/cli": "^2.27.7", "@jest/globals": "^29.5.12", "@sap-cloud-sdk/eslint-config": "^3.20.0", "@sap-cloud-sdk/connectivity": "^3.20.0", + "@sap-cloud-sdk/generator-common": "^3.20.0", "@sap-cloud-sdk/http-client": "^3.20.0", + "@sap-cloud-sdk/util": "^3.20.0", "@sap-ai-sdk/ai-core": "workspace:^", "@sap-ai-sdk/core": "workspace:^", "@sap-ai-sdk/gen-ai-hub": "workspace:^", "@types/jest": "^29.5.12", - "@jest/globals": "^29.5.12", "@types/jsonwebtoken": "^9.0.2", + "@types/mock-fs": "^4.13.4", "@types/node": "^20.16.3", "eslint": "^9.9.1", + "glob": "^10.4.3", "jest": "^30.0.0-alpha.6", "jsonwebtoken": "^9.0.2", + "mock-fs": "^4.13.4", "nock": "^13.5.5", "prettier": "^3.3.3", "ts-jest": "^29.2.5", diff --git a/packages/core/package.json b/packages/core/package.json index 65e4bb98..cac90f35 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -23,7 +23,8 @@ "compile:cjs": "tsc -p tsconfig.cjs.json", "test": "NODE_OPTIONS=--experimental-vm-modules jest", "lint": "eslint \"**/*.ts\" && prettier . --config ../../.prettierrc --ignore-path ../../.prettierignore -c", - "lint:fix": "eslint \"**/*.ts\" --fix && prettier . --config ../../.prettierrc --ignore-path ../../.prettierignore -w --log-level error" + "lint:fix": "eslint \"**/*.ts\" --fix && prettier . --config ../../.prettierrc --ignore-path ../../.prettierignore -w --log-level error", + "check:public-api": "node --loader ts-node/esm ../../scripts/check-public-api-cli.ts" }, "dependencies": { "@sap-cloud-sdk/http-client": "^3.20.0", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7435275f..3cc6a523 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,9 +1,9 @@ -export { - executeRequest, +export type { BaseLlmParametersWithDeploymentId, BaseLlmParameters, CustomRequestConfig, EndpointOptions } from './http-client.js'; +export { executeRequest } from './http-client.js'; export { getAiCoreDestination } from './context.js'; export { OpenApiRequestBuilder } from './openapi-request-builder.js'; diff --git a/packages/gen-ai-hub/package.json b/packages/gen-ai-hub/package.json index bdec8c0f..bc68c30c 100644 --- a/packages/gen-ai-hub/package.json +++ b/packages/gen-ai-hub/package.json @@ -27,7 +27,8 @@ "lint": "eslint \"**/*.ts\" && prettier . --config ../../.prettierrc --ignore-path ../../.prettierignore -c", "lint:fix": "eslint \"**/*.ts\" --fix && prettier . --config ../../.prettierrc --ignore-path ../../.prettierignore -w --log-level error", "generate": "pnpm generate:orchestration", - "generate:orchestration": "openapi-generator --generateESM --clearOutputDir -i ./src/orchestration/spec/api.yaml -o ./src/orchestration/client && pnpm lint:fix" + "generate:orchestration": "openapi-generator --generateESM --clearOutputDir -i ./src/orchestration/spec/api.yaml -o ./src/orchestration/client && pnpm lint:fix", + "check:public-api": "node --loader ts-node/esm ../../scripts/check-public-api-cli.ts" }, "dependencies": { "@sap-ai-sdk/core": "workspace:^", diff --git a/packages/gen-ai-hub/src/client/openai/index.ts b/packages/gen-ai-hub/src/client/openai/index.ts new file mode 100644 index 00000000..b660b2bc --- /dev/null +++ b/packages/gen-ai-hub/src/client/openai/index.ts @@ -0,0 +1,2 @@ +export * from './openai-types.js'; +export * from './openai-client.js'; diff --git a/packages/gen-ai-hub/src/index.ts b/packages/gen-ai-hub/src/index.ts index d9ecfe89..7a4cfd92 100644 --- a/packages/gen-ai-hub/src/index.ts +++ b/packages/gen-ai-hub/src/index.ts @@ -1,10 +1,76 @@ -export * from './client/index.js'; +export type { + OpenAiChatModel, + OpenAiEmbeddingModel, + OpenAiChatMessage, + OpenAiChatSystemMessage, + OpenAiChatUserMessage, + OpenAiChatAssistantMessage, + OpenAiChatToolMessage, + OpenAiChatFunctionMessage, + OpenAiChatCompletionFunction, + OpenAiChatCompletionTool, + OpenAiChatFunctionCall, + OpenAiChatToolCall, + OpenAiCompletionParameters, + OpenAiChatCompletionParameters, + OpenAiEmbeddingParameters, + OpenAiCompletionOutput, + OpenAiChatCompletionOutput, + OpenAiPromptFilterResult, + OpenAiContentFilterResultsBase, + OpenAiContentFilterPromptResults, + OpenAiContentFilterResultBase, + OpenAiContentFilterDetectedResult, + OpenAiContentFilterSeverityResult, + OpenAiEmbeddingOutput +} from './client/index.js'; +export { OpenAiClient } from './client/index.js'; + +export type { + ModelDeployment, + DeploymentIdConfiguration, + FoundationModel, + ModelConfiguration +} from './utils/index.js'; export { - OrchestrationClient, + isDeploymentIdConfiguration, + getDeploymentId, + resolveDeployment +} from './utils/index.js'; + +export type { OrchestrationCompletionParameters, CompletionPostResponse, - azureContentFilter, PromptConfig, LlmConfig, - ChatMessages + ChatMessages, + TokenUsage, + TemplatingModuleConfig, + OrchestrationConfig, + ModuleResults, + ModuleConfigs, + MaskingModuleConfig, + MaskingProviderConfig, + LLMModuleResult, + LLMModuleConfig, + LLMChoice, + GroundingModuleConfig, + GroundingFilter, + GenericModuleResult, + FilteringModuleConfig, + FilteringConfig, + FilterConfig, + ErrorResponse, + DPIEntities, + DPIEntityConfig, + DPIConfig, + CompletionPostRequest, + ChatMessage, + AzureThreshold, + AzureContentSafety, + AzureContentSafetyFilterConfig +} from './orchestration/index.js'; +export { + OrchestrationClient, + azureContentFilter } from './orchestration/index.js'; diff --git a/packages/gen-ai-hub/src/internal.ts b/packages/gen-ai-hub/src/internal.ts index c60344f8..b85ccaa0 100644 --- a/packages/gen-ai-hub/src/internal.ts +++ b/packages/gen-ai-hub/src/internal.ts @@ -1,2 +1,3 @@ export * from './client/index.js'; export * from './orchestration/index.js'; +export * from './utils/index.js'; diff --git a/packages/gen-ai-hub/src/utils/index.ts b/packages/gen-ai-hub/src/utils/index.ts new file mode 100644 index 00000000..250e710c --- /dev/null +++ b/packages/gen-ai-hub/src/utils/index.ts @@ -0,0 +1 @@ +export * from './deployment-resolver.js'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b87e549..bb93bcfe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,27 +31,42 @@ importers: '@sap-cloud-sdk/eslint-config': specifier: ^3.20.0 version: 3.20.0(@types/eslint@8.56.10)(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(typescript@5.5.4))(@typescript-eslint/parser@7.18.0(eslint@9.9.1)(typescript@5.5.4))(eslint@9.9.1)(prettier@3.3.3)(typescript@5.5.4) + '@sap-cloud-sdk/generator-common': + specifier: ^3.20.0 + version: 3.20.0 '@sap-cloud-sdk/http-client': specifier: ^3.20.0 version: 3.20.0 + '@sap-cloud-sdk/util': + specifier: ^3.20.0 + version: 3.20.0 '@types/jest': specifier: ^29.5.12 version: 29.5.12 '@types/jsonwebtoken': specifier: ^9.0.2 version: 9.0.6 + '@types/mock-fs': + specifier: ^4.13.4 + version: 4.13.4 '@types/node': specifier: ^20.16.3 version: 20.16.3 eslint: specifier: ^9.9.1 version: 9.9.1 + glob: + specifier: ^10.4.3 + version: 10.4.5 jest: specifier: ^30.0.0-alpha.6 version: 30.0.0-alpha.6(@types/node@20.16.3)(ts-node@10.9.2(@types/node@20.16.3)(typescript@5.5.4)) jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 + mock-fs: + specifier: ^4.13.4 + version: 4.14.0 nock: specifier: ^13.5.5 version: 13.5.5 @@ -820,6 +835,9 @@ packages: '@types/minimist@1.2.5': resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + '@types/mock-fs@4.13.4': + resolution: {integrity: sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -2499,6 +2517,9 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mock-fs@4.14.0: + resolution: {integrity: sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -4412,7 +4433,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 20.16.2 + '@types/node': 20.16.3 '@types/eslint@7.29.0': dependencies: @@ -4474,6 +4495,10 @@ snapshots: '@types/minimist@1.2.5': {} + '@types/mock-fs@4.13.4': + dependencies: + '@types/node': 20.16.3 + '@types/node@12.20.55': {} '@types/node@20.16.2': @@ -4495,7 +4520,7 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.16.2 + '@types/node': 20.16.3 '@types/serve-static@1.15.7': dependencies: @@ -6517,6 +6542,8 @@ snapshots: minipass@7.1.2: {} + mock-fs@4.14.0: {} + mri@1.2.0: {} ms@2.0.0: {} diff --git a/scripts/check-public-api-cli.ts b/scripts/check-public-api-cli.ts new file mode 100644 index 00000000..6942c166 --- /dev/null +++ b/scripts/check-public-api-cli.ts @@ -0,0 +1,9 @@ +import { createLogger } from '@sap-cloud-sdk/util'; +import { checkApiOfPackage } from './check-public-api.js'; + +const logger = createLogger('check-public-api'); + +checkApiOfPackage(process.cwd()).catch(err => { + logger.error(err); + process.exit(1); +}); \ No newline at end of file diff --git a/scripts/check-public-api.ts b/scripts/check-public-api.ts new file mode 100644 index 00000000..4717f583 --- /dev/null +++ b/scripts/check-public-api.ts @@ -0,0 +1,359 @@ +/* eslint-disable jsdoc/require-jsdoc */ + +import { join, resolve, parse, basename, dirname } from 'path'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { promises, existsSync } from 'fs'; +import { glob } from 'glob'; +import { createLogger, flatten, unixEOL } from '@sap-cloud-sdk/util'; +import mock from 'mock-fs'; +import { CompilerOptions } from 'typescript'; +/* eslint-disable-next-line no-restricted-imports */ +import { + readCompilerOptions, + readIncludeExcludeWithDefaults, + transpileDirectory, + defaultPrettierConfig +} from '@sap-cloud-sdk/generator-common/internal.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const { readFile, lstat, readdir } = promises; + +const logger = createLogger('check-public-api'); + +const pathToTsConfigRoot = join(__dirname, '../tsconfig.json'); +const pathRootNodeModules = resolve(__dirname, '../node_modules'); +export const regexExportedIndex = /\{([\w,]+)\}from'\./g; +export const regexExportedInternal = /\.\/([\w-]+)/g; + +function mockFileSystem(pathToPackage: string) { + const { pathToSource, pathToTsConfig, pathToNodeModules } = + paths(pathToPackage); + mock({ + [pathToTsConfig]: mock.load(pathToTsConfig), + [pathToSource]: mock.load(pathToSource), + [pathRootNodeModules]: mock.load(pathRootNodeModules), + [pathToNodeModules]: mock.load(pathToNodeModules), + [pathToTsConfigRoot]: mock.load(pathToTsConfigRoot) + }); +} + +function paths(pathToPackage: string): { + pathToSource: string; + pathToTsConfig: string; + pathToNodeModules: string; + pathCompiled: string; +} { + return { + pathToSource: join(pathToPackage, 'src'), + pathToTsConfig: join(pathToPackage, 'tsconfig.json'), + pathToNodeModules: join(pathToPackage, 'node_modules'), + pathCompiled: 'dist', + }; +} + +/** + * Read the compiler options from the root and cwd tsconfig.json. + * @param pathToPackage - Path to the package under investigation. + * @returns The compiler options. + */ +async function getCompilerOptions( + pathToPackage: string +): Promise { + const { pathToSource, pathToTsConfig, pathCompiled } = paths(pathToPackage); + const compilerOptions = await readCompilerOptions(pathToTsConfig); + const compilerOptionsRoot = await readCompilerOptions(pathToTsConfigRoot); + return { + ...compilerOptionsRoot, + ...compilerOptions, + stripInternal: true, + rootDir: pathToSource, + outDir: pathCompiled + }; +} + +/** + * For a detailed explanation what is happening here have a look at `0007-public-api-check.md` in the implementation documentation. + * Here the two sets: exports from index and exports from .d.ts are compared and logs are created. + * @param allExportedIndex - Names of the object imported by the index.ts. + * @param allExportedTypes - Exported object by the .d.ts files. + * @param verbose - Do a lot of detailed output on the packages. + * @returns True if the two sets export the same objects. + */ +function compareApisAndLog( + allExportedIndex: string[], + allExportedTypes: ExportedObject[], + verbose: boolean +): boolean { + let setsAreEqual = true; + + allExportedTypes.forEach(exportedType => { + if ( + !allExportedIndex.find(nameInIndex => exportedType.name === nameInIndex) + ) { + logger.error( + `The ${exportedType.type} "${exportedType.name}" in file: ${exportedType.path} is neither listed in the index.ts nor marked as internal.` + ); + setsAreEqual = false; + } + }); + + allExportedIndex.forEach(nameInIndex => { + if ( + !allExportedTypes.find(exportedType => exportedType.name === nameInIndex) + ) { + logger.error( + `The object "${nameInIndex}" is exported from the index.ts but marked as @internal.` + ); + setsAreEqual = false; + } + }); + logger.info(`We have found ${allExportedIndex.length} exports.`); + + if (verbose) { + logger.info(`Public api: + ${allExportedIndex.sort().join(`,${unixEOL}`)}`); + } + return setsAreEqual; +} + +/** + * Executes the public API check for a given package. + * @param pathToPackage - Path to the package. + */ +export async function checkApiOfPackage(pathToPackage: string): Promise { + try { + logger.info(`Check package: ${pathToPackage}`); + const { pathToSource, pathCompiled, pathToTsConfig } = paths(pathToPackage); + mockFileSystem(pathToPackage); + const opts = await getCompilerOptions(pathToPackage); + const includeExclude = await readIncludeExcludeWithDefaults(pathToTsConfig); + await transpileDirectory(pathToSource, { + compilerOptions: opts, + // We have things in our sources like `#!/usr/bin/env node` in CLI `.js` files which is not working with parser of prettier. + createFileOptions: { + overwrite: true, + prettierOptions: defaultPrettierConfig, + usePrettier: false + } + }, { exclude: includeExclude?.exclude!, include: ['**/*.ts'] }); + await checkBarrelRecursive(pathToSource); + + const indexFilePath = join(pathToSource, 'index.ts'); + checkIndexFileExists(indexFilePath); + + const allExportedTypes = await parseTypeDefinitionFiles(pathCompiled); + const allExportedIndex = await parseIndexFile(indexFilePath); + + const setsAreEqual = compareApisAndLog( + allExportedIndex, + allExportedTypes, + true + ); + mock.restore(); + if (!setsAreEqual) { + process.exit(1); + } + logger.info( + `The index.ts of package ${pathToPackage} is in sync with the type annotations.` + ); + } finally { + mock.restore(); + } +} + +export function checkIndexFileExists(indexFilePath: string): void { + if (!existsSync(indexFilePath)) { + throw new Error('No index.ts file found in root.'); + } +} + +/** + * Get the paths of all `.d.ts` files. + * @param cwd - Directory which is scanned for type definitions. + * @returns Paths to the `.d.ts` files excluding `index.d.ts` files. + */ +export async function typeDescriptorPaths(cwd: string): Promise { + const files = await glob('**/*.d.ts', { cwd }); + return files + .filter(file => !file.endsWith('index.d.ts')) + .map(file => join(cwd, file)); +} + +export interface ExportedObject { + name: string; + type: string; + path: string; +} + +/** + * Execute the parseTypeDefinitionFile for all files in the cwd. + * @param pathCompiled - Path to the compiled sources containing the .d.ts files. + * @returns Information on the exported objects. + */ +export async function parseTypeDefinitionFiles( + pathCompiled: string +): Promise { + const typeDefinitionPaths = await typeDescriptorPaths(pathCompiled); + const result = await Promise.all( + typeDefinitionPaths.map(async pathTypeDefinition => { + const fileContent = await readFile(pathTypeDefinition, 'utf8'); + const types = parseTypeDefinitionFile(fileContent); + return types.map(type => ({ path: pathTypeDefinition, ...type })); + }) + ); + return flatten(result); +} + +/** + * For a detailed explanation what is happening here have a look at `0007-public-api-check.md` in the implementation documentation. + * Parses a `d.ts` file for the exported objects in it. + * @param fileContent - Content of the .d.ts file to be processes. + * @returns List of exported object. + */ +export function parseTypeDefinitionFile( + fileContent: string +): Omit[] { + const normalized = fileContent.replace(/\n+/g, ''); + return [ + 'function', + 'const', + 'enum', + 'class', + 'abstract class', + 'type', + 'interface' + ].reduce[]>((allObjects, objectType) => { + const regex = + objectType === 'interface' + ? new RegExp(`export ${objectType} (\\w+)`, 'g') + : new RegExp(`export (?:declare )?${objectType} (\\w+)`, 'g'); + const exported = captureGroupsFromGlobalRegex(regex, normalized).map( + element => ({ name: element, type: objectType }) + ); + return [...allObjects, ...exported]; + }, []); +} + +/** + * Parse a barrel file for the exported objects. + * It selects all string in \{\} e.g. export \{a,b,c\} from './xyz' will result in [a,b,c]. + * @param fileContent - Content of the index file to be parsed. + * @param regex - Regular expression used for matching exports. + * @returns List of objects exported by the given index file. + */ +export function parseBarrelFile(fileContent: string, regex: RegExp): string[] { + const normalized = fileContent.replace(/\s+/g, ''); + const groups = captureGroupsFromGlobalRegex(regex, normalized); + + return flatten(groups.map(group => group.split(','))); +} + +function checkInternalReExports(fileContent: string, filePath: string): void { + const internalReExports = parseBarrelFile( + fileContent, + /\{([\w,]+)\}from'.*\/internal'/g + ); + if (internalReExports.length) { + throw new Error( + `Re-exporting internal modules is not allowed. ${internalReExports + .map(reExport => `'${reExport}'`) + .join(', ')} exported in '${filePath}'.` + ); + } +} + +export async function parseIndexFile(filePath: string): Promise { + const cwd = dirname(filePath); + const fileContent = await readFile(filePath, 'utf-8'); + checkInternalReExports(fileContent, filePath); + const localExports = parseBarrelFile(fileContent, regexExportedIndex); + const starFiles = captureGroupsFromGlobalRegex( + /export \* from '([\w\/.-]+)'/g, + fileContent + ); + const starFileExports = await Promise.all( + starFiles.map(async relativeFilePath => { + const fullPath = resolve(cwd, relativeFilePath); + const tsPath = fullPath.replace(/\.js$/, '.ts'); + return parseIndexFile(tsPath); + }) + ); + return [...localExports, ...starFileExports.flat()]; +} + +function captureGroupsFromGlobalRegex(regex: RegExp, str: string): string[] { + const groups = Array.from(str.matchAll(regex)); + return groups.map(group => group[1]); +} + +export async function checkBarrelRecursive(cwd: string): Promise { + (await readdir(cwd, { withFileTypes: true })) + .filter(dirent => dirent.isDirectory()) + .forEach(async subDir => { + if (['__snapshot__', 'spec'].includes(subDir.name)) { + await checkBarrelRecursive(join(cwd, subDir.name)); + } + }); + await exportAllInBarrel( + cwd, + parse(cwd).name === 'src' ? 'internal.ts' : 'index.ts' + ); +} + +export async function exportAllInBarrel( + cwd: string, + barrelFileName: string +): Promise { + const barrelFilePath = join(cwd, barrelFileName); + if (existsSync(barrelFilePath) && (await lstat(barrelFilePath)).isFile()) { + const dirContents = ( + await glob('*', { + ignore: [ + '**/*.test.ts', + '__snapshots__', + 'spec', + 'internal.ts', + 'index.ts', + 'cli.ts', + '**/*.md' + ], + cwd + }) + ).map(name => basename(name, '.ts')); + const exportedFiles = parseBarrelFile( + await readFile(barrelFilePath, 'utf8'), + regexExportedInternal + ); + if (compareBarrels(dirContents, exportedFiles, barrelFilePath)) { + throw Error(`'${barrelFileName}' is not in sync.`); + } + } else { + throw Error(`No '${barrelFileName}' file found in '${cwd}'.`); + } +} + +function compareBarrels( + dirContents: string[], + exportedFiles: string[], + barrelFilePath: string +) { + const missingBarrelExports = dirContents.filter( + x => !exportedFiles.includes(x) + ); + missingBarrelExports.forEach(tsFiles => + logger.error(`'${tsFiles}' is not exported in '${barrelFilePath}'.`) + ); + + const extraBarrelExports = exportedFiles.filter( + x => !dirContents.includes(x) + ); + extraBarrelExports.forEach(exports => + logger.error( + `'${exports}' is exported from the '${barrelFilePath}' but does not exist in this directory.` + ) + ); + + return missingBarrelExports.length || extraBarrelExports.length; +} diff --git a/tsconfig.json b/tsconfig.json index d5dd6e16..f5507897 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,109 +1,13 @@ { "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "Node16" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, - "declarationMap": true /* Create sourcemaps for d.ts files. */, - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - "sourceMap": true /* Create source map files for emitted JavaScript files. */, - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./dist" /* Specify an output folder for all emitted files. */, - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - - /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "target": "ES2022", + "module": "Node16", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true } }