Send to zulip

This commit is contained in:
Koper
2023-11-20 21:39:33 +07:00
parent 82f50817f8
commit ba40d28152
3609 changed files with 2311843 additions and 7 deletions

View File

@@ -0,0 +1,97 @@
# AWS SDK for JavaScript Changelog Scripts
These scripts create and update the changelog to summarize what has changed in
each version of the AWS SDK for JavaScript.
Here is a sample of what an entry in the changelog looks like:
## 2.4.5
* bugfix: Waiters: Some description of the bugfix ([Issue #9542]())
* feature: S3: Some descripton of the new feature
* API: RDS: Some description
* API: DynamoDB: Some description
Here is an overview of the scripts that create and update the changelog:
### create-changelog
This script can be used to create or recreate the changelog based on JSON files
in the `.changes/` directory in the root directory of the SDK. This does not
need to be run on a regular basis but is useful if the changelog accidentallly
gets deleted or corrupted. A `.changes/` directory in the root directory will
need to be created before running this script, if it does not already exist. To
run this script, type the following command from the root SDK directory in the
command line:
```
./scripts/changelog/create-changelog
```
The JSON files in the `.changes/` directory must be named with a version number
(e.g. `2.4.5.json`) and its contents should be an array of objects. Each object
represents one change in that version of the SDK, and should contain `"type"`,
`"category"`, and `"description"` properties with string values. Incorrectly
formatted filenames will be skipped over, and incorrectly formatted JSON within
files with correctly formatted names will cause an error to be thrown and halt
the execution of this script. The changelog file is not written to until the
end, so if execution is halted, no files will have changed and no cleanup is
required. The JSON files in `.changes/` are created in the `release` script.
### release
This script should be run for each release. It creates a new entry in the
changelog based on JSON files in the `next-release/` directory in the
`.changes/` directory in the root of the SDK. In addition, it will create a
JSON file for the new version in the `.changes/` directory so that the entry
can be recreated when the `create-changelog` script is run. The `.changes/` and
`next-release/` directories will need to be created before running this script,
if they do not already exist. To run this script, type the following command
from the root SDK directory in the command line:
```
./scripts/changelog/release
```
Optionally, you can provide an argument to specify the version number of the
new release. Accepted values are `major`, `minor`, `patch`, or a version number
that is greater than the latest version (e.g. `2.4.6`). An error will be thrown
if the specified version is not greater than the latest version, and execution
will be halted. The former 3 choices specifies the type of version bump. For
example, running
```
./scripts/changelog/release minor
```
will bump up the minor version from the latest version. If the latest version
is `2.4.5`, then this would set the new version to `2.5.0`. If no argument is
provided, then the script defaults to bumping the patch number.
The JSON files in the `next-release/` directory can either contain a single
object or an array of objects. Each object represents one change in the new
version, and should contain `"type"`, `"category"`, and `"description"`
properties with string values. Incorrectly formatted JSON will cause an error
to be thrown and halt execution of this script. If execution is halted due to
this error, no changes will have been made to any files yet at this point, so
no cleanup will be required.
The script merges all changes in `next-release/` to a new JSON file with the
version number as its name, and files in `next-release/` are deleted. A new
entry is then created in the changelog. If for any reason execution is halted
after `next-release/` is cleared but before changes are written to the
changelog, you can either just run the `create-changelog` script or you can
move the new version JSON file into `next-release/` and re-run the `release`
script (the name of the file does not matter).
### add-change cli
This script creates a changelog entry. The script prompts you to
specify a `type` (e.g. bugfix or feature), a `category` (e.g. a service name
or something like: Paginator), and a short `description` describing the change.
Type the following command from the root SDK directory in the command line to
run this script, using versions of node.js that support promises (0.12.x and higher):
```
node ./scripts/changelog/add-change.js
```
This script will place a JSON file representing your change in the following location:
```
$SDK_ROOT/.changes/next-release/
```
Please run this script and include the JSON file when submitting a pull request.

View File

@@ -0,0 +1,239 @@
var ChangeCreator = require('./change-creator').ChangeCreator;
/**
* The CLI class to add a changelog entry.
*/
function AddChangeCli() {
this._changeCreator = new ChangeCreator();
this._maxRetries = 2;
this._retryCount = 0;
}
AddChangeCli.prototype = {
/**
* Prints a string to stdout.
* @param {string} message - text to print.
*/
print: function print(message) {
process.stdout.write(message);
},
/**
* Prints the CLI intro message.
*/
showIntro: function showIntro() {
var intro = '\n';
intro += 'This utility will walk you through creating a changelog entry.\n\n';
intro += 'A changelog entry requires:\n';
intro += '\t- type: Type should be one of: feature, bugfix.\n';
intro += '\t- category: This can be a service identifier (e.g. "s3"), or something like: Paginator.\n';
intro += '\t- description: A brief description of the change.\n';
intro += '\t You can also include a github style reference such as "#111".\n\n'
intro += 'Please run this script before submitting a pull request.\n\n';
intro += 'Press ^C at any time to quit.\n';
this.print(intro);
},
/**
* Gets a string from stdin and returns a promise resolved with the string.
* Note: stdin is read when the user presses 'Enter'.
* Returns a promise that is resolved with the trimmed user input.
*/
retrieveInputAsync: function retrieveInput() {
return new Promise(function(resolve, reject) {
function getData() {
var chunk = process.stdin.read();
if (chunk !== null) {
// Remove self from stdin and call callback
process.stdin.removeListener('readable', getData);
resolve(chunk.trim());
}
}
process.stdin.setEncoding('utf8');
// start listening for input
process.stdin.on('readable', getData);
});
},
/**
* Prompts the user to enter a type.
* Will also process the user input.
* Returns a promise.
*/
promptType: function promptType() {
var changeCreator = this._changeCreator;
var existingType = changeCreator.getChangeType();
this.print('\nValid types are "feature" or "bugfix"\n');
this.print('type: ' + (existingType ? '(' + existingType + ') ' : ''));
return this.retrieveInputAsync()
.then(this.processType.bind(this));
},
/**
* Prompts the user to enter a category.
* Will also process the user input.
* Returns a promise.
*/
promptCategory: function promptCategory() {
var changeCreator = this._changeCreator;
var existingCategory = changeCreator.getChangeCategory();
this.print('\nCategory can be a service identifier or something like: Paginator\n');
this.print('category: ' + (existingCategory ? '(' + existingCategory + ') ' : ''));
return this.retrieveInputAsync()
.then(this.processCategory.bind(this));
},
/**
* Prompts the user to enter a description.
* Will also process the user input.
* Returns a promise.
*/
promptDescription: function promptDescription() {
var changeCreator = this._changeCreator;
var existingDescription = changeCreator.getChangeDescription();
this.print('\nA brief description of your change.\n');
this.print('description: ' + (existingDescription ? '(' + existingDescription + ') ' : ''));
return this.retrieveInputAsync()
.then(this.processDescription.bind(this));
},
/**
* Handles processing of `type` based on user input.
* If validation of `type` fails, the prompt will be shown again up to 3 times.
* Returns a promise.
*/
processType: function processType(type) {
var changeCreator = this._changeCreator;
var type = type.toLowerCase();
// validate
try {
if (type) {
changeCreator.setChangeType(type);
}
changeCreator.validateChangeType(type);
} catch (err) {
// Log the error
this.print(err.message + '\n');
// re-prompt if we still have retries
if (this._retryCount < this._maxRetries) {
this._retryCount++;
return this.promptType();
}
//otherwise, just exit
return Promise.reject();
}
// reset retry count
this._retryCount = 0;
return Promise.resolve();
},
/**
* Handles processing of `category` based on user input.
* If validation of `category` fails, the prompt will be shown again up to 3 times.
* Returns a promise.
*/
processCategory: function processCategory(category) {
var changeCreator = this._changeCreator;
// validate
try {
if (category) {
changeCreator.setChangeCategory(category);
}
changeCreator.validateChangeCategory(category);
} catch (err) {
// Log the error
this.print(err.message + '\n');
// re-prompt if we still have retries
if (this._retryCount < this._maxRetries) {
this._retryCount++;
return this.promptCategory();
}
//otherwise, just exit
return Promise.reject();
}
// reset retry count
this._retryCount = 0;
return Promise.resolve();
},
/**
* Handles processing of `description` based on user input.
* If validation of `description` fails, the prompt will be shown again up to 3 times.
* Returns a promise.
*/
processDescription: function processDescription(description) {
var changeCreator = this._changeCreator;
// validate
try {
if (description) {
changeCreator.setChangeDescription(description);
}
changeCreator.validateChangeDescription(description);
} catch (err) {
// Log the error
this.print(err.message + '\n');
// re-prompt if we still have retries
if (this._retryCount < this._maxRetries) {
this._retryCount++;
return this.promptDescription();
}
//otherwise, just exit
return Promise.reject();
}
// reset retry count
this._retryCount = 0;
return Promise.resolve();
},
/**
* Prompts the user for all inputs.
* Returns a promise.
*/
promptInputs: function promptInputs() {
var self = this;
return this.promptType()
.then(this.promptCategory.bind(this))
.then(this.promptDescription.bind(this))
.catch(function(err) {
self.print(err.message);
});
},
/**
* Writes the changelog entry to a JSON file.
* Returns a promise that is resolved with the output filename.
*/
writeChangeEntry: function writeChangeEntry() {
var self = this;
return new Promise(function(resolve, reject) {
var changeCreator = self._changeCreator;
changeCreator.writeChanges(function(err, data) {
if (err) {
return reject(err);
}
self.print('\nFile created at ' + data.file + '\n');
return resolve(data);
});
});
}
};
// Run the CLI program
var cli = new AddChangeCli();
cli.showIntro();
cli.promptInputs()
.then(cli.writeChangeEntry.bind(cli))
.then(function() {
// CLI done with its work, exit successfully.
setTimeout(function() {
process.exit(0)
}, 0);
})
.catch(function(err) {
cli.print(err.message);
cli.print('\nExiting...\n');
setTimeout(function() {
// CLI failed, exit with an error
process.exit(1);
}, 0);
});

View File

@@ -0,0 +1,222 @@
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
/**
* Configuration Options:
* - write file location
* - read file location
*/
function checkProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
/**
* Generates a 'random' hex value.
* More 'random' than Math.random without depending on a GUID module.
*/
function generateRandomIdentifier() {
return crypto.randomBytes(4).toString('hex');
}
/**
* Escapes illegal characters from filename
* Ref: https://github.com/aws/aws-sdk-js/issues/3691
*/
function sanitizeFileName(filename) {
return filename.replace(/[^a-zA-Z0-9\\.]/g, '-');
}
var CHANGES_DIR = path.join(process.cwd(), '.changes');
/**
* A map of valid change types.
* Can be referenced outside of this module.
*/
var VALID_TYPES = Object.create(null);
VALID_TYPES['bugfix'] = true;
VALID_TYPES['feature'] = true;
/**
* Handles creating a change log entry JSON file.
*/
function ChangeCreator(config) {
this._config = config || {};
this._type = '';
this._category = '';
this._description = '';
}
ChangeCreator.prototype = {
getChangeType: function getChangeType() {
return this._type;
},
setChangeType: function setChangeType(type) {
this._type = type;
},
getChangeCategory: function getChangeCategory() {
return this._category;
},
setChangeCategory: function setChangeCategory(category) {
this._category = category;
},
getChangeDescription: function getChangeDescription() {
return this._description;
},
setChangeDescription: function setChangeDescription(description) {
this._description = description;
},
/**
* Validates the entire change entry.
*/
validateChange: function validateChange() {
var type = this.getChangeType();
var category = this.getChangeCategory();
var description = this.getChangeDescription();
var missingFields = [];
this.validateChangeType(type);
this.validateChangeCategory(category);
this.validateChangeDescription(description);
return this;
},
/**
* Validates a change entry type.
*/
validateChangeType: function validateChangeType(type) {
var type = type || this._type;
if (!type) {
throw new Error('ValidationError: Missing `type` field.');
}
if (VALID_TYPES[type]) {
return this;
}
var validTypes = Object.keys(VALID_TYPES).join(',');
throw new Error('ValidationError: `type` set as "' + type + '" but must be one of [' + validTypes + '].');
},
/**
* Validates a change entry category.
*/
validateChangeCategory: function validateChangeCategory(category) {
var category = category || this._category;
if (!category) {
throw new Error('ValidationError: Missing `category` field.');
}
return this;
},
/**
* Validates a change entry description.
*/
validateChangeDescription: function validateChangeDescription(description) {
var description = description || this._description;
if (!description) {
throw new Error('ValidationError: Missing `description` field.');
}
return this;
},
/**
* Creates the output directory if it doesn't exist.
*/
createOutputDirectory: function createOutputDirectory(outPath) {
var pathObj = path.parse(outPath);
var sep = path.sep;
var directoryStructure = pathObj.dir.split(sep) || [];
for (var i = 0; i < directoryStructure.length; i++) {
var pathToCheck = directoryStructure.slice(0, i + 1).join(sep);
if (!pathToCheck) {
continue;
}
try {
var stats = fs.statSync(pathToCheck);
} catch (err) {
if (err.code === 'ENOENT') {
// Directory doesn't exist, so create it
fs.mkdirSync(pathToCheck);
} else {
throw err;
}
}
}
return this;
},
/**
* Returns a path to the future change entry file.
*/
determineWriteLocation: function determineWriteLocation() {
/* Order for determining write location:
1) Check configuration for `outFile` location.
2) Check configuration for `inFile` location.
3) Create a new file using default location.
*/
var config = this._config || {};
if (checkProperty(config, 'outFile') && config['outFile']) {
return config['outFile'];
}
if (checkProperty(config, 'inFile') && config['inFile']) {
return config['inFile'];
}
// Determine default location
var newFileName = sanitizeFileName(this._type) + '-' + sanitizeFileName(this._category)
+ '-' + generateRandomIdentifier() + '.json';
return path.join(process.cwd(), '.changes', 'next-release', newFileName);
},
/**
* Writes a change entry as a JSON file.
*/
writeChanges: function writeChanges(callback) {
var hasCallback = typeof callback === 'function';
var fileLocation = this.determineWriteLocation();
try {
// Will throw an error if the change is not valid
this.validateChange().createOutputDirectory(fileLocation);
var change = {
type: this.getChangeType(),
category: this.getChangeCategory(),
description: this.getChangeDescription()
}
fs.writeFileSync(fileLocation, JSON.stringify(change, null, 2));
var data = {
file: fileLocation
};
if (hasCallback) {
return callback(null, data);
} else {
return data;
}
} catch (err) {
if (hasCallback) {
return callback(err, null);
} else {
throw err;
}
}
}
}
module.exports = {
ChangeCreator: ChangeCreator,
VALID_TYPES: VALID_TYPES
};

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env node
var util = require('./util');
var versions = util.listVersions();
util.startNewChangelog();
versions.forEach(util.readVersionJSONAndAddToChangelog);
util.writeToChangelog();

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env node
var util = require('./util');
var input = process.argv[2];
var version;
switch (input) {
case 'major':
version = util.bumpMajor();
break;
case 'minor':
version = util.bumpMinor();
break;
case 'patch':
case undefined:
version = util.bumpPatch();
break;
default:
version = util.checkAndNormalizeVersion(input);
}
var nextReleaseFiles = util.listNextReleaseFiles();
var versionJSON = nextReleaseFiles.reduce(function(changes, filepath) {
return changes.concat(util.readChangesFromJSON(filepath));
}, []);
util.writeToVersionJSON(version, versionJSON);
util.clearNextReleaseDir();
util.addVersionJSONToChangelog(version, versionJSON);
util.writeToChangelog();

View File

@@ -0,0 +1,195 @@
var fs = require('fs');
var changelog, latest, nextReleaseFiles;
var changelogFile = process.cwd() + '/CHANGELOG.md';
var changesDir = process.cwd() + '/.changes/';
var nextReleaseDir = changesDir + 'next-release/';
var insertMarker = '<!--ENTRYINSERT-->';
var versionMarker = ['<!--LATEST=', '-->'];
var startContent = '# Changelog for AWS SDK for JavaScript\n' +
versionMarker.join('0.0.0') + '\n' + insertMarker;
var versionRegStr = '(\\d+)\\.(\\d+)\\.(\\d+)';
var versionReg = new RegExp('^' + versionRegStr + '$');
var versionMarkerReg = new RegExp(versionMarker.join(versionRegStr));
var versionJsonFileReg = new RegExp('^' + versionRegStr + '\\.json$');
function fsSyncFromRoot(operation, fileOrDir) {
try {
var result = fs[operation + 'Sync'](fileOrDir);
} catch(err) {
if (err.code === 'ENOENT') {
err.message += '. Make sure to run from sdk root directory'
}
throw err;
}
return result;
}
function readChangelog() {
changelog = fsSyncFromRoot('readFile', changelogFile).toString();
return changelog;
}
function getLatestVersion() {
if (!changelog) readChangelog();
var match = changelog.match(versionMarkerReg);
latest = {
major: parseInt(match[1],10),
minor: parseInt(match[2],10),
patch: parseInt(match[3],10)
};
return latest;
}
function checkAndNormalizeVersion(version) {
if (!latest) getLatestVersion();
var match = version.match(versionReg);
if (match) {
// convert to num for comparison and for normalizing leading zeros
var major = parseInt(match[1], 10);
var minor = parseInt(match[2], 10);
var patch = parseInt(match[3], 10);
if (major < latest.major ||
major == latest.major && minor < latest.minor ||
major == latest.major && minor == latest.minor && patch <= latest.patch) {
throw new Error('Version must be greater than latest version');
}
return major + '.' + minor + '.' + patch;
} else {
throw new Error('Provided input version is in wrong format');
}
}
function bumpMajor() {
if (!latest) getLatestVersion();
return (latest.major + 1) + '.0.0';
}
function bumpMinor() {
if (!latest) getLatestVersion();
return latest.major + '.' + (latest.minor + 1) + '.0';
}
function bumpPatch() {
if (!latest) getLatestVersion();
return latest.major + '.' + latest.minor + '.' + (latest.patch + 1);
}
function listVersions() {
var changeFiles = fsSyncFromRoot('readdir', changesDir);
return changeFiles
.map(function(file) { return file.match(versionJsonFileReg); })
.filter(function(version) { return !!version; })
.sort(function(v1, v2) {
var diff;
for (var i = 1; i <= 3; i++) {
diff = v1[i] - v2[i];
if (diff !== 0) {
return diff;
}
}
return 0;
})
.map(function(version) { return version.slice(1).join('.'); });
}
function listNextReleaseFiles() {
nextReleaseFiles = fsSyncFromRoot('readdir', nextReleaseDir)
.map(function(file) { return nextReleaseDir + file });
if (!nextReleaseFiles.length) throw new Error('No changes to be released');
return nextReleaseFiles;
}
function startNewChangelog() {
changelog = startContent;
return changelog;
}
function checkChangeFormat(change) {
if (!change.type || !change.category || !change.description ||
typeof change.type !== 'string' ||
typeof change.category !== 'string' ||
typeof change.description !== 'string') {
var err = new Error('JSON not in correct format');
err.code = 'InvalidFormat';
throw err;
}
}
function readChangesFromJSON(filepath) {
var changes = JSON.parse(fsSyncFromRoot('readFile', filepath));
if (!Array.isArray(changes)) changes = [changes];
if (!changes.length) throw new Error(filepath + ' contains no changes');
try {
changes.forEach(checkChangeFormat);
} catch (err) {
if (err.code === 'InvalidFormat') {
err.message += ' in ' + filepath;
}
throw err;
}
return changes;
}
// This will not to write to file
// writeToChangelog must be called after
function addVersionJSONToChangelog(version, changes) {
if (!changelog) readChangelog();
var entry = '\n\n## ' + version;
changes.forEach(function(change) {
entry += '\n* ' + change.type + ': ' + change.category + ': ' +
change.description;
});
var logParts = changelog.split(insertMarker);
logParts[0] = logParts[0]
.replace(versionMarkerReg, versionMarker.join(version)) + insertMarker;
changelog = logParts.join(entry);
}
// This will not to write to file
// writeToChangelog must be called after
function readVersionJSONAndAddToChangelog(version) {
var changes = readChangesFromJSON(changesDir + version + '.json');
addVersionJSONToChangelog(version, changes);
}
function writeToChangelog() {
if (!changelog) throw new Error('Nothing to write');
fs.writeFileSync(changelogFile, changelog);
console.log('Successfully updated CHANGELOG');
}
function writeToVersionJSON(version, json) {
var content = JSON.stringify(json, null, 4);
fs.writeFileSync(changesDir + version + '.json', content);
console.log('Successfully added ' + version + '.json to ' + changesDir);
}
function clearNextReleaseDir() {
if (!nextReleaseFiles) listNextReleaseFiles();
nextReleaseFiles.forEach(function(filepath) {
fsSyncFromRoot('unlink', filepath);
});
console.log(nextReleaseDir + ' has been cleared');
}
module.exports = {
readChangelog: readChangelog,
getLatestVersion: getLatestVersion,
checkAndNormalizeVersion: checkAndNormalizeVersion,
bumpMajor: bumpMajor,
bumpMinor: bumpMinor,
bumpPatch: bumpPatch,
listVersions: listVersions,
listNextReleaseFiles: listNextReleaseFiles,
startNewChangelog: startNewChangelog,
readChangesFromJSON: readChangesFromJSON,
addVersionJSONToChangelog: addVersionJSONToChangelog,
readVersionJSONAndAddToChangelog: readVersionJSONAndAddToChangelog,
writeToChangelog: writeToChangelog,
writeToVersionJSON: writeToVersionJSON,
clearNextReleaseDir: clearNextReleaseDir
};