import chalk from 'chalk'; import { CollectionModuleConfig, isCollectionModuleConfig, } from '../domain/collection-modules/collection-module-config'; import { ProductModule, productModuleFromNetwork } from '../domain/product-module'; import { RootConfigWrite } from '../domain/root-config'; import { CLIError, ExitCodes, PlatformError } from '../errors/platform-error'; import { publishCollectionModuleDefinition } from '../helpers/collection-modules/publish-collection-module'; import { fetchProductModuleDefinition } from '../helpers/fetch-product-module-definition'; import { publishLatestProductModuleDraftDefinition } from '../helpers/publish-product-module'; import { readAuthAndConfig } from '../helpers/read-auth-and-config'; import { ask } from '../helpers/readline-helper'; import { getNewApiHelper } from '../helpers/root-api'; import { createSpinner, runWithSpinner } from '../helpers/spinner'; import { symbols } from '../helpers/symbols'; interface PublishOptions { force?: boolean; } const publishProductModule = async (params: { options: PublishOptions; apiKey: string; productModuleConfig: RootConfigWrite; }) => { const { apiKey, productModuleConfig, options } = params; const { productModuleKey, host } = productModuleConfig; const forcePublish = !!options.force; // Fetch the draft definition to get the version number. This doubles as a pre-check: // the publish endpoint promotes the current draft, so if there is no draft to fetch // there is nothing to publish and we can stop before the confirmation prompt. let draftDefinition; try { draftDefinition = await runWithSpinner('Fetching draft version...', () => fetchProductModuleDefinition({ apiKey, productModuleKey, pullLive: false, host }), ); } catch (error) { // A missing module ("Product module with key 'x' not found") should surface as-is; // a missing definition means there is simply no draft yet. const isMissingDefinition = error instanceof PlatformError && (error.status === 404 || error.networkError?.error?.type === 'not_found_error') && !!error.networkError?.error?.message?.toLowerCase().includes('definition'); if (isMissingDefinition) { throw new CLIError( `No draft definition found for product module '${productModuleKey}'.\nRun \`rp push\` to upload your local definition, then retry \`rp publish\`.`, ExitCodes.GENERAL_ERROR, ); } throw error; } const draftVersion = draftDefinition.version; if (!forcePublish) { console.log( `You are about to publish draft version ${chalk.blue(draftVersion)} of product module '${chalk.blue(productModuleKey)}' to live.\n`, ); console.log( chalk.yellow( 'Publishing to the product module will affect all existing policies on the live environment, please ensure you are familiar with product module versioning before you continue.\n', ), ); const answer = await ask.yesNo('Do you want to continue (y/n)? '); if (!answer) { console.log('\nAborting publish. When you are ready, run the "rp publish" command again.'); return; } } await runWithSpinner('Publishing product module...', () => publishLatestProductModuleDraftDefinition({ apiKey, host, productModuleKey, }), ); // Fetch the new live/draft versions AND the product-module metadata (for the `live` flag) // in parallel — these three requests are independent, so doing them sequentially adds // a full round-trip to every publish. const rootApi = getNewApiHelper({ host, apiKey, throwResponseErrors: true }); const versionSpinner = createSpinner('Fetching updated versions and module metadata...'); versionSpinner.start(); let newLiveVersion: string; let newDraftVersion: string; let productModule: ProductModule; try { const [liveDefinition, newDraftDefinition, productModuleResponse] = await Promise.all([ fetchProductModuleDefinition({ apiKey, productModuleKey, pullLive: true, host }), fetchProductModuleDefinition({ apiKey, productModuleKey, pullLive: false, host }), rootApi.send({ path: `/insurance/product-modules/${productModuleKey}` }), ]); newLiveVersion = liveDefinition.version; newDraftVersion = newDraftDefinition.version; productModule = productModuleFromNetwork(productModuleResponse); versionSpinner.succeed('Versions and module metadata fetched'); } catch (error) { versionSpinner.fail(); throw error; } console.log( chalk.green( `\nProduct module '${productModuleKey}' published successfully. Live version is now ${newLiveVersion}, new draft version is ${newDraftVersion}.`, ), ); if (!productModule.live) { console.log( `${symbols.disabled} This product module is disabled for production. Contact Root support at support@root.co.za to enable it.`, ); } }; const publishCollectionModule = async (params: { options: PublishOptions; apiKey: string; collectionModuleConfig: CollectionModuleConfig; }) => { const { apiKey, collectionModuleConfig, options } = params; const { collectionModuleKey, host } = collectionModuleConfig; const forcePush = !!options.force; if (!forcePush) { console.log( `You are about to publish the latest draft for the collection module with key ${chalk.blue(collectionModuleKey)}\n`, ); console.log( chalk.yellow( 'Publishing the collection module will affect all existing policies on the live environment, please ensure you are familiar with collection module versioning before you continue.\n', ), ); const answer = await ask.yesNo('Do you want to continue (y/n)? '); if (!answer) { console.log('\nAborting publish. When you are ready, run the "rp publish" command again.'); return; } } await runWithSpinner('Publishing collection module...', () => publishCollectionModuleDefinition({ apiKey, collectionModuleKey, host, }), ); console.log(chalk.green(`\nCollection module with key ${collectionModuleKey} has been published`)); }; export const publish = async (options: PublishOptions) => { const { apiKey, config } = readAuthAndConfig(); await (isCollectionModuleConfig(config) ? publishCollectionModule({ apiKey, options, collectionModuleConfig: config }) : publishProductModule({ apiKey, options, productModuleConfig: config })); };