Skip to content

Commit

Permalink
[sdk-gen] add new state & format script line code (#9858)
Browse files Browse the repository at this point in the history
* add new state & format script line code

* fix comment
  • Loading branch information
JackTn authored Feb 19, 2025
1 parent d784f16 commit ca0625e
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 105 deletions.
5 changes: 5 additions & 0 deletions tools/spec-gen-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Release

## 2025-02-18 - 0.1.8

- Format error log which has ANSI code that garbled characters are displayed on github checks
- Add new state `NotEnabled` when missing language config in readme.md or tspconfig.yaml

## 2025-02-11 - 0.1.7

- Excluded general messages containing 'error' from being displayed in the Azure pipeline results
Expand Down
2 changes: 1 addition & 1 deletion tools/spec-gen-sdk/src/automation/entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export const sdkAutoMain = async (options: SdkAutoOptions) => {
} catch (e) {
if (workflowContext) {
sdkContext.logger.error(`FatalError: ${e.message}. Please refer to the inner logs for details or report this issue through https://aka.ms/azsdk/support/specreview-channel.`);
workflowContext.status = 'failed';
workflowContext.status = workflowContext.status === 'notEnabled' ? workflowContext.status : 'failed';
setFailureType(workflowContext, FailureType.PipelineFrameworkFailed);
workflowContext.messages.push(e.message);
}
Expand Down
102 changes: 8 additions & 94 deletions tools/spec-gen-sdk/src/automation/reportStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { setSdkAutoStatus } from '../utils/runScript';
import { FailureType, setFailureType, WorkflowContext } from './workflow';
import { formatSuppressionLine } from '../utils/reportFormat';
import { extractPathFromSpecConfig, removeAnsiEscapeCodes } from '../utils/utils';
import { CommentCaptureTransport, vsoAddAttachment, vsoLogIssue } from './logging';
import { vsoAddAttachment, vsoLogIssue } from './logging';
import { ExecutionReport, PackageReport } from '../types/ExecutionReport';
import { writeTmpJsonFile } from '../utils/fsUtils';
import { getGenerationBranchName } from '../types/PackageData';
Expand Down Expand Up @@ -95,6 +95,9 @@ export const generateReport = (context: WorkflowContext) => {
writeTmpJsonFile(context, 'execution-report.json', executionReport);
if (context.status === 'failed') {
vsoLogIssue(`The generation process failed for ${specConfigPath}. Refer to the full log for details.`);
}
else if (context.status === 'notEnabled') {
vsoLogIssue(`SDK configuration is not enabled for ${specConfigPath}. Refer to the full log for details.`, "warning");
} else {
context.logger.info(`Main status [${context.status}]`);
}
Expand Down Expand Up @@ -148,7 +151,8 @@ export const saveFilteredLog = (context: WorkflowContext) => {
inProgress: 'Error',
failed: 'Error',
warning: 'Warning',
succeeded: 'Info'
succeeded: 'Info',
notEnabled: 'Warning'
} as const;
const type = statusMap[context.status];
const filteredResultData = [
Expand Down Expand Up @@ -303,103 +307,12 @@ return `
`
}

export const sdkAutoReportStatus = async (context: WorkflowContext) => {
context.logger.log('section', 'Report status');

const captureTransport = new CommentCaptureTransport({
extraLevelFilter: ['error', 'warn'],
level: 'debug',
output: context.messages
});
context.logger.add(captureTransport);

let hasBreakingChange = false;
let isBetaMgmtSdk = true;
let isDataPlane = true;
let showLiteInstallInstruction = false;
let hasSuppressions = false
let hasAbsentSuppressions = false;
if (context.pendingPackages.length > 0) {
setSdkAutoStatus(context, 'failed');
setFailureType(context, FailureType.PipelineFrameworkFailed);
context.logger.error(`GenerationError: The following packages are still pending.`);
for (const pkg of context.pendingPackages) {
context.logger.error(`\t${pkg.name}`);
context.handledPackages.push(pkg);
}
}

for (const pkg of context.handledPackages) {
setSdkAutoStatus(context, pkg.status);
hasBreakingChange = hasBreakingChange || Boolean(pkg.hasBreakingChange);
isBetaMgmtSdk = isBetaMgmtSdk && Boolean(pkg.isBetaMgmtSdk);
isDataPlane = isDataPlane && Boolean(pkg.isDataPlane);
hasSuppressions = hasSuppressions || Boolean(pkg.presentSuppressionLines.length > 0);
hasAbsentSuppressions = hasAbsentSuppressions || Boolean(pkg.absentSuppressionLines.length > 0);
showLiteInstallInstruction = showLiteInstallInstruction || !!pkg.liteInstallationInstruction;
}

context.logger.info(`Main status [${context.status}] hasBreakingChange [${hasBreakingChange}] isBetaMgmtSdk [${isBetaMgmtSdk}] hasSuppressions [${hasSuppressions}] hasAbsentSuppressions [${hasAbsentSuppressions}]`);
if (context.status === 'failed') {
console.log(`##vso[task.complete result=Failed;]`);
sendFailure();
} else {
sendSuccess();
}

const extra = { hasBreakingChange, showLiteInstallInstruction };
let subTitle = renderHandlebarTemplate(commentSubTitleView, context, extra);
let commentBody = renderHandlebarTemplate(commentDetailView, context, extra);

try {
context.logger.info(`Rendered commentSubTitle: ${prettyFormatHtml(subTitle)}`);
context.logger.info(`Rendered commentBody: ${prettyFormatHtml(commentBody)}`);
} catch (e) {
context.logger.error(`RenderingError: exception is thrown while rendering the title and the body. Error details: ${e.message} ${e.stack}. This doesn't impact the SDK generation, and please click over the details link to view the full pipeine log.`);
// To add log to PR comment
subTitle = renderHandlebarTemplate(commentSubTitleView, context, extra);
commentBody = renderHandlebarTemplate(commentDetailView, context, extra);
}

const statusMap = {
pending: 'Error',
inProgress: 'Error',
failed: 'Error',
warning: 'Warning',
succeeded: 'Info'
} as const;
const type = statusMap[context.status];
const pipelineResultData = [
{
type: 'Markdown',
mode: 'replace',
level: type,
message: commentBody,
time: new Date()
} as MessageRecord
].concat(context.extraResultRecords);

const encode = (str: string): string => Buffer.from(str, 'binary').toString('base64');
console.log(`##vso[task.setVariable variable=SubTitle]${encode(subTitle)}`);

const outputPath = path.join(context.config.workingFolder, 'pipe.log');
context.logger.info(`Writing unified pipeline message to ${outputPath}`);
const content = JSON.stringify(pipelineResultData);

writeFileSync(outputPath, content);

context.logger.log('endsection', 'Report status');
context.logger.remove(captureTransport);
};

export const prettyFormatHtml = (s: string) => {
return prettier.format(s, { parser: 'html' }).replace(/<br>/gi, '<br>\n');
};

const commentDetailTemplate = readFileSync(`${__dirname}/../templates/commentDetailNew.handlebars`).toString();
const commentDetailView = Handlebars.compile(commentDetailTemplate, { noEscape: true });
const commentSubTitleTemplate = readFileSync(`${__dirname}/../templates/commentSubtitleNew.handlebars`).toString();
const commentSubTitleView = Handlebars.compile(commentSubTitleTemplate, { noEscape: true });

const htmlEscape = (s: string) => Handlebars.escapeExpression(s);

Expand All @@ -408,7 +321,8 @@ const githubStateEmoji: { [key in SDKAutomationState]: string } = {
failed: '❌',
inProgress: '🔄',
succeeded: '️✔️',
warning: '⚠️'
warning: '⚠️',
notEnabled: '🚫'
};
const trimNewLine = (line: string) => htmlEscape(line.trimEnd());
const handleBarHelpers = {
Expand Down
6 changes: 5 additions & 1 deletion tools/spec-gen-sdk/src/automation/sdkAutomationState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ const SDKAutomationStateStrings = {
/**
* The generation process has warnings.
*/
warning: `Warning`
warning: `Warning`,
/**
* The generation process exited due to missing language configuration in tspconfig.yaml or reamd.md .
*/
notEnabled: `NotEnabled`
};

/**
Expand Down
5 changes: 4 additions & 1 deletion tools/spec-gen-sdk/src/automation/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export const workflowValidateSdkConfigForSpecPr = async (context: WorkflowContex
throw new Error(`No changes detected in the API specs; SDK generation skipped.`);
}
if (sdkToGenerate.size === 0) {
context.status = 'notEnabled';
throw new Error(`No SDKs are enabled for generation. Please check the configuration in the realted tspconfig.yaml or readme.md`);
}
context.logger.info(`SDK to generate:`);
Expand All @@ -188,7 +189,7 @@ export const workflowValidateSdkConfigForSpecPr = async (context: WorkflowContex
context.logger.log('endsection', 'Validate SDK config for spec PR scenario');
};

export const workflowValidateSdkConfig = async (context: SdkAutoContext) => {
export const workflowValidateSdkConfig = async (context: WorkflowContext) => {
context.logger.log('section', 'Validate SDK configuration');
let sdkToGenerate = "";

Expand Down Expand Up @@ -231,6 +232,7 @@ export const workflowValidateSdkConfig = async (context: SdkAutoContext) => {
context.logger.info(`SDK to generate:${context.config.sdkName}`);
}
else {
context.status = 'notEnabled';
throw new Error(`No SDKs are enabled for generation. Please check the configuration in the related tspconfig.yaml or readme.md`);
}
context.logger.log('endsection', 'Validate SDK configuration');
Expand Down Expand Up @@ -291,6 +293,7 @@ const workflowHandleReadmeMdOrTypeSpecProject = async (context: WorkflowContext,
}

if (typespecProjectList.length === 0 && readmeMdList.length === 0) {
context.status = 'notEnabled';
context.logger.remove(context.messageCaptureTransport);
return;
}
Expand Down
8 changes: 5 additions & 3 deletions tools/spec-gen-sdk/src/utils/runScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import { RunLogFilterOptions, RunLogOptions, RunOptions } from '../types/Swagger
import { Readable } from 'stream';
import { SDKAutomationState } from '../automation/sdkAutomationState';
import { vsoLogIssue } from '../automation/logging';
import { removeAnsiEscapeCodes } from './utils';

export type RunResult = Exclude<SDKAutomationState, 'inProgress' | 'pending'>;
export type RunResult = Exclude<SDKAutomationState, 'inProgress' | 'pending' | 'notEnabled'>;
export type StatusContainer = { status: SDKAutomationState };
const resultLevelMap: { [result in SDKAutomationState]: number } = {
pending: -2,
inProgress: -1,
succeeded: 0,
warning: 1,
failed: 2
notEnabled: 2,
failed: 3
};
const resultLogLevelMap: { [result in RunResult]: string } = {
succeeded: 'info',
Expand Down Expand Up @@ -147,7 +149,7 @@ const listenOnStream = (
}
setSdkAutoStatus(result, lineResult);
if (context.config.runEnv === 'azureDevOps' && line.toLowerCase().includes("[error]")) {
vsoLogIssue(line);
vsoLogIssue(removeAnsiEscapeCodes(line));
} else {
context.logger.log(logType, `${prefix} ${line}`, { showInComment: _showInComment, lineResult });
}
Expand Down
8 changes: 3 additions & 5 deletions tools/spec-gen-sdk/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,13 @@ export function getValueByKey<T>(
return foundObject ? foundObject[targetKey] : undefined;
}

export function removeAnsiEscapeCodes(
messages: string[] | string
): string[] | string {
export function removeAnsiEscapeCodes<T extends string | string[]>(messages: T): T {
// eslint-disable-next-line no-control-regex
const ansiEscapeCodeRegex = /\x1b\[(\d{1,2}(;\d{0,2})*)?[A-HJKSTfimnsu]/g;
if (typeof messages === "string") {
return messages.replace(ansiEscapeCodeRegex, "");
return messages.replace(ansiEscapeCodeRegex, "") as T;
}
return messages.map((item) => item.replace(ansiEscapeCodeRegex, ""));
return messages.map((item) => item.replace(ansiEscapeCodeRegex, "")) as T;
}

export function extractServiceName(path: string): string {
Expand Down
5 changes: 5 additions & 0 deletions tools/spec-gen-sdk/test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ describe('Remove AnsiEscape Codes', () => {

expect(removeAnsiEscapeCodes(ansiArr)).toEqual(expect.arrayContaining(resArr));
})

it('test ansi code error in net generate script', () => {
const ansiError = '\x1b[31;1mWrite-Error: \x1b[31;1m[ERROR] The service service is not onboarded yet. We will not support onboard a new service from swagger. Please contact the DotNet language support channel at https://aka.ms/azsdk/donet-teams-channel and include this spec pull request.\x1b[0m';
expect(removeAnsiEscapeCodes(ansiError)).toEqual('Write-Error: [ERROR] The service service is not onboarded yet. We will not support onboard a new service from swagger. Please contact the DotNet language support channel at https://aka.ms/azsdk/donet-teams-channel and include this spec pull request.');
})
})

describe('getTypeSpecProjectServiceName', () => {
Expand Down

0 comments on commit ca0625e

Please sign in to comment.