Skip to content

Commit fd5009a

Browse files
authored
chore: batch rebuild clients script (#200)
1 parent aae3b39 commit fd5009a

File tree

4 files changed

+178
-7
lines changed

4 files changed

+178
-7
lines changed

CONTRIBUTING.md

+17-7
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,21 @@ Please be aware of the following notes prior to opening a pull request:
5858
3. Wherever possible, pull requests should contain tests as appropriate.
5959
Bugfixes should contain tests that exercise the corrected behavior (i.e., the
6060
test should fail without the bugfix and pass with it), and new features
61-
should be accompanied by tests exercising the feature.
62-
63-
4. Pull requests that contain failing tests will not be merged until the test
64-
failures are addressed. Pull requests that cause a significant drop in the
65-
SDK's test coverage percentage are unlikely to be merged until tests have
66-
been added.
61+
should be accompanied by tests exercising the feature. Pull requests that
62+
contain failing tests will not be merged until the test failures are addressed.
63+
Pull requests that cause a significant drop in the SDK's test coverage
64+
percentage are unlikely to be merged until tests have been added.
65+
66+
4. Commit tile and message and pull request title and description must adhere to
67+
[conventional commits][conventional commits]. Title must begin with `feat(module): title`,
68+
`fix(module): title`, `docs(module): title`, `test(module): title`, `chore(module): title`.
69+
Title should be lowercase and not period at the end of it. If the commit includes
70+
a breaking change, the commit message must end with a single paragraph: `BREAKING
71+
CHANGE: a description of what broke`
72+
73+
5. After getting ready to open a pull request, make sure to run the `scripts/
74+
rebuildClients.js` to re-generate all the service clients, and commit the
75+
change(if any) to a standalone commit following the guide above.
6776

6877
### Setup and Testing
6978

@@ -129,4 +138,5 @@ node ./packages/package-generator/build/cli.js client --model models/dynamodb/20
129138
[pr]: https://github.com/aws/aws-sdk-js-v3/pulls
130139
[license]: http://aws.amazon.com/apache2.0/
131140
[cla]: http://en.wikipedia.org/wiki/Contributor_License_Agreement
132-
[AWS service models]: https://github.com/aws/aws-sdk-js-v3/tree/master/models
141+
[AWS service models]: https://github.com/aws/aws-sdk-js-v3/tree/master/models
142+
[conventional commits]: https://www.conventionalcommits.org/

scripts/rebuildClients.js

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
const yargs = require('yargs');
2+
const path = require('path');
3+
const fs = require('fs');
4+
const execSync = require('child_process').execSync;
5+
const ServiceModelFinder = require('./utils/ModelFinder').ServiceModelFinder;
6+
const clientNameRegex = require('./utils/constants').clientNameRegex;
7+
8+
/**
9+
* This script will scan packages directory, recognize service client packages and re-generate them from models
10+
* If this command fails with cannot find model \'clientModuleIdentifier\', please run npm bootstrap && npm test first.
11+
*/
12+
const models = yargs
13+
.alias('m', 'models')
14+
.string('m')
15+
.default('m', path.join('..', '..', 'models'))
16+
.describe('m', 'the directory of models')
17+
.help()
18+
.coerce('m', (directory) => {
19+
return path.normalize(path.join(__filename, path.normalize(directory)));
20+
})
21+
.argv
22+
.models;
23+
24+
console.info('models directory: ', models);
25+
26+
const existingServiceClients = grabExistingClients();
27+
console.info('existing service clients: ', existingServiceClients.map(item => path.parse(item).base));
28+
console.info('generating service clients...');
29+
const serviceModelFinder = new ServiceModelFinder(models);
30+
for (const serviceClient of existingServiceClients) {
31+
const clientName = path.parse(serviceClient).base;
32+
const models = serviceModelFinder.findModelsForService(clientName);
33+
const [_, serviceId, runtime] = clientNameRegex.exec(clientName);
34+
console.info(`generating ${runtime} client from model at ${models.service}`)
35+
generateClient(models, runtime);
36+
}
37+
38+
console.log('done!');
39+
40+
41+
function grabExistingClients() {
42+
const packagesDir = path.join(path.dirname(__dirname), 'packages');
43+
const clientPackagesPaths = [];
44+
for (const package of fs.readdirSync(packagesDir)) {
45+
const packageDir = path.join(packagesDir, package);
46+
if (fs.statSync(packageDir).isDirectory() && isClientPackage(packageDir)) {
47+
clientPackagesPaths.push(packageDir);
48+
}
49+
}
50+
return clientPackagesPaths;
51+
}
52+
53+
function isClientPackage(directory) {
54+
const baseName = path.parse(directory).base;
55+
if (!clientNameRegex.test(baseName)) return false;
56+
const clientFiles = [
57+
{base: 'commands', isFile: false},
58+
{base: 'model', isFile: false},
59+
{base: 'types', isFile: false},
60+
{base: `(\\w+)Client\\.ts`, isFile: true},
61+
{base: `(\\w+)Configuration\\.ts`, isFile: true},
62+
{base: `index\\.ts`, isFile: true},
63+
]
64+
try {
65+
const files = fs.readdirSync(directory);
66+
for (const clientFilePattern of clientFiles) {
67+
const matchedFile = arrayFindPattern(files, clientFilePattern.base);
68+
if (
69+
!matchedFile &&
70+
fs.statSync(path.join(directory, matchedFile)).isFile() !== clientFilePattern.isFile
71+
) {
72+
return false;
73+
}
74+
75+
}
76+
} catch(e) {
77+
return false;
78+
}
79+
return true;
80+
}
81+
82+
function arrayFindPattern(array, pattern) {
83+
return array.find((item) => {
84+
const matched = new RegExp(pattern).exec(item);
85+
//RegExp.exec() returns null if no matched
86+
return Boolean(matched)
87+
})
88+
}
89+
90+
function generateClient(models, runtime) {
91+
const command = `node packages/package-generator/build/cli.js client --m ${models.service} ${models.smoke ? `--s ${models.smoke}` : ''} --r ${runtime}`;
92+
const packageRoot = path.dirname(__dirname);
93+
const log = execSync(command, {cwd: packageRoot});
94+
console.info(log.toString());
95+
}
96+
97+
module.exports.clientNameRegex = clientNameRegex;

scripts/utils/ModelFinder.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const clientNameRegex = require('./constants').clientNameRegex;
4+
const clientModuleIdentifier = require('../../packages/package-generator/build/clientModuleIdentifier').clientModuleIdentifier;
5+
6+
class ServiceModelFinder {
7+
constructor(models) {
8+
this.latestModels = this.fetchLatestModels(models);
9+
this.loadedModelCache = new Map();
10+
}
11+
12+
fetchLatestModels(modelsDir) {
13+
const serviceModelDirs = [];
14+
for (const modelName of fs.readdirSync(modelsDir)) {
15+
const modelDir = path.join(modelsDir, modelName);
16+
if (!fs.statSync(modelDir).isDirectory()) continue;
17+
const versions = fs.readdirSync(modelDir).filter(
18+
version => fs.statSync(path.join(modelDir, version)).isDirectory()
19+
);
20+
if (!versions || versions.length === 0) {
21+
throw new Error(`No api version found in ${modelDir}`);
22+
}
23+
const latestVersion = versions.sort().reverse()[0];
24+
const versionDir = path.join(modelDir, latestVersion);
25+
const serviceModels = fs.readdirSync(versionDir);
26+
if (serviceModels.find(modelName => modelName === 'service-2.json')) {
27+
serviceModelDirs.push(versionDir);
28+
}
29+
}
30+
return serviceModelDirs;
31+
}
32+
33+
/**
34+
* Fetch the directory of model and smoke test for given service name.
35+
* @param {string} service service client package name. like: client-sqs-node
36+
* @returns {object} looks like {service: string, smoke: string};
37+
*/
38+
findModelsForService(packageName) {
39+
const [_, service, runtime] = clientNameRegex.exec(packageName);
40+
if (this.loadedModelCache.has(`client-${service}`)) {
41+
return this.loadedModelCache.get(`client-${service}`);
42+
}
43+
for (const latestModel of this.latestModels.slice(this.loadedModelCache.size)) {
44+
const modelDir = path.join(latestModel, 'service-2.json');
45+
const smokeDir = path.join(latestModel, 'smoke.json');
46+
const {metadata} = JSON.parse(fs.readFileSync(modelDir).toString());
47+
const universalClientName = clientModuleIdentifier(metadata);
48+
const loadedModel = {service: modelDir};
49+
if (fs.existsSync(smokeDir)) {
50+
loadedModel.smoke = smokeDir;
51+
}
52+
this.loadedModelCache.set(universalClientName, loadedModel);
53+
if (universalClientName === `client-${service}`) {
54+
return loadedModel;
55+
}
56+
}
57+
throw new Error(`No model found for ${packageName}`);
58+
}
59+
}
60+
61+
module.exports.ServiceModelFinder = ServiceModelFinder;

scripts/utils/constants.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
clientNameRegex: /^client-(\w+-?\w+)-(node|browser|universal)$/
3+
}

0 commit comments

Comments
 (0)