# Introduction In this PR we've migrated `twenty-shared` from a `vite` app [libary-mode](https://vite.dev/guide/build#library-mode) to a [preconstruct](https://preconstruct.tools/) "atomic" application ( in the future would like to introduce preconstruct to handle of all our atomic dependencies such as `twenty-emails` `twenty-ui` etc it will be integrated at the monorepo's root directly, would be to invasive in the first, starting incremental via `twenty-shared`) For more information regarding the motivations please refer to nor: - https://github.com/twentyhq/core-team-issues/issues/587 - https://github.com/twentyhq/core-team-issues/issues/281#issuecomment-2630949682 close https://github.com/twentyhq/core-team-issues/issues/589 close https://github.com/twentyhq/core-team-issues/issues/590 ## How to test In order to ease the review this PR will ship all the codegen at the very end, the actual meaning full diff is `+2,411 −114` In order to migrate existing dependent packages to `twenty-shared` multi barrel new arch you need to run in local: ```sh yarn tsx packages/twenty-shared/scripts/migrateFromSingleToMultiBarrelImport.ts && \ npx nx run-many -t lint --fix -p twenty-front twenty-ui twenty-server twenty-emails twenty-shared twenty-zapier ``` Note that `migrateFromSingleToMultiBarrelImport` is idempotent, it's atm included in the PR but should not be merged. ( such as codegen will be added before merging this script will be removed ) ## Misc - related opened issue preconstruct https://github.com/preconstruct/preconstruct/issues/617 ## Closed related PR - https://github.com/twentyhq/twenty/pull/11028 - https://github.com/twentyhq/twenty/pull/10993 - https://github.com/twentyhq/twenty/pull/10960 ## Upcoming enhancement: ( in others dedicated PRs ) - 1/ refactor generate barrel to export atomic module instead of `*` - 2/ generate barrel own package with several files and tests - 3/ Migration twenty-ui the same way - 4/ Use `preconstruct` at monorepo global level ## Conclusion As always any suggestions are welcomed !
254 lines
7.2 KiB
TypeScript
254 lines
7.2 KiB
TypeScript
import prettier from '@prettier/sync';
|
|
import * as fs from 'fs';
|
|
import path from 'path';
|
|
import { Options } from 'prettier';
|
|
import slash from 'slash';
|
|
|
|
// 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';
|
|
const PACKAGE_PATH = path.resolve('packages/twenty-shared');
|
|
const SRC_PATH = path.resolve(`${PACKAGE_PATH}/src`);
|
|
const PACKAGE_JSON_PATH = path.join(PACKAGE_PATH, PACKAGE_JSON_FILENAME);
|
|
const NX_PROJECT_CONFIGURATION_PATH = path.join(
|
|
PACKAGE_PATH,
|
|
NX_PROJECT_CONFIGURATION_FILENAME,
|
|
);
|
|
|
|
const prettierConfigFile = prettier.resolveConfigFile();
|
|
if (prettierConfigFile == null) {
|
|
throw new Error('Prettier config file not found');
|
|
}
|
|
const prettierConfiguration = prettier.resolveConfig(prettierConfigFile);
|
|
const prettierFormat = (str: string, parser: Options['parser']) =>
|
|
prettier.format(str, {
|
|
...prettierConfiguration,
|
|
parser,
|
|
});
|
|
type createTypeScriptFileArgs = {
|
|
path: string;
|
|
content: string;
|
|
filename: string;
|
|
};
|
|
const createTypeScriptFile = ({
|
|
content,
|
|
path: filePath,
|
|
filename,
|
|
}: createTypeScriptFileArgs) => {
|
|
const header = `
|
|
/*
|
|
* _____ _
|
|
*|_ _|_ _____ _ __ | |_ _ _
|
|
* | | \\ \\ /\\ / / _ \\ '_ \\| __| | | | Auto-generated file
|
|
* | | \\ V V / __/ | | | |_| |_| | Any edits to this will be overridden
|
|
* |_| \\_/\\_/ \\___|_| |_|\\__|\\__, |
|
|
* |___/
|
|
*/
|
|
`;
|
|
const formattedContent = prettierFormat(
|
|
`${header}\n${content}\n`,
|
|
'typescript',
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(filePath, `${filename}.ts`),
|
|
formattedContent,
|
|
'utf-8',
|
|
);
|
|
};
|
|
|
|
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;
|
|
})
|
|
.map((subDirectoryName) => path.join(directoryPath, subDirectoryName));
|
|
|
|
const getDirectoryPathsRecursive = (directoryPath: string): string[] => [
|
|
directoryPath,
|
|
...getSubDirectoryPaths(directoryPath).flatMap(getDirectoryPathsRecursive),
|
|
];
|
|
|
|
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),
|
|
),
|
|
);
|
|
// 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<createTypeScriptFileArgs>((moduleDirectory) => {
|
|
const directoryPaths = getDirectoryPathsRecursive(moduleDirectory);
|
|
const content = directoryPaths
|
|
.flatMap((directoryPath) => {
|
|
const directFilesPaths = getFilesPaths(directoryPath);
|
|
|
|
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');
|
|
|
|
return {
|
|
content,
|
|
path: moduleDirectory,
|
|
filename: INDEX_FILENAME,
|
|
};
|
|
});
|
|
};
|
|
|
|
type JsonUpdate = Record<string, any>;
|
|
type WriteInJsonFileArgs = {
|
|
content: JsonUpdate;
|
|
file: string;
|
|
};
|
|
const updateJsonFile = ({ content, file }: WriteInJsonFileArgs) => {
|
|
const updatedJsonFile = JSON.stringify(content);
|
|
const formattedContent = prettierFormat(updatedJsonFile, 'json-stringify');
|
|
fs.writeFileSync(file, formattedContent, 'utf-8');
|
|
};
|
|
|
|
const writeInPackageJson = (update: JsonUpdate) => {
|
|
const rawJsonFile = fs.readFileSync(PACKAGE_JSON_PATH, 'utf-8');
|
|
const initialJsonFile = JSON.parse(rawJsonFile);
|
|
|
|
updateJsonFile({
|
|
file: PACKAGE_JSON_PATH,
|
|
content: {
|
|
...initialJsonFile,
|
|
...update,
|
|
},
|
|
});
|
|
};
|
|
|
|
const updateNxProjectConfigurationBuildOutputs = (outputs: JsonUpdate) => {
|
|
const rawJsonFile = fs.readFileSync(NX_PROJECT_CONFIGURATION_PATH, 'utf-8');
|
|
const initialJsonFile = JSON.parse(rawJsonFile);
|
|
|
|
updateJsonFile({
|
|
file: NX_PROJECT_CONFIGURATION_PATH,
|
|
content: {
|
|
...initialJsonFile,
|
|
targets: {
|
|
...initialJsonFile.targets,
|
|
build: {
|
|
...initialJsonFile.targets.build,
|
|
outputs,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
};
|
|
|
|
const computePackageJsonFilesAndPreconstructConfig = (
|
|
moduleDirectories: string[],
|
|
) => {
|
|
const entrypoints = [...moduleDirectories.map(getLastPathFolder)];
|
|
|
|
return {
|
|
preconstruct: {
|
|
entrypoints: [
|
|
'./index.ts',
|
|
...entrypoints.map((module) => `./${module}/index.ts`),
|
|
],
|
|
},
|
|
files: ['dist', ...entrypoints],
|
|
};
|
|
};
|
|
|
|
const computeProjectNxBuildOutputsPath = (moduleDirectories: string[]) => {
|
|
const dynamicOutputsPath = moduleDirectories
|
|
.map(getLastPathFolder)
|
|
.flatMap((barrelName) =>
|
|
['package.json', 'dist'].map(
|
|
(subPath) => `{projectRoot}/${barrelName}/${subPath}`,
|
|
),
|
|
);
|
|
|
|
return ['{projectRoot}/dist', ...dynamicOutputsPath];
|
|
};
|
|
|
|
const main = () => {
|
|
const moduleDirectories = getSubDirectoryPaths(SRC_PATH);
|
|
const moduleIndexFiles = generateModuleIndexFiles(moduleDirectories);
|
|
const packageJsonPreconstructConfigAndFiles =
|
|
computePackageJsonFilesAndPreconstructConfig(moduleDirectories);
|
|
const nxBuildOutputsPath =
|
|
computeProjectNxBuildOutputsPath(moduleDirectories);
|
|
|
|
updateNxProjectConfigurationBuildOutputs(
|
|
nxBuildOutputsPath
|
|
);
|
|
writeInPackageJson(packageJsonPreconstructConfigAndFiles);
|
|
moduleIndexFiles.forEach(createTypeScriptFile);
|
|
};
|
|
main();
|