Repository: storybookjs/storybook
File: code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts (TypeScript)

Code before:
    workingDir,
  });

  logger.debug('Getting package manager...');
  const packageManager = JsPackageManagerFactory.getPackageManager({
    force: packageManagerName,
    configDir,
    storiesPaths,
  });

  logger.debug('Getting Storybook version...');
  const storybookVersion = await getCoercedStorybookVersion(packageManager);

  logger.debug('Getting csf version...');
  const csf4 = previewConfigPath ? isCsfFactoryPreview(await readConfig(previewConfigPath)) : false;

  return {
    configDir,
    mainConfig,
    storybookVersionSpecifier,
    storybookVersion,
    mainConfigPath,
    previewConfigPath,
    packageManager,
    storiesPaths,
    csf4,
  };
};
export type GetStorybookData = typeof getStorybookData;

/**
 * A helper function to safely read and write the main config file. At the end of the callback,
 * main.js will be overwritten. If it fails, it will handle the error and log a message to the user
 * explaining what to do.
 *
 * It receives a mainConfigPath and a callback which will have access to utilities to manipulate
 * main.js.
 *
 * @example
 *
 * ```ts
 * await safeWriteMain({ mainConfigPath, dryRun }, async ({ main }) => {
 *   // manipulate main.js here
 * });
 * ```
 */
export const updateMainConfig = async (
  { mainConfigPath, dryRun }: { mainConfigPath: string; dryRun: boolean },
  callback: (main: ConfigFile) => Promise<void> | void
) => {
  try {

Review comment (suggestion):
nit: I think this could be named `hasCSF4Preview` or `hasCSFFactory` instead. @kasperpeulen might have an opinion as to what the best naming is.

It's possible to have mixed CSF3/CSF4 stories, and I wouldn't want us to start using this variable as evidence that a story file being automigrated is in CSF4 syntax, as the function specifically checks that the preview file is a CSF factory.

Code after:
};
export type GetStorybookData = typeof getStorybookData;

/**
 * A helper function to safely read and write the main config file. At the end of the callback,
 * main.js will be overwritten. If it fails, it will handle the error and log a message to the user
 * explaining what to do.
 *
 * It receives a mainConfigPath and a callback which will have access to utilities to manipulate
 * main.js.
 *
 * @example
 *
 * ```ts
 * await safeWriteMain({ mainConfigPath, dryRun }, async ({ main }) => {
 *   // manipulate main.js here
 * });
 * ```
 */
export const updateMainConfig = async (
  { mainConfigPath, dryRun }: { mainConfigPath: string; dryRun: boolean },
  callback: (main: ConfigFile) => Promise<void> | void
) => {
  try {
    const main = await readConfig(mainConfigPath);
    await callback(main);
    if (!dryRun) {
      await writeConfigFile(main);
    }
  } catch (e) {
    logger.log(
      `❌ The migration failed to update your ${picocolors.blue(
        mainConfigPath
      )} on your behalf because of the following error:
        ${e}\n`
    );
    logger.log(
      `⚠️ Storybook automigrations are based on AST parsing and it's possible that your ${picocolors.blue(
        mainConfigPath
      )} file contains a non-standard format (e.g. your export is not an object) or that there was an error when parsing dynamic values (e.g. "require" calls, or usage of environment variables). When your main config is non-standard, automigrations are unfortunately not possible. Please follow the instructions given previously and follow the documentation to make the updates manually.`
    );
  }
};

/** Check if a file is in ESM format based on its content */
export function containsESMUsage(content: string): boolean {
  // For .js/.ts files, check the content for ESM syntax
  // Check for ESM syntax indicators (multiline aware)
  const hasImportStatement =
    /^\s*import\s+/m.test(content) ||
    /^\s*import\s*{/m.test(content) ||