File

libs/sdk/src/node/journey/generate/generate.ts

Extends

Omit

Index

Properties

Properties

directory
Type string
Description

Path where the new workspace will be generated

ignoreLibraryCompatibility (Optional)
Type boolean
Description

Skip the library compatibility check and always use the TALY version that is installed already regardless of potential incompatibilities.

legacyDependencyResolution (Optional)
Type boolean
name
Type string
Description

The name of the journey project that will be created inside of the new workspace

skipInstall
Type boolean
Description

Skip the installation of the Building Block libraries consumed by the journey

import { kebabCase } from 'lodash';
import { existsSync } from 'node:fs';
import { readFile, rm } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import { lt, neq } from 'semver';
import { version as TALY_VERSION } from '../../../../package.json';
import { assertValidName } from '../../../lib/journey/utils/validate-parameters';
import { JourneySrcGenerationOptions } from '../../../lib/model/journey-src-generation-options';
import { loadJourneyFromFolder } from '../load/load';
import { createJourney } from './lib/create-journey/create-journey';
import { createWorkspace } from './lib/create-workspace/create-workspace';
import { generateJourneySrc } from './lib/generate-journey-src/generate-journey-src';
import { installJourneyDependencies } from './lib/install-journey-dependencies/install-journey-dependencies';
import { proceedWithTemporaryTalyVersion } from './lib/proceed-with-temp-taly/proceed-with-temp-taly';
import { findLatestCompatibleTalyVersion } from './utils/taly-version-compatibility';
import { assertValidPaths } from './utils/validate-parameters';

export interface GenerateJourneyOptions extends Omit<JourneySrcGenerationOptions, 'project'> {
  /**
   * Path where the new workspace will be generated
   */

  directory: string;
  /*
   * With this flag the peer dependencies are collected by TALY and not automatically by npm. It can be used to work around peer dependency conflicts.
   */

  legacyDependencyResolution?: boolean;
  /**
   * The name of the journey project that will be created inside of the new workspace
   */
  name: string;

  /**
   * Skip the installation of the Building Block libraries consumed by the journey
   */
  skipInstall: boolean;

  /**
   * Skip the library compatibility check and always use the TALY version that is
   * installed already regardless of potential incompatibilities.
   */
  ignoreLibraryCompatibility?: boolean;
}

export interface ProgressReport {
  type: 'start' | 'success' | 'info' | 'warning';
  text: string;
}

/**
 * This function generates a journey in a new Nx workspace.
 *
 * It returns an AsyncIterator that yields the generation progress.
 *
 * Usage:
 * ```ts
 * for await (const progress of generateJourney(options)) {
 *  console.log(progress.text);
 * }
 * ```
 */
export async function* generateJourney(
  options: GenerateJourneyOptions
): AsyncGenerator<ProgressReport, void> {
  const configDirectory = resolve(options.configDirectory ?? 'config');
  const destinationDirectory = resolve(options.directory);

  const normalizedOptions: GenerateJourneyOptions = {
    ...options,
    configDirectory,
    directory: destinationDirectory
  };

  assertValidName(normalizedOptions.name);
  assertValidPaths(normalizedOptions);

  let yieldIncompatibleTalyVersionWarning = false;
  let compatibleTalyVersion: string;
  let compatibilityError: Error | null = null;

  if (process.env['TALY_SKIP_COMPATIBILITY_CHECK'] !== 'true') {
    yield {
      type: 'start',
      text: 'Checking the compatibility of the libraries used in your journey configuration'
    };
    compatibleTalyVersion = await findLatestCompatibleTalyVersion(
      configDirectory,
      TALY_VERSION
    ).catch((error) => (compatibilityError = error));

    if (compatibleTalyVersion === TALY_VERSION) {
      yield {
        type: 'success',
        text: 'All of the libraries used in your journey configuration are compatible with this version of TALY'
      };
    } else if (normalizedOptions.ignoreLibraryCompatibility) {
      if (compatibilityError) {
        yield {
          type: 'warning',
          text: `Could not find a compatible TALY version for the given combination of libraries and their versions.`
        };
        yield {
          type: 'warning',
          text: `Due to the 'ignoreLibraryCompatibility' option, I will proceed with your local TALY version ${TALY_VERSION}.`
        };
        yieldIncompatibleTalyVersionWarning = true;
      } else if (compatibleTalyVersion !== TALY_VERSION) {
        yield {
          type: 'warning',
          text: `The latest TALY version that is compatible with your journey configuration is ${compatibleTalyVersion}.`
        };
        yield {
          type: 'warning',
          text: `Due to the 'ignoreLibraryCompatibility' option, I will proceed with your local TALY version ${TALY_VERSION}.`
        };
        yieldIncompatibleTalyVersionWarning = true;
      }
    } else {
      if (compatibilityError) {
        throw compatibilityError;
      } else if (lt(compatibleTalyVersion, '23.1.0')) {
        // v23.1.0 was the first to include the `generate-journey` CLI command
        // so we don't support anything below that
        throw new Error(
          `Your journey configuration requires TALY version ${compatibleTalyVersion}. This command only supports Journey configurations with libraries that require TALY version 23.1.0 or above. Please update your journey configuration to use libraries that are compatible with TALY version 23.1.0 or above. It is not recommended, but if you need to disable this check, pass the 'ignoreLibraryCompatibility' option.`
        );
      } else if (compatibleTalyVersion !== TALY_VERSION) {
        yield {
          type: 'success',
          text: `The latest TALY version that is compatible with your journey configuration is ${compatibleTalyVersion} (currently installed: ${TALY_VERSION})`
        };

        // we need to remove the `ignoreLibraryCompatibility` option from the options object
        // otherwise the schema validation from our Nx generator will fail
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const { ignoreLibraryCompatibility, ...otherGenerationOptions } = normalizedOptions;
        yield* proceedWithTemporaryTalyVersion(compatibleTalyVersion, {
          ...otherGenerationOptions,
          configDirectory
        });

        return;
      }
    }
  }

  yield { type: 'start', text: 'Loading Journey Config from Folder' };
  const journeyConfig = await loadJourneyFromFolder(configDirectory);
  yield { type: 'success', text: 'Loaded Journey Config from Folder' };

  if (!existsSync(destinationDirectory)) {
    yield { type: 'start', text: 'Creating a fresh workspace' };
    await createWorkspace(destinationDirectory, normalizedOptions.legacyDependencyResolution);
    yield { type: 'success', text: 'Created a fresh workspace' };
  } else {
    const existingWorkspacePackageJson = JSON.parse(
      await readFile(join(destinationDirectory, 'package.json'), 'utf-8')
    ) as { dependencies: Record<string, string> };
    const talyDependencyVersion = existingWorkspacePackageJson.dependencies['@allianz/taly-nx'];
    const talyDependencyVersionWithoutRange = talyDependencyVersion.replace(/^\^|~/, '');
    if (neq(TALY_VERSION, talyDependencyVersionWithoutRange)) {
      yield {
        type: 'start',
        text: `The existing workspace does not provide the required TALY version (Available: ${talyDependencyVersionWithoutRange}. Required: ${TALY_VERSION}). Removing existing workspace and creating a new one...`
      };
      await rm(destinationDirectory, { recursive: true });
      await createWorkspace(destinationDirectory, normalizedOptions.legacyDependencyResolution);
      yield { type: 'success', text: 'Created a new workspace' };
    } else {
      yield {
        type: 'info',
        text: `Reusing existing workspace in "${destinationDirectory}"`
      };
    }
  }

  const projectName = kebabCase(normalizedOptions.name);

  yield {
    type: 'start',
    text: 'Integrating your journey configuration into the generated workspace'
  };
  await createJourney({
    destinationDirectory: destinationDirectory,
    configDirectory: configDirectory,
    journeyConfig,
    journeyName: projectName,
    envPath: normalizedOptions.envPath,
    aquilaTheme: normalizedOptions.aquilaTheme
  });
  yield {
    type: 'success',
    text: 'Integrated your journey configuration into the generated workspace'
  };

  if (normalizedOptions.skipInstall) {
    yield {
      type: 'warning',
      text: 'Skipping installation of Building Block libraries. This is an advanced feature and should only be used if you know what you are doing.'
    };
  } else {
    yield {
      type: 'start',
      text: 'Installing the required Building Block libraries'
    };
    const isWebcomponent = normalizedOptions.target === 'webcomponent';
    await installJourneyDependencies(
      destinationDirectory,
      journeyConfig,
      isWebcomponent,
      normalizedOptions.legacyDependencyResolution
    );
    yield {
      type: 'success',
      text: 'Installed the required Building Block libraries'
    };
  }

  yield {
    type: 'start',
    text: 'Generating the source code of your journey'
  };
  // we only want to pass the options that are relevant for the `journey-src` generator,
  // so we use object destructuring with the spread operator to "remove" the additional options
  const {
    /* eslint-disable @typescript-eslint/no-unused-vars */
    name,
    directory,
    ignoreLibraryCompatibility,
    legacyDependencyResolution,
    skipInstall,
    /* eslint-disable @typescript-eslint/no-unused-vars */
    ...journeySrcOptions
  } = normalizedOptions;
  await generateJourneySrc(destinationDirectory, {
    ...journeySrcOptions,
    project: projectName
  });
  yield {
    type: 'success',
    text: 'Generated the source code of your journey'
  };

  if (yieldIncompatibleTalyVersionWarning) {
    yield {
      type: 'warning',
      text: 'Please note that the generated journey might not work as expected due to a potentially incompatible TALY version.'
    };
    if (compatibilityError) {
      yield {
        type: 'warning',
        text: `The TALY version that was used to generate your journey is ${TALY_VERSION}, however there is no TALY version that satisfies all of the libraries in your journey configuration.`
      };
      yield {
        type: 'warning',
        text: 'Please make sure to test your journey thoroughly and update your journey configuration to use up-to-date libraries that share a common TALY dependency.'
      };
      yield {
        type: 'warning',
        text: compatibilityError
      };
    } else {
      yield {
        type: 'warning',
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        text: `The TALY version that was used to generate your journey is ${TALY_VERSION}, however the TALY version that is guaranteed to be compatible with your journey configuration is ${compatibleTalyVersion!}.`
      };
      yield {
        type: 'warning',
        text: 'Please make sure to test your journey thoroughly and consider removing the "ignoreLibraryCompatibility" option to use the compatible TALY version to generate your journey.'
      };
    }
    yield {
      type: 'warning',
      text: 'See the documentation for more information: https://taly.frameworks.allianz.io/additional-documentation/app-generation.html#requirement-library-compatibility'
    };
  }
}

results matching ""

    No results matching ""