Skip to content

Commit

Permalink
Merge branch 'main' into huijbers/progressive-import
Browse files Browse the repository at this point in the history
  • Loading branch information
rix0rrr authored Oct 19, 2023
2 parents 809a45c + c2a6fe8 commit 208689c
Show file tree
Hide file tree
Showing 161 changed files with 1,130 additions and 292 deletions.
6 changes: 5 additions & 1 deletion .projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const serviceSpecBuild = new TypeScriptWorkspace({
parent: repo,
name: '@aws-cdk/service-spec-build',
description: 'Build the service spec from service-spec-sources to service-spec',
deps: [tsKb, serviceSpecSources, serviceSpecTypes],
deps: [tsKb, serviceSpecTypes, 'commander', 'chalk@^4', serviceSpecSources],
devDeps: ['source-map-support'],
private: true,
});
Expand All @@ -133,6 +133,10 @@ serviceSpecBuild.tasks.addTask('analyze:db', {
exec: 'ts-node src/cli/analyze-db',
receiveArgs: true,
});
serviceSpecBuild.tasks.addTask('diff:db', {
exec: 'ts-node src/cli/diff-db',
receiveArgs: true,
});
serviceSpecBuild.gitignore.addPatterns('db.json');
serviceSpecBuild.gitignore.addPatterns('build-report');

Expand Down
9 changes: 9 additions & 0 deletions packages/@aws-cdk/service-spec-build/.projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion packages/@aws-cdk/service-spec-build/.projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion packages/@aws-cdk/service-spec-build/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions packages/@aws-cdk/service-spec-build/src/cli/diff-db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { loadDatabase } from '@aws-cdk/service-spec-types';
import { Command } from 'commander';
import { DbDiff } from '../db-diff';
import { DiffFormatter } from '../diff-fmt';

async function main() {
const program = new Command();

program
.name('diff-db')
.description('Calculate differences between two databases')
.argument('<db1>', 'First database file')
.argument('<db2>', 'Second database file')
.option('-j, --json', 'Output json', false)
.parse();
const options = program.opts();
const args = program.args;

const db1 = await loadDatabase(args[0]);
const db2 = await loadDatabase(args[1]);

const result = new DbDiff(db1, db2).diff();

const hasChanges =
Object.keys(result.services.added ?? {}).length +
Object.keys(result.services.removed ?? {}).length +
Object.keys(result.services.updated ?? {}).length >
0;

if (options.json) {
console.log(JSON.stringify(result, undefined, 2));
} else {
console.log(new DiffFormatter(db1, db2).format(result));
}

process.exitCode = hasChanges ? 1 : 0;
}

main().catch((e) => {
console.error(e);
process.exitCode = 1;
});
150 changes: 150 additions & 0 deletions packages/@aws-cdk/service-spec-build/src/db-diff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import {
Attribute,
Property,
PropertyType,
Resource,
RichPropertyType,
Service,
SpecDatabase,
SpecDatabaseDiff,
TypeDefinition,
UpdatedAttribute,
UpdatedProperty,
UpdatedResource,
UpdatedService,
UpdatedTypeDefinition,
} from '@aws-cdk/service-spec-types';
import {
diffByKey,
collapseUndefined,
diffScalar,
collapseEmptyDiff,
jsonEq,
diffMap,
diffList,
diffField,
} from './diff-helpers';

export class DbDiff {
constructor(private readonly db1: SpecDatabase, private readonly db2: SpecDatabase) {}

public diff(): SpecDatabaseDiff {
return {
services: diffByKey(
this.db1.all('service'),
this.db2.all('service'),
(service) => service.name,
(a, b) => this.diffService(a, b),
),
};
}

private diffService(a: Service, b: Service): UpdatedService | undefined {
return collapseUndefined({
capitalized: diffScalar(a, b, 'capitalized'),
cloudFormationNamespace: diffScalar(a, b, 'cloudFormationNamespace'),
name: diffScalar(a, b, 'name'),
shortName: diffScalar(a, b, 'shortName'),
resourceDiff: this.diffServiceResources(a, b),
});
}

private diffServiceResources(a: Service, b: Service): UpdatedService['resourceDiff'] {
const aRes = this.db1.follow('hasResource', a).map((r) => r.entity);
const bRes = this.db2.follow('hasResource', b).map((r) => r.entity);

return collapseEmptyDiff(
diffByKey(
aRes,
bRes,
(resource) => resource.cloudFormationType,
(x, y) => this.diffResource(x, y),
),
);
}

private diffResource(a: Resource, b: Resource): UpdatedResource | undefined {
return collapseUndefined({
cloudFormationTransform: diffScalar(a, b, 'cloudFormationTransform'),
documentation: diffScalar(a, b, 'documentation'),
cloudFormationType: diffScalar(a, b, 'cloudFormationType'),
isStateful: diffScalar(a, b, 'isStateful'),
identifier: diffField(a, b, 'identifier', jsonEq),
name: diffScalar(a, b, 'name'),
scrutinizable: diffScalar(a, b, 'scrutinizable'),
tagInformation: diffField(a, b, 'tagInformation', jsonEq),
attributes: collapseEmptyDiff(diffMap(a.attributes, b.attributes, (x, y) => this.diffAttribute(x, y))),
properties: collapseEmptyDiff(diffMap(a.properties, b.properties, (x, y) => this.diffProperty(x, y))),
typeDefinitionDiff: this.diffResourceTypeDefinitions(a, b),
});
}

private diffAttribute(a: Attribute, b: Attribute): UpdatedAttribute | undefined {
const eqType = this.eqType.bind(this);

const anyDiffs = collapseUndefined({
documentation: diffScalar(a, b, 'documentation'),
previousTypes: collapseEmptyDiff(diffList(a.previousTypes ?? [], b.previousTypes ?? [], eqType)),
type: diffField(a, b, 'type', eqType),
});

if (anyDiffs) {
return { old: a, new: b };
}
return undefined;
}

private diffProperty(a: Property, b: Property): UpdatedProperty | undefined {
const eqType = this.eqType.bind(this);

const anyDiffs = collapseUndefined({
documentation: diffScalar(a, b, 'documentation'),
defaultValue: diffScalar(a, b, 'defaultValue'),
deprecated: diffScalar(a, b, 'deprecated'),
required: diffScalar(a, b, 'required'),
scrutinizable: diffScalar(a, b, 'scrutinizable'),
previousTypes: collapseEmptyDiff(diffList(a.previousTypes ?? [], b.previousTypes ?? [], eqType)),
type: diffField(a, b, 'type', eqType),
});

if (anyDiffs) {
return { old: a, new: b };
}
return undefined;
}

private diffResourceTypeDefinitions(a: Resource, b: Resource): UpdatedResource['typeDefinitionDiff'] {
const aTypes = this.db1.follow('usesType', a).map((r) => r.entity);
const bTypes = this.db2.follow('usesType', b).map((r) => r.entity);

return collapseEmptyDiff(
diffByKey(
aTypes,
bTypes,
(type) => type.name,
(x, y) => this.diffTypeDefinition(x, y),
),
);
}

private diffTypeDefinition(a: TypeDefinition, b: TypeDefinition): UpdatedTypeDefinition | undefined {
return collapseUndefined({
documentation: diffScalar(a, b, 'documentation'),
name: diffScalar(a, b, 'name'),
mustRenderForBwCompat: diffScalar(a, b, 'mustRenderForBwCompat'),
properties: collapseEmptyDiff(diffMap(a.properties, b.properties, (x, y) => this.diffProperty(x, y))),
});
}

/**
* Tricky -- we have to deep-compare all the type references which will have different ids in
* different databases.
*
* Solve it by doing a string-render and comparing those (for now).
*/
private eqType(a: PropertyType, b: PropertyType): boolean {
const s1 = new RichPropertyType(a).stringify(this.db1, false);
const s2 = new RichPropertyType(b).stringify(this.db2, false);
return s1 === s2;
}
}
Loading

0 comments on commit 208689c

Please sign in to comment.