diff --git a/packages/twenty-shared/scripts/generateBarrels.ts b/packages/twenty-shared/scripts/generateBarrels.ts index 187cddb42..585ba00a6 100644 --- a/packages/twenty-shared/scripts/generateBarrels.ts +++ b/packages/twenty-shared/scripts/generateBarrels.ts @@ -1,26 +1,13 @@ import prettier from '@prettier/sync'; import * as fs from 'fs'; +import glob from 'glob'; import path from 'path'; import { Options } from 'prettier'; import slash from 'slash'; +import ts from 'typescript'; // TODO prastoin refactor this file in several one into its dedicated package and make it a TypeScript CLI -const INCLUDED_EXTENSIONS = ['.ts', '.tsx']; -const EXCLUDED_EXTENSIONS = [ - '.test.ts', - '.test.tsx', - '.spec.ts', - '.spec.tsx', - '.stories.ts', - '.stories.tsx', -]; -const EXCLUDED_DIRECTORIES = [ - '__tests__', - '__mocks__', - '__stories__', - 'internal', -]; const INDEX_FILENAME = 'index'; const PACKAGE_JSON_FILENAME = 'package.json'; const NX_PROJECT_CONFIGURATION_FILENAME = 'project.json'; @@ -78,91 +65,77 @@ const getLastPathFolder = (path: string) => path.split('/').pop(); const getSubDirectoryPaths = (directoryPath: string): string[] => fs .readdirSync(directoryPath) - .filter((fileOrDirectoryName) => { - const isDirectory = fs - .statSync(path.join(directoryPath, fileOrDirectoryName)) - .isDirectory(); - if (!isDirectory) { - return false; - } - - const isExcludedDirectory = - EXCLUDED_DIRECTORIES.includes(fileOrDirectoryName); - return !isExcludedDirectory; - }) + .filter((fileOrDirectoryName) => + fs.statSync(path.join(directoryPath, fileOrDirectoryName)).isDirectory(), + ) .map((subDirectoryName) => path.join(directoryPath, subDirectoryName)); -const getDirectoryPathsRecursive = (directoryPath: string): string[] => [ - directoryPath, - ...getSubDirectoryPaths(directoryPath).flatMap(getDirectoryPathsRecursive), -]; +const partitionFileExportsByType = (declarations: DeclarationOccurence[]) => { + return declarations.reduce<{ + typeAndInterfaceDeclarations: DeclarationOccurence[]; + otherDeclarations: DeclarationOccurence[]; + }>( + (acc, { kind, name }) => { + if (kind === 'type' || kind === 'interface') { + return { + ...acc, + typeAndInterfaceDeclarations: [ + ...acc.typeAndInterfaceDeclarations, + { kind, name }, + ], + }; + } -const getFilesPaths = (directoryPath: string): string[] => - fs.readdirSync(directoryPath).filter((filePath) => { - const isFile = fs.statSync(path.join(directoryPath, filePath)).isFile(); - if (!isFile) { - return false; - } - - const isIndexFile = filePath.startsWith(INDEX_FILENAME); - if (isIndexFile) { - return false; - } - - const isWhiteListedExtension = INCLUDED_EXTENSIONS.some((extension) => - filePath.endsWith(extension), - ); - const isExcludedExtension = EXCLUDED_EXTENSIONS.every( - (excludedExtension) => !filePath.endsWith(excludedExtension), - ); - return isWhiteListedExtension && isExcludedExtension; - }); - -type ComputeExportLineForGivenFileArgs = { - filePath: string; - moduleDirectory: string; // Rename - directoryPath: string; // Rename -}; -const computeExportLineForGivenFile = ({ - filePath, - moduleDirectory, - directoryPath, -}: ComputeExportLineForGivenFileArgs) => { - const fileNameWithoutExtension = filePath.split('.').slice(0, -1).join('.'); - const pathToImport = slash( - path.relative( - moduleDirectory, - path.join(directoryPath, fileNameWithoutExtension), - ), + return { + ...acc, + otherDeclarations: [...acc.otherDeclarations, { kind, name }], + }; + }, + { + typeAndInterfaceDeclarations: [], + otherDeclarations: [], + }, ); - // TODO refactor should extract all exports atomically please refer to https://github.com/twentyhq/core-team-issues/issues/644 - return `export * from './${pathToImport}';`; }; -const generateModuleIndexFiles = (moduleDirectories: string[]) => { - return moduleDirectories.map((moduleDirectory) => { - const directoryPaths = getDirectoryPathsRecursive(moduleDirectory); - const content = directoryPaths - .flatMap((directoryPath) => { - const directFilesPaths = getFilesPaths(directoryPath); +const generateModuleIndexFiles = (exportByBarrel: ExportByBarrel[]) => { + return exportByBarrel.map( + ({ barrel: { moduleDirectory }, allFileExports }) => { + const content = allFileExports + .map(({ exports, file }) => { + const { otherDeclarations, typeAndInterfaceDeclarations } = + partitionFileExportsByType(exports); - return directFilesPaths.map((filePath) => - computeExportLineForGivenFile({ - directoryPath, - filePath, - moduleDirectory: moduleDirectory, - }), - ); - }) - .sort((a, b) => a.localeCompare(b)) // Could be removed as using prettier afterwards anw ? - .join('\n'); + const fileWithoutExtension = file.split('.').slice(0, -1).join('.'); + const pathToImport = slash( + path.relative(moduleDirectory, fileWithoutExtension), + ); + const mapDeclarationNameAndJoin = ( + declarations: DeclarationOccurence[], + ) => declarations.map(({ name }) => name).join(', '); - return { - content, - path: moduleDirectory, - filename: INDEX_FILENAME, - }; - }); + const typeExport = + typeAndInterfaceDeclarations.length > 0 + ? `export type { ${mapDeclarationNameAndJoin(typeAndInterfaceDeclarations)} } from "./${pathToImport}"` + : ''; + const othersExport = + otherDeclarations.length > 0 + ? `export { ${mapDeclarationNameAndJoin(otherDeclarations)} } from "./${pathToImport}"` + : ''; + + return [typeExport, othersExport] + .filter((el) => el !== '') + .join('\n'); + }) + .join('\n'); + + return { + content, + path: moduleDirectory, + filename: INDEX_FILENAME, + }; + }, + ); }; type JsonUpdate = Record; @@ -201,7 +174,7 @@ const updateNxProjectConfigurationBuildOutputs = (outputs: JsonUpdate) => { ...initialJsonFile.targets, build: { ...initialJsonFile.targets.build, - outputs, + outputs, }, }, }, @@ -236,17 +209,200 @@ const computeProjectNxBuildOutputsPath = (moduleDirectories: string[]) => { return ['{projectRoot}/dist', ...dynamicOutputsPath]; }; +const EXCLUDED_EXTENSIONS = [ + '**/*.test.ts', + '**/*.test.tsx', + '**/*.spec.ts', + '**/*.spec.tsx', + '**/*.stories.ts', + '**/*.stories.tsx', +]; +const EXCLUDED_DIRECTORIES = [ + '**/__tests__/**', + '**/__mocks__/**', + '**/__stories__/**', + '**/internal/**', +]; +function getTypeScriptFiles( + directoryPath: string, + includeIndex: boolean = false, +): string[] { + const pattern = path.join(directoryPath, '**/*.{ts,tsx}'); + const files = glob.sync(pattern, { + ignore: [...EXCLUDED_EXTENSIONS, ...EXCLUDED_DIRECTORIES], + }); + + return files.filter( + (file) => + !file.endsWith('.d.ts') && + (includeIndex ? true : !file.endsWith('index.ts')), + ); +} + +const getKind = ( + node: ts.VariableStatement, +): Extract => { + const isConst = (node.declarationList.flags & ts.NodeFlags.Const) !== 0; + if (isConst) { + return 'const'; + } + + const isLet = (node.declarationList.flags & ts.NodeFlags.Let) !== 0; + if (isLet) { + return 'let'; + } + + return 'var'; +}; + +function extractExportsFromSourceFile(sourceFile: ts.SourceFile) { + const exports: DeclarationOccurence[] = []; + + function visit(node: ts.Node) { + if (!ts.canHaveModifiers(node)) { + return ts.forEachChild(node, visit); + } + const modifiers = ts.getModifiers(node); + const isExport = modifiers?.some( + (mod) => mod.kind === ts.SyntaxKind.ExportKeyword, + ); + + if (!isExport) { + return ts.forEachChild(node, visit); + } + + switch (true) { + case ts.isTypeAliasDeclaration(node): + exports.push({ + kind: 'type', + name: node.name.text, + }); + break; + + case ts.isInterfaceDeclaration(node): + exports.push({ + kind: 'interface', + name: node.name.text, + }); + break; + + case ts.isEnumDeclaration(node): + exports.push({ + kind: 'enum', + name: node.name.text, + }); + break; + + case ts.isFunctionDeclaration(node) && node.name !== undefined: + exports.push({ + kind: 'function', + name: node.name.text, + }); + break; + + case ts.isVariableStatement(node): + node.declarationList.declarations.forEach((decl) => { + if (ts.isIdentifier(decl.name)) { + const kind = getKind(node); + exports.push({ + kind, + name: decl.name.text, + }); + } + }); + break; + + case ts.isClassDeclaration(node) && node.name !== undefined: + exports.push({ + kind: 'class', + name: node.name.text, + }); + break; + } + return ts.forEachChild(node, visit); + } + + visit(sourceFile); + return exports; +} + +type ExportKind = + | 'type' + | 'interface' + | 'enum' + | 'function' + | 'const' + | 'let' + | 'var' + | 'class'; +type DeclarationOccurence = { kind: ExportKind; name: string }; +type FileExports = Array<{ + file: string; + exports: DeclarationOccurence[]; +}>; + +function findAllExports(directoryPath: string): FileExports { + const results: FileExports = []; + + const files = getTypeScriptFiles(directoryPath); + + for (const file of files) { + const sourceFile = ts.createSourceFile( + file, + fs.readFileSync(file, 'utf8'), + ts.ScriptTarget.Latest, + true, + ); + + const exports = extractExportsFromSourceFile(sourceFile); + if (exports.length > 0) { + results.push({ + file, + exports, + }); + } + } + + return results; +} + +type ExportByBarrel = { + barrel: { + moduleName: string; + moduleDirectory: string; + }; + allFileExports: FileExports; +}; +const retrieveExportsByBarrel = (barrelDirectories: string[]) => { + return barrelDirectories.map((moduleDirectory) => { + const moduleExportsPerFile = findAllExports(moduleDirectory); + const moduleName = moduleDirectory.split('/').pop(); + if (!moduleName) { + throw new Error( + `Should never occur moduleName not found ${moduleDirectory}`, + ); + } + + return { + barrel: { + moduleName, + moduleDirectory, + }, + allFileExports: moduleExportsPerFile, + }; + }); +}; + const main = () => { const moduleDirectories = getSubDirectoryPaths(SRC_PATH); - const moduleIndexFiles = generateModuleIndexFiles(moduleDirectories); + const exportsByBarrel = retrieveExportsByBarrel(moduleDirectories); + const moduleIndexFiles = generateModuleIndexFiles(exportsByBarrel); const packageJsonPreconstructConfigAndFiles = computePackageJsonFilesAndPreconstructConfig(moduleDirectories); const nxBuildOutputsPath = computeProjectNxBuildOutputsPath(moduleDirectories); - updateNxProjectConfigurationBuildOutputs( - nxBuildOutputsPath - ); + updateNxProjectConfigurationBuildOutputs(nxBuildOutputsPath); writeInPackageJson(packageJsonPreconstructConfigAndFiles); moduleIndexFiles.forEach(createTypeScriptFile); }; diff --git a/packages/twenty-shared/src/constants/index.ts b/packages/twenty-shared/src/constants/index.ts index f6a7b3cde..50a06be40 100644 --- a/packages/twenty-shared/src/constants/index.ts +++ b/packages/twenty-shared/src/constants/index.ts @@ -7,8 +7,8 @@ * |___/ */ -export * from './FieldForTotalCountAggregateOperation'; -export * from './PermissionsOnAllObjectRecords'; -export * from './StandardObjectRecordsUnderObjectRecordsPermissions'; -export * from './TwentyCompaniesBaseUrl'; -export * from './TwentyIconsBaseUrl'; +export { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from './FieldForTotalCountAggregateOperation'; +export { PermissionsOnAllObjectRecords } from './PermissionsOnAllObjectRecords'; +export { STANDARD_OBJECT_RECORDS_UNDER_OBJECT_RECORDS_PERMISSIONS } from './StandardObjectRecordsUnderObjectRecordsPermissions'; +export { TWENTY_COMPANIES_BASE_URL } from './TwentyCompaniesBaseUrl'; +export { TWENTY_ICONS_BASE_URL } from './TwentyIconsBaseUrl'; diff --git a/packages/twenty-shared/src/testing/index.ts b/packages/twenty-shared/src/testing/index.ts index 720f4106b..053ac0f70 100644 --- a/packages/twenty-shared/src/testing/index.ts +++ b/packages/twenty-shared/src/testing/index.ts @@ -7,4 +7,4 @@ * |___/ */ -export * from './types/EachTestingContext.type'; +export type { EachTestingContext } from './types/EachTestingContext.type'; diff --git a/packages/twenty-shared/src/translations/index.ts b/packages/twenty-shared/src/translations/index.ts index e03c03686..5de276811 100644 --- a/packages/twenty-shared/src/translations/index.ts +++ b/packages/twenty-shared/src/translations/index.ts @@ -7,5 +7,5 @@ * |___/ */ -export * from './constants/AppLocales'; -export * from './constants/SourceLocale'; +export { APP_LOCALES } from './constants/AppLocales'; +export { SOURCE_LOCALE } from './constants/SourceLocale'; diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts index 8309bf772..5cd74c8a9 100644 --- a/packages/twenty-shared/src/types/index.ts +++ b/packages/twenty-shared/src/types/index.ts @@ -7,6 +7,6 @@ * |___/ */ -export * from './ConnectedAccountProvider'; -export * from './FieldMetadataType'; -export * from './IsExactly'; +export { ConnectedAccountProvider } from './ConnectedAccountProvider'; +export { FieldMetadataType } from './FieldMetadataType'; +export type { IsExactly } from './IsExactly'; diff --git a/packages/twenty-shared/src/utils/index.ts b/packages/twenty-shared/src/utils/index.ts index dbd31d980..a162430d5 100644 --- a/packages/twenty-shared/src/utils/index.ts +++ b/packages/twenty-shared/src/utils/index.ts @@ -7,16 +7,19 @@ * |___/ */ -export * from './assertUnreachable'; -export * from './fieldMetadata/isFieldMetadataDateKind'; -export * from './image/getImageAbsoluteURI'; -export * from './image/getLogoUrlFromDomainName'; -export * from './strings/capitalize'; -export * from './url/absoluteUrlSchema'; -export * from './url/getAbsoluteUrlOrThrow'; -export * from './url/getUrlHostnameOrThrow'; -export * from './url/isValidHostname'; -export * from './url/isValidUrl'; -export * from './validation/isDefined'; -export * from './validation/isValidLocale'; -export * from './validation/isValidUuid'; +export { assertUnreachable } from './assertUnreachable'; +export { isFieldMetadataDateKind } from './fieldMetadata/isFieldMetadataDateKind'; +export { getImageAbsoluteURI } from './image/getImageAbsoluteURI'; +export { + sanitizeURL, + getLogoUrlFromDomainName, +} from './image/getLogoUrlFromDomainName'; +export { capitalize } from './strings/capitalize'; +export { absoluteUrlSchema } from './url/absoluteUrlSchema'; +export { getAbsoluteUrlOrThrow } from './url/getAbsoluteUrlOrThrow'; +export { getUrlHostnameOrThrow } from './url/getUrlHostnameOrThrow'; +export { isValidHostname } from './url/isValidHostname'; +export { isValidUrl } from './url/isValidUrl'; +export { isDefined } from './validation/isDefined'; +export { isValidLocale } from './validation/isValidLocale'; +export { isValidUuid } from './validation/isValidUuid'; diff --git a/packages/twenty-shared/src/workspace/index.ts b/packages/twenty-shared/src/workspace/index.ts index 09abc32e7..cc47128cd 100644 --- a/packages/twenty-shared/src/workspace/index.ts +++ b/packages/twenty-shared/src/workspace/index.ts @@ -7,5 +7,5 @@ * |___/ */ -export * from './types/WorkspaceActivationStatus'; -export * from './utils/isWorkspaceActiveOrSuspended'; +export { WorkspaceActivationStatus } from './types/WorkspaceActivationStatus'; +export { isWorkspaceActiveOrSuspended } from './utils/isWorkspaceActiveOrSuspended';