generator/cmd/generateall.ts (133 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { generateBasePaths, getPackageString, resolveAbsolutePath, validateAndReturnReadmePath } from '../specs';
import { SchemaConfiguration, generateSchemas, clearAutoGeneratedSchemaRefs, saveAutoGeneratedSchemaRefs } from '../generate';
import { findOrGenerateAutogenEntries } from '../autogenlist';
import colors from 'colors';
import { flatten } from 'lodash';
import { executeSynchronous, chunker, writeJsonFile } from '../utils';
import { Package } from '../models';
import yargs from 'yargs';
import path from 'path';
import { createWriteStream } from 'fs';
import stripAnsi from 'strip-ansi';
import { schemasBasePath } from '../constants';
const argsConfig = yargs
.strict()
.option('specs-dir', { type: 'string', demandOption: true, desc: 'Path to the specs dir' })
.option('batch-count', { type: 'number', desc: 'If running in batch mode, the total number of batch jobs running' })
.option('batch-index', { type: 'number', desc: 'If running in batch mode, the index of this batch job' })
.option('readme-files', { type: 'array', desc: 'The list of readme.md files to generate schemas for' })
.option('output-path', { type: 'string', desc: 'The base path to save schema output' })
.option('combine-batch-mode', { type: 'boolean', desc: 'If true, we rely on the combine-batches command to summarize the results.' })
.option('summary-log-path', { type: 'string', desc: 'The path to store generation summary information. File will be saved in md format.' });
interface ILogger {
out: (data: string) => void;
}
executeSynchronous(async () => {
const args = await argsConfig.parseAsync();
const specsPath = await resolveAbsolutePath(args['specs-dir']);
let basePaths = await generateBasePaths(specsPath);
let summaryPath = args['summary-log-path'];
const combineBatchMode = args['combine-batch-mode'] ?? false;
if (!summaryPath) {
summaryPath = path.join(specsPath, 'summary.log');
console.log(`Summary path not passed, using default value: ${summaryPath}`);
}
// resolve absolute path
summaryPath = await resolveAbsolutePath(summaryPath);
const batchIndex = args['batch-index'];
const batchCount = args['batch-count'];
if (batchCount !== undefined && batchIndex !== undefined) {
basePaths = chunker(basePaths, batchCount)[batchIndex];
console.log(`Generating following base paths for batch ${batchIndex}: ${JSON.stringify(basePaths, null, 2)}`);
}
const schemaConfigs: SchemaConfiguration[] = [];
const packages: Package[] = [];
const summaryLogger = await getLogger(summaryPath);
for (const basePath of basePaths) {
try {
const readme = validateAndReturnReadmePath(specsPath, basePath);
let filteredAutoGenList = (await findOrGenerateAutogenEntries(basePath, readme))
.filter(x => x.disabledForAutogen !== true);
if (args['readme-files']) {
filteredAutoGenList = filteredAutoGenList.filter(c => {
const readmeFiles = args['readme-files']?.map(x => x.toString());
const r = readmeFiles?.find(f => f.startsWith('specification/' + c.basePath));
if (r) {
c.readmeFile = r;
return true;
}
return false;
});
}
if (!combineBatchMode) {
await clearAutoGeneratedSchemaRefs(filteredAutoGenList);
}
for (const autoGenConfig of filteredAutoGenList) {
const pkg = {
path: ['schemas']
} as Package;
try {
const readme = validateAndReturnReadmePath(specsPath, autoGenConfig.readmeFile || autoGenConfig.basePath);
pkg.packageName = getPackageString(readme);
const startTime = Date.now();
const newConfigs = await generateSchemas(readme, autoGenConfig);
const generationTime = Date.now() - startTime;
console.log(`Time taken to generate ${colors.green.italic(autoGenConfig.basePath)} : ${colors.magenta.bold(generationTime.toString())} ms.`);
schemaConfigs.push(...newConfigs);
pkg.result = 'succeeded';
logOut(summaryLogger,
`<details>
<summary>Successfully generated types for base path '${basePath}'.</summary>
</details>
`);
} catch(error) {
pkg.packageName = autoGenConfig.basePath;
pkg.result = 'failed';
console.log(colors.red(`Caught exception processing autogenlist entry ${autoGenConfig.basePath}.`));
console.log(colors.red(`${(error as Error)?.stack || error}`));
// return a non-zero exit code to mark the process as failed
process.exitCode = 1;
// Use markdown formatting as this summary will be included in the PR description
logOut(summaryLogger,
`<details>
<summary>Failed to generate types for base path '${autoGenConfig.basePath}' and namespace '${autoGenConfig.namespace}'</summary>
\`\`\`
${error}
\`\`\`
</details>
`);
}
packages.push(pkg);
}
} catch (error) {
// return a non-zero exit code to mark the process as failed
process.exitCode = 1;
// Use markdown formatting as this summary will be included in the PR description
// This error usually indicates that a file has not been found (readme)
logOut(summaryLogger,
`<details>
<summary>Failed to generate types for base path '${basePath}' probably due to readme not found or due to any other file not found exception.</summary>
\`\`\`
${error}
\`\`\`
</details>
`);
}
}
if (combineBatchMode) {
const schemaConfigFile = path.join(schemasBasePath, 'schemaconfig.json');
await writeJsonFile(schemaConfigFile, schemaConfigs);
} else {
await saveAutoGeneratedSchemaRefs(flatten(schemaConfigs), true);
}
if (args['output-path']) {
const outputPath = await resolveAbsolutePath(args['output-path']);
await writeJsonFile(outputPath, { packages });
}
});
function logOut(logger: ILogger, line: string) {
logger.out(`${line}\n`);
}
async function getLogger(logFilePath: string): Promise<ILogger> {
const logFileStream = createWriteStream(logFilePath, { flags: 'a' });
return {
out: (data: string) => {
process.stdout.write(data);
logFileStream.write(stripAnsi(data));
}
};
}