export async function generateRLCInPipeline()

in tools/js-sdk-release-tools/src/llc/generateRLCInPipeline/generateRLCInPipeline.ts [26:279]


export async function generateRLCInPipeline(options: {
    sdkRepo: string;
    swaggerRepo: string;
    readmeMd: string | undefined;
    typespecProject: string | undefined;
    autorestConfig: string | undefined;
    sdkGenerationType: "script" | "command";
    swaggerRepoUrl: string;
    gitCommitId: string;
    typespecEmitter: string;
    use?: string;
    outputJson?: any;
    additionalArgs?: string;
    skipGeneration?: boolean, 
    runningEnvironment?: RunningEnvironment;
}) {
    let packagePath: string | undefined;
    let relativePackagePath: string | undefined;
    const outputPackageInfo = getOutputPackageInfo(options.runningEnvironment, options.readmeMd, options.typespecProject);
    if (options.typespecProject) {
        const typespecProject = path.join(options.swaggerRepo, options.typespecProject); 
        const generatedPackageDir = await getGeneratedPackageDirectory(typespecProject, options.sdkRepo);
        await remove(generatedPackageDir);

        if (!options.skipGeneration) {
            logger.info(`Start to generate rest level client SDK from '${options.typespecProject}'.`);
            // TODO: remove it, since this function is used in pipeline.
            if(options.sdkGenerationType === "command") {
                logger.info("Start to run TypeSpec command directly.");
                const copyPackageJsonName = 'emitter-package.json';
                logger.info(`Start to copy package.json file if not exist from SDK repo '${copyPackageJsonName}'.`);
                const installCommand = prepareCommandToInstallDependenciesForTypeSpecProject(path.join(options.sdkRepo, 'eng', copyPackageJsonName), path.join(options.swaggerRepo, options.typespecProject, 'package.json'));
                logger.info(`Start to run command: '${installCommand}'`);
                execSync(installCommand, {
                    stdio: 'inherit',
                    cwd: path.join(options.swaggerRepo, options.typespecProject)
                });
                updateTypeSpecProjectYamlFile(path.join(options.swaggerRepo, options.typespecProject, 'tspconfig.yaml'), options.sdkRepo, options.typespecEmitter);
                let typespecSource = '.';
                if (fs.existsSync(path.join(options.swaggerRepo, options.typespecProject, 'client.tsp'))) {
                    typespecSource = 'client.tsp';
                }
                logger.info(`Start to run command: 'npx tsp compile ${typespecSource} --emit ${options.typespecEmitter} --arg "js-sdk-folder=${options.sdkRepo}"'.`);
                execSync(`npx tsp compile ${typespecSource} --emit ${options.typespecEmitter} --arg "js-sdk-folder=${options.sdkRepo}"`, {
                    stdio: 'inherit',
                    cwd: path.join(options.swaggerRepo, options.typespecProject)
                });
                logger.info("End with TypeSpec command.");
            } else {
                logger.info("Start to generate code by tsp-client.");
                const tspDefDir = path.join(options.swaggerRepo, options.typespecProject);
                const scriptCommand = ['tsp-client', 'init', '--debug', '--tsp-config', path.join(tspDefDir, 'tspconfig.yaml'), '--local-spec-repo', tspDefDir, '--repo', generateRepoDataInTspLocation(options.swaggerRepoUrl), '--commit', options.gitCommitId].join(" ");
                logger.info(`Start to run command: '${scriptCommand}'`);
                execSync(scriptCommand, {stdio: 'inherit'});
                logger.info("Generated code by tsp-client successfully.");
            }
            packagePath = generatedPackageDir;
            relativePackagePath = path.relative(options.sdkRepo, packagePath);
        }
    } else {
        logger.info(`Start to generate SDK from '${options.readmeMd}'.`);
        if (!options.skipGeneration) {
            let autorestConfigFilePath: string | undefined;
            let isMultiClient: boolean = false;
            if (!!options.autorestConfig) {
                logger.info(`Start to find autorest configuration in PR comment: '${options.autorestConfig}'.`);
                logger.info(`Start to parse the autorest configuration in PR comment.`);
                const yamlBlocks: {
                    condition: string;
                    yamlContent: any;
                }[] = [];
                try {
                    const regexToExtractAutorestConfig = new RegExp(
                        '(?<=``` *(?<condition>yaml.*)\\r\\n)(?<yaml>[^(```)]*)(?=\\r\\n```)', 'g');
                    let match = regexToExtractAutorestConfig.exec(options.autorestConfig);
                    while (!!match) {
                        if (!!match.groups) {
                            // try to load the yaml to check whether it's valid
                            yamlBlocks.push({
                                condition: match.groups.condition,
                                yamlContent: yaml.load(match.groups.yaml)
                            });
                        }
                        match = regexToExtractAutorestConfig.exec(options.autorestConfig);
                    }
                } catch (e: any) {
                    logger.error(`Failed to parse autorestConfig from PR comment: \nErr: ${e}\nStderr: "${e.stderr}"\nStdout: "${e.stdout}"\nErrorStack: "${e.stack}"`);
                    logger.error(`Please check out https://github.com/Azure/autorest/blob/main/docs/troubleshooting.md to troubleshoot the issue.`);
                    throw e;
                }

                yamlBlocks.forEach(e => {
                    if (e.condition.includes(`multi-client`)) {
                        isMultiClient = true;
                    }
                });

                if (isMultiClient) {
                    autorestConfigFilePath = await generateAutorestConfigurationFileForMultiClientByPrComment(yamlBlocks, options.swaggerRepo, options.sdkRepo);
                } else {
                    if (yamlBlocks.length !== 1) {
                        throw new Error(`The yaml config in comment should be 1, but find autorestConfig length: ${yamlBlocks.length}`);
                    }
                    const yamlContent = yamlBlocks[0].yamlContent;
                    autorestConfigFilePath = await generateAutorestConfigurationFileForSingleClientByPrComment(yamlContent, options.swaggerRepo, options.sdkRepo);
                }
            } else {
                logger.info(`Autorest configuration is not found in spec PR comment, and start to find it in sdk repository.`);
                const sdkFolderPath = path.join(options.sdkRepo, 'sdk');
                for (const rp of fs.readdirSync(sdkFolderPath)) {
                    logger.info(`Start to find autorest configuration in '${rp}'.`);
                    if (!!autorestConfigFilePath) break;
                    const rpFolderPath = path.join(sdkFolderPath, rp);
                    if (fs.lstatSync(rpFolderPath).isDirectory()) {
                        for (const packageFolder of fs.readdirSync(rpFolderPath)) {
                            if (!!autorestConfigFilePath) break;
                            if (!packageFolder.endsWith('-rest')) 
                                continue;

                            const packageFolderPath = path.join(rpFolderPath, packageFolder);
                            if (!fs.lstatSync(packageFolderPath).isDirectory()) {
                                continue;
                            }
                            const currentAutorestConfigFilePath = path.join(packageFolderPath, 'swagger', 'README.md');
                            if (!fs.existsSync(currentAutorestConfigFilePath)) {
                                continue;
                            }
                            
                            const autorestConfigFilterRegex = new RegExp(`require:[\\s]*-?[\\s]*(.*${options.readmeMd!.replace(/\//g, '\\/').replace(/\./, '\\.')})`);
                            const autoRestConfigContent = fs.readFileSync(currentAutorestConfigFilePath, 'utf-8');
                            const regexExecResult = autorestConfigFilterRegex.exec(autoRestConfigContent);
                            const requireFoundOnlyOne = regexExecResult && regexExecResult.length === 2;

                            const InputFilePattern = new RegExp(`input-file:.*${path.dirname(options.readmeMd!)}.*`);
                            const containsInputFile = InputFilePattern.test(autoRestConfigContent);

                            if (containsInputFile || requireFoundOnlyOne) {
                                // NOTE: it can be overrided from other RPs
                                if (requireFoundOnlyOne) replaceRequireInAutorestConfigurationFile(currentAutorestConfigFilePath, regexExecResult![1], path.join(options.swaggerRepo, options.readmeMd!));
                                autorestConfigFilePath = currentAutorestConfigFilePath;
                                isMultiClient = fs.readFileSync(currentAutorestConfigFilePath, 'utf-8').includes('multi-client');
                                break;
                            }
                        }
                    }
                }
            }

            if (!autorestConfigFilePath) {
                logger.warn(`Failed to find autorest configuration in spec PR comment or sdk repository, skip generating codes.`);
                logger.warn(`The autorest config file path should be 'sdk/<RP_NAME>-rest/swagger/README.md' in sdk repository, and the autorest config should contain one of the patterns:`);
                logger.warn(`- input-file field contains the 'specification/<RP_NAME>/data-plane' in swagger repository.`);
                logger.warn(`- require field contains the URL to 'specification/<RP_NAME>/data-plane/readme.md' in swagger repository.`);
                logger.warn(`If you ensure there is autorest configuration file in sdk repository, please make sure it contains require keyword and the corresponding readme.md in swagger repository.`);
                return;
            }

            packagePath = path.dirname(path.dirname(autorestConfigFilePath));
            relativePackagePath = path.relative(options.sdkRepo, packagePath);

            let cmd = `autorest --version=3.9.7 ${path.basename(autorestConfigFilePath)} --output-folder=${packagePath}`;
            if (options.use) {
                cmd += ` --use=${options.use}`;
            }
            if (options.additionalArgs) {
                cmd += ` ${options.additionalArgs}`;
            }
            if (isMultiClient) {
                cmd += ` --multi-client=true`;
            }

            logger.info(`Start to run command: ${cmd}.`);
            try {
                execSync(cmd, {stdio: 'inherit', cwd: path.dirname(autorestConfigFilePath), timeout: defaultChildProcessTimeout});
            } catch (e: any) {
                throw new Error(`Failed to generate codes for readme file: "${options.readmeMd}":\nErr: ${e}\nStderr: "${e.stderr}"\nStdout: "${e.stdout}"\nErrorStack: "${e.stack}"`);
            }
        }
    }    

    try {
        if (!packagePath || !relativePackagePath) {
            throw new Error(`Failed to get package path`);
        }
        const packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'), {encoding: 'utf-8'}));
        const packageName = packageJson.name;
        logger.info(`Start to generate some other files for '${packageName}' in '${packagePath}'.`);
        if (!options.skipGeneration) {
            await modifyOrGenerateCiYml(options.sdkRepo, packagePath, packageName, packageName.includes("arm"));

            await changeRushJson(options.sdkRepo, packageName, getRelativePackagePath(packagePath), 'client');

            // TODO: remove it for typespec project, since no need now, the test and sample are decouple from build
            // change configuration to skip build test, sample
            changeConfigOfTestAndSample(packagePath, ChangeModel.Change, SdkType.Rlc);
        }

        if (options.outputJson && options.runningEnvironment !== undefined && outputPackageInfo !== undefined) {
            outputPackageInfo.packageName = packageName;
            outputPackageInfo['version'] = packageJson.version;
            outputPackageInfo.path.push(relativePackagePath);
            for (const file of await getChangedCiYmlFilesInSpecificFolder(path.dirname(relativePackagePath))) {
                outputPackageInfo.path.push(file);
            }
            if (options.runningEnvironment === RunningEnvironment.SdkGeneration) {
                outputPackageInfo.packageFolder = relativePackagePath;
            }
        }

        logger.info(`Start to update rush.`);
        execSync('node common/scripts/install-run-rush.js update', {stdio: 'inherit'});
        
        await migratePackage(packagePath);
        
        logger.info(`Start to build '${packageName}', except for tests and samples, which may be written manually.`);
        // To build generated codes except test and sample, we need to change tsconfig.json.
        execSync(`node common/scripts/install-run-rush.js build -t ${packageName} --verbose`, {stdio: 'inherit'});
        logger.info(`Start to run command 'node common/scripts/install-run-rush.js pack --to ${packageName} --verbose'.`);
        execSync(`node common/scripts/install-run-rush.js pack --to ${packageName} --verbose`, {stdio: 'inherit'});
        if (!options.skipGeneration) {
            const changelog = await generateChangelogAndBumpVersion(relativePackagePath);
            outputPackageInfo.changelog.breakingChangeItems = changelog?.getBreakingChangeItems() ?? [];
            outputPackageInfo.changelog.content = changelog?.displayChangeLog() ?? '';
            outputPackageInfo.changelog.hasBreakingChange = changelog?.hasBreakingChange ?? false;
        }
        if (options.outputJson && options.runningEnvironment !== undefined && outputPackageInfo !== undefined) {
            for (const file of fs.readdirSync(packagePath)) {
                if (file.startsWith('azure-rest') && file.endsWith('.tgz')) {
                    outputPackageInfo.artifacts.push(path.join(relativePackagePath, file));
                }
            }
            addApiViewInfo(outputPackageInfo, packagePath, relativePackagePath);
        }
    } catch (e: any) {
        if (options.typespecProject) {
            logger.error(`Failed to build typespec project: "${options.typespecProject}":\nErr: ${e}\nStderr: "${e.stderr}"\nStdout: "${e.stdout}"\nErrorStack: "${e.stack}".`);
            logger.error(`Please check out https://github.com/Azure/autorest.typescript/blob/main/packages/typespec-ts/CONTRIBUTING.md#how-to-debug to troubleshoot the issue.`);
        } else {
            logger.error(`Failed to build for readme file: "${options.readmeMd}":\nErr: ${e}\nStderr: "${e.stderr}"\nStdout: "${e.stdout}"\nErrorStack: "${e.stack}".`);
            logger.error(`Please check out https://github.com/Azure/autorest/blob/main/docs/troubleshooting.md to troubleshoot the issue.`);
        }
        if (outputPackageInfo) {
            outputPackageInfo.result = 'failed';
        }
        throw e;
    } finally {
        if (options.outputJson && outputPackageInfo) {
            options.outputJson.packages.push(outputPackageInfo);
        }
        if (!options.skipGeneration && !!packagePath) {
            changeConfigOfTestAndSample(packagePath, ChangeModel.Revert, SdkType.Rlc);
        }
    }
}