Skip to content

Commit

Permalink
docs: generating compatibility table (#26)
Browse files Browse the repository at this point in the history
* docs: generate SDK Clients compatibility table

* fix: eslint for generate-compatibility script

* fix: add DOM types for checking SDKs compatibility

* docs: update compatibility table

* ci: generate compatibility table on release

* build: update yarn.lock
  • Loading branch information
m-radzikowski committed May 31, 2021
1 parent 5a885a1 commit 9dfd256
Show file tree
Hide file tree
Showing 10 changed files with 1,099 additions and 39 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
*.js
test-d/**
dist/**
compatibility/**
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ jobs:
git diff-index --quiet HEAD || git commit -m 'docs: update docs website'
- run: yarn run release

- run: yarn run generate-compatibility
- name: commit compatibility table
run: |
git add compatibility.md
git diff-index --quiet HEAD || git commit -m 'docs: update compatibility table'
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
node_modules/
dist/
coverage/
compatibility/
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,10 @@ it('SNS Client is called', async () => {

For more examples, see the [unit tests](./test/mockClient.test.ts).

## Compatibility

See [compatibility table](compatibility.md) for AWS SDK v3 Clients.

## Caveats

### Order of mock behaviors
Expand Down
274 changes: 274 additions & 0 deletions compatibility.md

Large diffs are not rendered by default.

204 changes: 204 additions & 0 deletions misc/generate-compatibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/* eslint-disable no-console */

/*
* Checks if all AWS SDK v3 clients can be mocked properly based on their types.
*/

import child_process from 'child_process';
import * as fs from 'fs';
import tsd from 'tsd';
import packageJson from '../package.json';

const dir = './compatibility/';
const packagePrefix = '@aws-sdk/client-';

const main = async () => {
const clients = listClients();

console.log(`Found ${clients.length} clients:`);
console.log(clients.map(c => `${c.packageName}@${c.packageVersion}`).join('\n'));
console.log();

setupProject();
installClients(clients);
console.log();

createTests(clients);

const results = await runTests();
console.log(`Found ${results.length} type errors`);

writeResults(clients, results);
};

const listClients = (): Client[] => {
const packages: NpmPackage[] = [];

const size = 250;
let from = 0;
let lastCount = 0;
do {
const found = JSON.parse(child_process.execSync(
`npms search -o json -s ${size} -f ${from} ${packagePrefix}`,
).toString()) as [];
packages.push(...found);

lastCount = found.length;
from += size;
} while (lastCount === size);

const filteredPackages = packages
.map(client => `${client.package.name}@${client.package.version}`)
.filter(client =>
client.startsWith(packagePrefix)
&& !client.includes('browser')
&& !client.includes('node')
&& !client.includes('documentation-generator')
&& !client.includes('client-commander'),
)
.sort();

return filteredPackages.map(p => ({
packageName: p.split('@3')[0],
packageVersion: '3' + p.split('@3')[1],
clientName: toClientName(p),
}));
};

const toClientName = (packageName: string): string => {
let client = packageName.substr(packagePrefix.length).split('@3')[0];

client = client.charAt(0).toUpperCase() + client.slice(1);
client = client.replace(/-([a-z0-9])/g, g => g[1].toUpperCase());

if (client.length <= 3) {
client = client.toUpperCase();
}

const correctCase = [
'AccessAnalyzer', 'ACMPCA', 'Amp', 'AmplifyBackend',
'AppConfig', 'AppIntegrations', 'AppStream', 'AppSync',
'CloudDirectory', 'CloudHSM', 'CloudSearch', 'CloudTrail',
'CodeCommit', 'CodeBuild', 'CodePipeline', 'CodeDeploy', 'CodeGuru', 'CodeGuruProfiler', 'CodeStar',
'CodestarNotifications',
'ComprehendMedical', 'ConnectParticipant',
'DataBrew', 'DataExchange', 'DataSync', 'DevOps',
'DocDB', 'EC2', 'ECR', 'EMR', 'Fis', 'FraudDetector', 'FSx', 'GameLift',
'ECRPUBLIC', 'Secrets',
'GroundStation', 'GuardDuty', 'HealthLake',
'IoT', '1Click', 'Analytics', 'IotDeviceAdvisor', 'FleetHub', 'SecureTunneling', 'SiteWise', 'ThingsGraph',
'Ivs', 'LakeFormation', 'Equipment', 'Metrics', 'Vision', 'Blockchain',
'Connect', 'Convert', 'Live', 'Package', 'Store', 'Tailor', 'Hub', 'Mgn',
'Mq', 'MTurk', 'MWAA', 'Manager', 'OpsWorks', 'OpsWorksCM',
'SMS', 'DynamoDB', 'CloudFormation', 'CloudFront', 'CloudWatch',
'APIGateway', 'V2', 'ApiGatewayV2', 'ApiGatewayManagementApi',
'ElastiCache', 'EventBridge', 'XRay',
'QLDB', 'QuickSight', 'RDS', 'ResourceGroupsTaggingAPI',
'RoboMaker', 'Resolver', 'Outposts', 'SageMaker', 'SagemakerEdge', 'A2I',
'ServerlessApplicationRepository', 'AppRegistry', 'Discovery', 'SES', 'SSM', 'SSO', 'OIDC',
'Identitystore', 'SESv2', 'TimestreamQuery', 'WAF', 'Session',
'Architected', 'Docs', 'Link', 'WorkMail', 'MessageFlow', 'Spaces',
];
correctCase.forEach(correct => {
const re = new RegExp(correct, 'gi');
client = client.replace(re, correct);
});

return client + 'Client';
};

const setupProject = () => {
const tsconfig = JSON.stringify({
'compilerOptions': {
'module': 'CommonJS',
'target': 'ES2018',
'lib': [
'ESNext',
'DOM',
],
'moduleResolution': 'node',
'strict': true,
'skipLibCheck': true,
},
});
fs.writeFileSync(`${dir}tsconfig.json`, tsconfig);
};

const installClients = (clients: Client[]): void => {
child_process.execSync('yarn init -y', {cwd: dir, stdio: 'inherit'});

const packages = clients.map(c => `${c.packageName}@${c.packageVersion}`);
child_process.execSync(`yarn add ${packages.join(' ')}`, {cwd: dir, stdio: 'inherit'});
};

const createTests = (clients: Client[]): void => {
let data =
'import {AwsClientStub, mockClient} from \'../src\';\n' +
'import {expectType} from \'tsd\';\n\n';

data += clients
.map(client => `import {${client.clientName}} from '${client.packageName}';`)
.join('\n') + '\n\n';

data += clients
.map(client => `expectType<AwsClientStub<${client.clientName}>>(mockClient(${client.clientName}));`)
.join('\n');

fs.writeFileSync(`${dir}types.ts`, data);
};

const runTests = async (): Promise<string[]> => {
const diagnostics = await tsd({
cwd: dir,
typingsFile: '../dist/types/index.d.ts',
testFiles: ['types.ts'],
});

return diagnostics
.filter(d => d.severity === 'error')
.map(d => d.message);
};

const writeResults = (clients: Client[], results: string[]): void => {
const clientsResults = clients.map(client => {
const errorFound = results.some(result => result.includes(`Type '${client.clientName}'`));
return {
...client,
compatible: !errorFound,
};
});

const compatibleCount = clientsResults.filter(r => r.compatible).length;

const data = '# Compatibility table\n\n' +
`Generated automatically for **AWS SDK v3 Client mock v${packageJson.version}**\n\n` +
`Compatible clients: ${compatibleCount} of ${clients.length}\n\n` +
'| Client | Package | Version | Compatible |\n' +
'|---|---|---|---|\n' +
clientsResults.map(res =>
`| ${res.clientName.substring(0, res.clientName.length - 'Client'.length)} ` +
`| [${res.packageName}](https://www.npmjs.com/package/${res.packageName}) ` +
`| ${res.packageVersion} ` +
`| ${res.compatible ? 'Yes ✅' : 'No ❌'} |`,
).join('\n');
fs.writeFileSync('compatibility.md', data);
};

main()
.catch(e => {
console.error(e);
process.exit(1);
});

interface NpmPackage {
package: {
name: string;
version: string;
};
}

interface Client {
packageName: string;
packageVersion: string;
clientName: string;
}
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"build": "yarn run build:cjs && yarn run build:es",
"size": "size-limit",
"size:analyze": "size-limit --why",
"release": "standard-version"
"release": "standard-version",
"generate-compatibility": "ts-node ./misc/generate-compatibility.ts"
},
"main": "dist/cjs/index.js",
"types": "dist/types/index.d.ts",
Expand All @@ -52,18 +53,21 @@
"@commitlint/config-conventional": "11.0.0",
"@size-limit/preset-small-lib": "4.10.2",
"@types/jest": "26.0.20",
"@types/node": "14.17.1",
"@types/sinon": "9.0.10",
"@typescript-eslint/eslint-plugin": "4.21.0",
"@typescript-eslint/parser": "4.21.0",
"eslint": "7.24.0",
"husky": "4.3.8",
"jest": "26.6.3",
"lint-staged": "10.5.4",
"npms-cli": "1.6.0",
"rimraf": "3.0.2",
"size-limit": "4.10.2",
"standard-version": "9.1.0",
"ts-jest": "26.5.0",
"tsd": "0.14.0",
"ts-node": "10.0.0",
"tsd": "0.16.0",
"tslib": "2.1.0",
"typedoc": "0.20.35",
"typescript": "4.2.4"
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.eslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"extends": "./tsconfig.json",
"include": [
"src",
"test"
"test",
"misc"
]
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"noImplicitReturns": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"outDir": "dist/cjs",
"declarationDir": "./dist/types"
},
Expand Down
Loading

0 comments on commit 9dfd256

Please sign in to comment.