|
|
|
@@ -29,26 +29,34 @@ import {
|
|
|
|
|
import {
|
|
|
|
|
BackstagePackageJson,
|
|
|
|
|
isBackstagePackage,
|
|
|
|
|
isRejected,
|
|
|
|
|
isFulfilled,
|
|
|
|
|
readFile,
|
|
|
|
|
writeFile,
|
|
|
|
|
} from './utils';
|
|
|
|
|
import { CodeOwnersEntry } from 'codeowners-utils';
|
|
|
|
|
|
|
|
|
|
type CreateFixPackageInfoYamlsOptions = {
|
|
|
|
|
ci?: boolean;
|
|
|
|
|
dryRun?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default async (opts: CreateFixPackageInfoYamlsOptions) => {
|
|
|
|
|
const { dryRun = false } = opts;
|
|
|
|
|
const { dryRun = false, ci = false } = opts;
|
|
|
|
|
const packages = await PackageGraph.listTargetPackages();
|
|
|
|
|
const codeowners = await loadCodeowners();
|
|
|
|
|
const limit = pLimit(10);
|
|
|
|
|
|
|
|
|
|
const results = await Promise.allSettled<void>(
|
|
|
|
|
// If --ci is passed, don't make any changes to the file system; we're only
|
|
|
|
|
// interested in knowing if changes would have been made.
|
|
|
|
|
const isDryRun = ci ? true : dryRun;
|
|
|
|
|
const checkForChanges = ci;
|
|
|
|
|
|
|
|
|
|
const results = await Promise.allSettled<string>(
|
|
|
|
|
packages.map(({ packageJson, dir }) =>
|
|
|
|
|
limit(async () => {
|
|
|
|
|
if (!isBackstagePackage(packageJson)) {
|
|
|
|
|
return;
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if there is already a corresponding catalog-info.yaml
|
|
|
|
@@ -59,32 +67,30 @@ export default async (opts: CreateFixPackageInfoYamlsOptions) => {
|
|
|
|
|
yamlString = await readFile(infoYamlPath, { encoding: 'utf-8' });
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (e.code === 'ENOENT') {
|
|
|
|
|
await createCatalogInfoYaml({
|
|
|
|
|
return await createCatalogInfoYaml({
|
|
|
|
|
yamlPath: infoYamlPath,
|
|
|
|
|
packageJson,
|
|
|
|
|
codeowners,
|
|
|
|
|
dryRun,
|
|
|
|
|
dryRun: isDryRun,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await fixCatalogInfoYaml({
|
|
|
|
|
return await fixCatalogInfoYaml({
|
|
|
|
|
yamlPath: infoYamlPath,
|
|
|
|
|
packageJson,
|
|
|
|
|
codeowners,
|
|
|
|
|
yamlString,
|
|
|
|
|
dryRun,
|
|
|
|
|
dryRun: isDryRun,
|
|
|
|
|
ci,
|
|
|
|
|
});
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const rejects = results.filter(
|
|
|
|
|
r => r.status === 'rejected',
|
|
|
|
|
) as PromiseRejectedResult[];
|
|
|
|
|
const rejects = results.filter(isRejected);
|
|
|
|
|
if (rejects.length > 0) {
|
|
|
|
|
// Problems encountered. Print details here.
|
|
|
|
|
console.error(
|
|
|
|
@@ -94,6 +100,25 @@ export default async (opts: CreateFixPackageInfoYamlsOptions) => {
|
|
|
|
|
console.error();
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (checkForChanges) {
|
|
|
|
|
const instructions = results
|
|
|
|
|
.filter(isFulfilled)
|
|
|
|
|
.map(r => r.value)
|
|
|
|
|
.filter(r => r !== '');
|
|
|
|
|
|
|
|
|
|
// Non-empty instructions indicate changes would have been made.
|
|
|
|
|
if (instructions.length > 0) {
|
|
|
|
|
console.error(
|
|
|
|
|
'\ncatalog-info.yaml file(s) out of sync with CODEOWNERS and/or package.json (see instructions above)\n',
|
|
|
|
|
);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
} else {
|
|
|
|
|
console.error(
|
|
|
|
|
'catalog-info.yaml file(s) in sync with CODEOWNERS and package.json',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type CreateOptions = {
|
|
|
|
@@ -105,6 +130,7 @@ type CreateOptions = {
|
|
|
|
|
|
|
|
|
|
type FixOptions = CreateOptions & {
|
|
|
|
|
yamlString: string;
|
|
|
|
|
ci: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type BackstagePackageEntity = Entity & {
|
|
|
|
@@ -117,18 +143,23 @@ type BackstagePackageEntity = Entity & {
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function createCatalogInfoYaml(options: CreateOptions) {
|
|
|
|
|
async function createCatalogInfoYaml(options: CreateOptions) {
|
|
|
|
|
const { codeowners, dryRun, packageJson, yamlPath } = options;
|
|
|
|
|
const instruction = `Create ${relativePath('.', yamlPath)}`;
|
|
|
|
|
const owner = getOwnerFromCodeowners(codeowners, yamlPath);
|
|
|
|
|
const entity = createOrMergeEntity(packageJson, owner);
|
|
|
|
|
|
|
|
|
|
return dryRun
|
|
|
|
|
? Promise.resolve(console.error(`Create ${relativePath('.', yamlPath)}`))
|
|
|
|
|
: writeFile(yamlPath, YAML.dump(entity));
|
|
|
|
|
if (dryRun) {
|
|
|
|
|
console.error(instruction);
|
|
|
|
|
} else {
|
|
|
|
|
await writeFile(yamlPath, YAML.dump(entity));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return instruction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fixCatalogInfoYaml(options: FixOptions) {
|
|
|
|
|
const { codeowners, dryRun, packageJson, yamlPath, yamlString } = options;
|
|
|
|
|
async function fixCatalogInfoYaml(options: FixOptions) {
|
|
|
|
|
const { ci, codeowners, dryRun, packageJson, yamlPath, yamlString } = options;
|
|
|
|
|
const possibleOwners = getPossibleCodeowners(
|
|
|
|
|
codeowners,
|
|
|
|
|
relativePath('.', yamlPath),
|
|
|
|
@@ -156,12 +187,49 @@ function fixCatalogInfoYaml(options: FixOptions) {
|
|
|
|
|
? getOwnerFromCodeowners(codeowners, yamlPath)
|
|
|
|
|
: yamlJson.spec?.owner;
|
|
|
|
|
const newJson = createOrMergeEntity(packageJson, owner, yamlJson);
|
|
|
|
|
return dryRun
|
|
|
|
|
? Promise.resolve(console.error(`Update ${relativePath('.', yamlPath)}`))
|
|
|
|
|
: writeFile(yamlPath, yamlOverwrite(yamlString, newJson));
|
|
|
|
|
const instructions = [`Update ${relativePath('.', yamlPath)}`];
|
|
|
|
|
|
|
|
|
|
// Show more detailed instructions when --ci flag is set.
|
|
|
|
|
if (ci) {
|
|
|
|
|
if (badOwner) {
|
|
|
|
|
instructions.push(
|
|
|
|
|
` spec.owner cannot be "${
|
|
|
|
|
yamlJson.spec.owner
|
|
|
|
|
}" because it must be one of (${possibleOwners.join(
|
|
|
|
|
', ',
|
|
|
|
|
)}) as listed in CODEOWNERS`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (badTitle) {
|
|
|
|
|
instructions.push(
|
|
|
|
|
` metadata.title cannot be "${yamlJson.metadata.title}" because it must be exactly "${packageJson.name}", the package.json name`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (badName) {
|
|
|
|
|
instructions.push(
|
|
|
|
|
` metadata.name cannot be "${yamlJson.metadata.name}" because it must be exactly "${safeName}", as derived from package.json name`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (badType) {
|
|
|
|
|
instructions.push(
|
|
|
|
|
` spec.type cannot be "${yamlJson.spec.type}" because it must be exactly "backstage-${packageJson.backstage.role}", as derived from package.json backstage.role`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dryRun) {
|
|
|
|
|
console.error(instructions.join('\n'));
|
|
|
|
|
} else {
|
|
|
|
|
await writeFile(yamlPath, yamlOverwrite(yamlString, newJson));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return instructions.join('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|