|
|
@@ -31,9 +31,83 @@ const defaultLogger: Logger = {
|
|
|
},
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * @description
|
|
|
+ * The PathAdapter interface allows customization of how paths are handled
|
|
|
+ * when compiling the Vendure config and its imports.
|
|
|
+ *
|
|
|
+ * This is particularly useful in complex project structures, such as monorepos,
|
|
|
+ * where the Vendure config file may not be in the root directory,
|
|
|
+ * or when you need to transform TypeScript path mappings.
|
|
|
+ */
|
|
|
+export interface PathAdapter {
|
|
|
+ /**
|
|
|
+ * @description
|
|
|
+ * A function to determine the path to the compiled Vendure config file. The default implementation
|
|
|
+ * simple joins the output directory with the config file name:
|
|
|
+ *
|
|
|
+ * ```ts
|
|
|
+ * return path.join(outputPath, configFileName)
|
|
|
+ * ```
|
|
|
+ *
|
|
|
+ * However, in some cases with more complex project structures, you may need to
|
|
|
+ * provide a custom implementation to ensure the compiled config file is
|
|
|
+ * correctly located.
|
|
|
+ *
|
|
|
+ * @example
|
|
|
+ * ```ts
|
|
|
+ * getCompiledConfigPath: ({ inputRootDir, outputPath, configFileName }) => {
|
|
|
+ * const projectName = inputRootDir.split('/libs/')[1].split('/')[0];
|
|
|
+ * const pathAfterProject = inputRootDir.split(`/libs/${projectName}`)[1];
|
|
|
+ * const compiledConfigFilePath = `${outputPath}/${projectName}${pathAfterProject}`;
|
|
|
+ * return path.join(compiledConfigFilePath, configFileName);
|
|
|
+ * },
|
|
|
+ * ```
|
|
|
+ */
|
|
|
+ getCompiledConfigPath?: (params: {
|
|
|
+ inputRootDir: string;
|
|
|
+ outputPath: string;
|
|
|
+ configFileName: string;
|
|
|
+ }) => string;
|
|
|
+ /**
|
|
|
+ * If your project makes use of the TypeScript `paths` configuration, the compiler will
|
|
|
+ * attempt to use these paths when compiling the Vendure config and its imports.
|
|
|
+ *
|
|
|
+ * In certain cases, you may need to transform these paths before they are used. For instance,
|
|
|
+ * if your project is a monorepo and the paths are defined relative to the root of the monorepo,
|
|
|
+ * you may need to adjust them to be relative to the output directory where the compiled files are located.
|
|
|
+ *
|
|
|
+ * @example
|
|
|
+ * ```ts
|
|
|
+ * transformTsConfigPathMappings: ({ phase, patterns }) => {
|
|
|
+ * // "loading" phase is when the compiled Vendure code is being loaded by
|
|
|
+ * // the plugin, in order to introspect the configuration of your app.
|
|
|
+ * if (phase === 'loading') {
|
|
|
+ * return patterns.map((p) =>
|
|
|
+ * p.replace('libs/', '').replace(/.ts$/, '.js'),
|
|
|
+ * );
|
|
|
+ * }
|
|
|
+ * return patterns;
|
|
|
+ * },
|
|
|
+ * ```
|
|
|
+ * @param params
|
|
|
+ */
|
|
|
+ transformTsConfigPathMappings?: (params: {
|
|
|
+ phase: 'compiling' | 'loading';
|
|
|
+ alias: string;
|
|
|
+ patterns: string[];
|
|
|
+ }) => string[];
|
|
|
+}
|
|
|
+
|
|
|
+const defaultPathAdapter: Required<PathAdapter> = {
|
|
|
+ getCompiledConfigPath: ({ outputPath, configFileName }) => path.join(outputPath, configFileName),
|
|
|
+ transformTsConfigPathMappings: ({ patterns }) => patterns,
|
|
|
+};
|
|
|
+
|
|
|
export interface ConfigLoaderOptions {
|
|
|
vendureConfigPath: string;
|
|
|
tempDir: string;
|
|
|
+ pathAdapter?: PathAdapter;
|
|
|
vendureConfigExport?: string;
|
|
|
logger?: Logger;
|
|
|
reportCompilationErrors?: boolean;
|
|
|
@@ -62,7 +136,11 @@ export interface LoadVendureConfigResult {
|
|
|
* to handle the compiled JavaScript output.
|
|
|
*/
|
|
|
export async function loadVendureConfig(options: ConfigLoaderOptions): Promise<LoadVendureConfigResult> {
|
|
|
- const { vendureConfigPath, vendureConfigExport, tempDir } = options;
|
|
|
+ const { vendureConfigPath, vendureConfigExport, tempDir, pathAdapter } = options;
|
|
|
+ const getCompiledConfigPath =
|
|
|
+ pathAdapter?.getCompiledConfigPath ?? defaultPathAdapter.getCompiledConfigPath;
|
|
|
+ const transformTsConfigPathMappings =
|
|
|
+ pathAdapter?.transformTsConfigPathMappings ?? defaultPathAdapter.transformTsConfigPathMappings;
|
|
|
const logger = options.logger || defaultLogger;
|
|
|
const outputPath = tempDir;
|
|
|
const configFileName = path.basename(vendureConfigPath);
|
|
|
@@ -73,11 +151,15 @@ export async function loadVendureConfig(options: ConfigLoaderOptions): Promise<L
|
|
|
inputPath: vendureConfigPath,
|
|
|
outputDir: outputPath,
|
|
|
logger,
|
|
|
+ transformTsConfigPathMappings,
|
|
|
});
|
|
|
- const compiledConfigFilePath = pathToFileURL(path.join(outputPath, configFileName)).href.replace(
|
|
|
- /.ts$/,
|
|
|
- '.js',
|
|
|
- );
|
|
|
+ const compiledConfigFilePath = pathToFileURL(
|
|
|
+ getCompiledConfigPath({
|
|
|
+ inputRootDir,
|
|
|
+ outputPath,
|
|
|
+ configFileName,
|
|
|
+ }),
|
|
|
+ ).href.replace(/.ts$/, '.js');
|
|
|
// create package.json with type commonjs and save it to the output dir
|
|
|
await fs.writeFile(path.join(outputPath, 'package.json'), JSON.stringify({ type: 'commonjs' }, null, 2));
|
|
|
|
|
|
@@ -98,7 +180,12 @@ export async function loadVendureConfig(options: ConfigLoaderOptions): Promise<L
|
|
|
}
|
|
|
|
|
|
// Register path aliases from tsconfig before importing
|
|
|
- const tsConfigInfo = await findTsConfigPaths(vendureConfigPath, logger);
|
|
|
+ const tsConfigInfo = await findTsConfigPaths(
|
|
|
+ vendureConfigPath,
|
|
|
+ logger,
|
|
|
+ 'loading',
|
|
|
+ transformTsConfigPathMappings,
|
|
|
+ );
|
|
|
if (tsConfigInfo) {
|
|
|
tsConfigPaths.register({
|
|
|
baseUrl: outputPath,
|
|
|
@@ -122,6 +209,8 @@ export async function loadVendureConfig(options: ConfigLoaderOptions): Promise<L
|
|
|
async function findTsConfigPaths(
|
|
|
configPath: string,
|
|
|
logger: Logger,
|
|
|
+ phase: 'compiling' | 'loading',
|
|
|
+ transformTsConfigPathMappings: Required<PathAdapter>['transformTsConfigPathMappings'],
|
|
|
): Promise<{ baseUrl: string; paths: Record<string, string[]> } | undefined> {
|
|
|
const configDir = path.dirname(configPath);
|
|
|
let currentDir = configDir;
|
|
|
@@ -147,10 +236,15 @@ async function findTsConfigPaths(
|
|
|
|
|
|
for (const [alias, patterns] of Object.entries(compilerOptions.paths)) {
|
|
|
// Store paths as defined in tsconfig, they will be relative to baseUrl
|
|
|
- paths[alias] = (patterns as string[]).map(pattern =>
|
|
|
+ const normalizedPatterns = (patterns as string[]).map(pattern =>
|
|
|
// Normalize slashes for consistency, keep relative
|
|
|
pattern.replace(/\\/g, '/'),
|
|
|
);
|
|
|
+ paths[alias] = transformTsConfigPathMappings({
|
|
|
+ phase,
|
|
|
+ alias,
|
|
|
+ patterns: normalizedPatterns,
|
|
|
+ });
|
|
|
}
|
|
|
logger.debug(
|
|
|
`Found tsconfig paths in ${tsConfigPath}: ${JSON.stringify(
|
|
|
@@ -184,6 +278,7 @@ type CompileFileOptions = {
|
|
|
inputRootDir: string;
|
|
|
inputPath: string;
|
|
|
outputDir: string;
|
|
|
+ transformTsConfigPathMappings: Required<PathAdapter>['transformTsConfigPathMappings'];
|
|
|
logger?: Logger;
|
|
|
compiledFiles?: Set<string>;
|
|
|
isRoot?: boolean;
|
|
|
@@ -195,6 +290,7 @@ export async function compileFile({
|
|
|
inputRootDir,
|
|
|
inputPath,
|
|
|
outputDir,
|
|
|
+ transformTsConfigPathMappings,
|
|
|
logger = defaultLogger,
|
|
|
compiledFiles = new Set<string>(),
|
|
|
isRoot = true,
|
|
|
@@ -220,7 +316,12 @@ export async function compileFile({
|
|
|
let tsConfigInfo: { baseUrl: string; paths: Record<string, string[]> } | undefined;
|
|
|
|
|
|
if (isRoot) {
|
|
|
- tsConfigInfo = await findTsConfigPaths(absoluteInputPath, logger);
|
|
|
+ tsConfigInfo = await findTsConfigPaths(
|
|
|
+ absoluteInputPath,
|
|
|
+ logger,
|
|
|
+ 'compiling',
|
|
|
+ transformTsConfigPathMappings,
|
|
|
+ );
|
|
|
if (tsConfigInfo) {
|
|
|
logger?.debug(`Using TypeScript configuration: ${JSON.stringify(tsConfigInfo, null, 2)}`);
|
|
|
}
|
|
|
@@ -355,6 +456,7 @@ export async function compileFile({
|
|
|
inputPath: importPath,
|
|
|
outputDir,
|
|
|
logger,
|
|
|
+ transformTsConfigPathMappings,
|
|
|
compiledFiles,
|
|
|
isRoot: false,
|
|
|
pluginInfo,
|