diff --git a/.github/workflows/generate_monthly_changelog.yml b/.github/workflows/generate_monthly_changelog.yml new file mode 100644 index 000000000000..16c8f8bb4198 --- /dev/null +++ b/.github/workflows/generate_monthly_changelog.yml @@ -0,0 +1,138 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2025 The Stdlib Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#/ + +# Workflow name: +name: generate_monthly_changelog + +# Workflow triggers: +on: + # Run the workflow at midnight UTC on the first day of each month: + schedule: + - cron: '0 0 1 * *' + + # Allow the workflow to be manually run: + workflow_dispatch: + +# Global permissions: +permissions: + # Allow read-only access to the repository contents: + contents: read + +# Workflow jobs: +jobs: + # Generate a monthly changelog: + generate-monthly-changelog: + # Define a display name: + name: 'Generate Monthly Changelog' + + # Define the type of virtual host machine: + runs-on: ubuntu-latest + + # Workflow steps: + steps: + - name: 'Checkout source repository' + # Pin action to full length commit SHA + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + # Specify whether to remove untracked files before checking out the repository: + clean: false + + # Limit clone depth to the most recent commit: + fetch-depth: 1 + + # Token for accessing the repository: + token: ${{ secrets.STDLIB_BOT_FGPAT_REPO_READ }} + + # Avoid storing GitHub token in local Git configuration: + persist-credentials: false + + # Install Node.js: + - name: 'Install Node.js' + # Pin action to full length commit SHA + uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 + with: + node-version: '20' # 'lts/*' + timeout-minutes: 5 + + # Install dependencies (accounting for possible network failures, etc, when installing node module dependencies): + - name: 'Install dependencies' + run: | + make install-node-modules || make install-node-modules || make install-node-modules + timeout-minutes: 15 + + - name: 'Checkout monthly changelog repository' + # Pin action to full length commit SHA + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + # Monthly changelog repository: + repository: 'stdlib-js/www-blog-monthly-changelog' + + # File path to checkout to: + path: './www-blog-monthly-changelog' + + # Specify whether to remove untracked files before checking out the repository: + clean: false + + # Limit clone depth to the most recent commit: + fetch-depth: 1 + + # Token for accessing the repository: + token: ${{ secrets.STDLIB_BOT_FGPAT_REPO_READ }} + + # Avoid storing GitHub token in local Git configuration: + persist-credentials: false + + # Generate changelog for last month: + - name: 'Generate changelog for last month' + run: | + UNTIL=$(date +"%Y-%m-01") + node -e " + var generate = require( '@stdlib/_tools/changelog/generate' ); + var changelog = generate( '@stdlib', { + 'flags': { + 'since': '$UNTIL - 1 month', + 'until': '$UNTIL' + }, + 'format': 'aggregated' + }); + console.log( changelog.content ); + + " > ./www-blog-monthly-changelog/monthly_changelog_${UNTIL//-/_}.md + + # Import GPG key to sign commits: + - name: 'Import GPG key to sign commits' + # Pin action to full length commit SHA + uses: crazy-max/ghaction-import-gpg@cb9bde2e2525e640591a934b1fd28eef1dcaf5e5 # v6.2.0 + with: + gpg_private_key: ${{ secrets.STDLIB_BOT_GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.STDLIB_BOT_GPG_PASSPHRASE }} + git_user_signingkey: true + git_commit_gpgsign: true + + # Commit and push changes: + - name: 'Commit and push changes' + env: + REPO_GITHUB_TOKEN: ${{ secrets.STDLIB_BOT_PAT_REPO_WRITE }} + USER_NAME: stdlib-bot + run: | + cd ./www-blog-monthly-changelog + git config --local user.email "82920195+stdlib-bot@users.noreply.github.com" + git config --local user.name "stdlib-bot" + git add . + git commit -m "Add monthly changelog" || exit 0 + git push "https://$USER_NAME:$REPO_GITHUB_TOKEN@github.com/stdlib-js/www-blog-monthly-changelog.git" main diff --git a/lib/node_modules/@stdlib/_tools/changelog/generate/README.md b/lib/node_modules/@stdlib/_tools/changelog/generate/README.md index 007011a6cb82..9b8ee8792450 100644 --- a/lib/node_modules/@stdlib/_tools/changelog/generate/README.md +++ b/lib/node_modules/@stdlib/_tools/changelog/generate/README.md @@ -30,10 +30,12 @@ limitations under the License. var generate = require( '@stdlib/_tools/changelog/generate' ); ``` -#### generate( pkg\[, releaseType] ) +#### generate( pkg\[, options] ) Generates a Markdown formatted changelog for a specified package. + + ```javascript var changelog = generate( '@stdlib/assert/contains' ); // returns {...} @@ -44,13 +46,30 @@ The function returns an object with the following properties: - **content**: Markdown formatted changelog. - **releaseType**: release type (`null` if changelog is for a non-release). -To generate a changelog for an upcoming release, provide a valid release type as the second argument. +The function accepts the following `options`: + +- **releaseType**: a release type for which to generate the changelog. + +- **format**: changelog format. Must be one of the following: + + - `'grouped'`: group commits by package. + - `'aggregated'`: display all commits in a flat list without grouping commits by package. + +- **flags**: `git log` options used to retrieve commits from which to generate the changelog. + +By default, the changelog is generated for a non-release. To generate a changelog for an upcoming release, provide a valid release type: + + ```javascript -var changelog = generate( '@stdlib/assert/contains', 'patch' ); +var changelog = generate( '@stdlib/assert/contains', { + 'releaseType': 'patch' +}); // returns {...} -changelog = generate( '@stdlib/assert/contains', 'minor' ); +changelog = generate( '@stdlib/assert/contains', { + 'releaseType': 'minor' +}); // returns {...} ``` @@ -66,6 +85,44 @@ The following release types are supported: - `auto`: automatically determine the release type based on parsed commit messages. - `none`: no release (equivalent to not specifying a release type). +By default, the function generates a `grouped` changelog for namespace packages and an `aggregated` changelog for non-namespace packages. To specify a desired output format, set the `format` option: + + + +```javascript +// Changelog grouped by individual packages: +var changelog = generate( '@stdlib/math/base/utils', { + 'format': 'grouped' +}); +// returns {...} + +// Changelog where all commits, potentially touching many different packages, are merged together: +changelog = generate( '@stdlib/math/base/utils', { + 'format': 'aggregated' +}); +// returns {...} +``` + +When generating a changelog, the function uses `git log` to retrieve the commits from which to assemble a set of changes. The `flags` option allows passing options to directly to the `git log` command (e.g., to generate a changelog for a specified time interval or for an individual contributor). + + + +```javascript +var changelog = generate( '@stdlib/ndarray', { + 'flags': { + 'since': 'last year' + } +}); +// returns {...} + +changelog = generate( '@stdlib/ndarray', { + 'flags': { + 'author': 'Athan Reines ' + } +}); +// returns {...} +``` + @@ -80,6 +137,8 @@ The following release types are supported: ## Examples + + ```javascript var generate = require( '@stdlib/_tools/changelog/generate' ); @@ -92,7 +151,9 @@ var releaseType = changelog.releaseType; // returns null // Generate a changelog for a new release: -changelog = generate( '@stdlib/utils/curry', 'patch' ); +changelog = generate( '@stdlib/utils/curry', { + 'releaseType': 'patch' +}); content = changelog.content; // returns '...' @@ -103,6 +164,15 @@ releaseType = changelog.releaseType; changelog = generate( '@stdlib/string/base' ); content = changelog.content; // returns '...' + +// Generate a changelog restricted to a single author: +changelog = generate( '@stdlib/string/base', { + 'flags': { + 'author': 'Athan Reines ' + } +}); +content = changelog.content; +// returns '...' ``` diff --git a/lib/node_modules/@stdlib/_tools/changelog/generate/examples/index.js b/lib/node_modules/@stdlib/_tools/changelog/generate/examples/index.js index 43b3f5f8ead8..5c52da09fa3d 100644 --- a/lib/node_modules/@stdlib/_tools/changelog/generate/examples/index.js +++ b/lib/node_modules/@stdlib/_tools/changelog/generate/examples/index.js @@ -31,7 +31,9 @@ console.log( releaseType ); // => null // Generate a changelog for a new release: -changelog = generate( '@stdlib/utils/curry', 'patch' ); +changelog = generate( '@stdlib/utils/curry', { + 'releaseType': 'patch' +}); content = changelog.content; console.log( content ); // => '...' @@ -45,3 +47,12 @@ changelog = generate( '@stdlib/string/base' ); content = changelog.content; console.log( content ); // => '...' + +// Generate a changelog restricted to a single author: +changelog = generate( '@stdlib/string/base', { + 'flags': { + 'author': 'Athan Reines ' + } +}); +content = changelog.content; +console.log( content ); diff --git a/lib/node_modules/@stdlib/_tools/changelog/generate/lib/main.js b/lib/node_modules/@stdlib/_tools/changelog/generate/lib/main.js index de02c8b74f16..7d8ba9b5ee20 100644 --- a/lib/node_modules/@stdlib/_tools/changelog/generate/lib/main.js +++ b/lib/node_modules/@stdlib/_tools/changelog/generate/lib/main.js @@ -48,6 +48,7 @@ var formatCommits = require( './format_commits.js' ); var npmReleases = require( './npm_releases.js' ); var sectionStart = require( './section_start.js' ); var sectionEnd = require( './section_end.js' ); +var validate = require( './validate.js' ); var heading = require( './heading.js' ); @@ -208,7 +209,10 @@ function packageSummaryWrapper( pkg, version, name, summary ) { * Generates a Markdown formatted changelog for a specified stdlib package. * * @param {string} pkg - package name -* @param {string} [releaseType] - release type (`patch`, `minor`, `major`, `prerelease`, `prepatch`, `preminor`, `premajor`, or `auto`) +* @param {Options} options - function options +* @param {string} [options.releaseType] - release type (`patch`, `minor`, `major`, `prerelease`, `prepatch`, `preminor`, `premajor`, or `auto`) +* @param {string} [options.format] - output format (`grouped` or `aggregated`) +* @param {Options} [options.flags] - `git log` options used to retrieve commits * @throws {TypeError} must provide a string * @throws {Error} must provide a valid package name * @throws {TypeError} must provide a recognized release type @@ -227,18 +231,24 @@ function packageSummaryWrapper( pkg, version, name, summary ) { * // returns {...} * * @example -* var changelog = generate( '@stdlib/utils/curry', 'patch' ); +* var changelog = generate( '@stdlib/utils/curry', { +* 'releaseType': 'patch' +* }); * // returns {...} * * @example -* var changelog = generate( '@stdlib/utils/curry', 'major' ); +* var changelog = generate( '@stdlib/utils/curry', { +* 'releaseType': 'major' +* }); * // returns {...} */ -function generate( pkg, releaseType ) { +function generate( pkg, options ) { var isNamespacePkg; var releaseCommits; var newestRelease; + var outputFormat; var bySubpackage; + var releaseType; var nextVersion; var standalone; var unreleased; @@ -247,14 +257,28 @@ function generate( pkg, releaseType ) { var commits; var version; var summary; + var opts; var name; var str; + var err; var i; var j; if ( !isString( pkg ) ) { throw new TypeError( format( 'invalid argument. Must provide a string. Value: `%s`.', pkg ) ); } + opts = {}; + if ( arguments.length > 1 ) { + err = validate( opts, options ); + if ( err ) { + throw err; + } + } + outputFormat = opts.format; + if ( !outputFormat ) { + outputFormat = ( isNamespacePkg ) ? 'grouped' : 'aggregated'; + } + if ( pkg === '@stdlib' || pkg === '@stdlib/stdlib' ) { // Case: root package isNamespacePkg = true; @@ -275,9 +299,16 @@ function generate( pkg, releaseType ) { str = '# CHANGELOG\n\n'; str += '> Package changelog.\n\n'; - commits = parseCommits({ - 'dir': join( STDLIB_LIB_DIR, pkg ) - }); + if ( opts.flags ) { + commits = parseCommits({ + 'dir': join( STDLIB_LIB_DIR, pkg ), + 'flags': opts.flags + }); + } else { + commits = parseCommits({ + 'dir': join( STDLIB_LIB_DIR, pkg ) + }); + } if ( commits.length === 0 ) { throw new Error( format( 'invalid argument. Unable to parse commits for package: `%s`.', pkg ) ); } @@ -291,6 +322,7 @@ function generate( pkg, releaseType ) { commits.unreleased = []; } + releaseType = opts.releaseType; if ( releaseType === 'auto' ) { releaseType = recommendVersionBump( commits.unreleased ); @@ -315,7 +347,7 @@ function generate( pkg, releaseType ) { } } - if ( isNamespacePkg ) { + if ( outputFormat === 'grouped' ) { str += releaseSectionStart( nextVersion ); str += '## ' + ( nextVersion || 'Unreleased' ) + ' (' + formatDate() + ')\n\n'; if ( commits.unreleased.length > 0 ) { @@ -348,7 +380,7 @@ function generate( pkg, releaseType ) { str += sectionEnd( 'release' ); } } - if ( isNamespacePkg ) { + if ( outputFormat === 'grouped' ) { for ( i = releases.length-1; i >= 0; i-- ) { version = releases[ i ][ 0 ]; str += releaseSectionStart( version ); @@ -381,6 +413,9 @@ function generate( pkg, releaseType ) { version = releases[ i ][ 0 ]; summary = releaseSummary( commits[ version ] ); if ( !summary ) { + if ( opts.flags ) { + continue; + } summary = PLACEHOLDER_SUMMARY; } str += releaseSectionStart( version ); diff --git a/lib/node_modules/@stdlib/_tools/changelog/generate/lib/validate.js b/lib/node_modules/@stdlib/_tools/changelog/generate/lib/validate.js new file mode 100644 index 000000000000..8203df3f9f27 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/changelog/generate/lib/validate.js @@ -0,0 +1,88 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var isObject = require( '@stdlib/assert/is-plain-object' ); +var hasOwnProp = require( '@stdlib/assert/has-own-property' ); +var isString = require( '@stdlib/assert/is-string' ).isPrimitive; +var isPlainObject = require( '@stdlib/assert/is-plain-object' ); +var contains = require( '@stdlib/array/base/assert/contains' ).factory; +var format = require( '@stdlib/string/format' ); + + +// VARIABLES // + +var FORMAT_OPTIONS = [ 'grouped', 'aggregated' ]; +var isFormat = contains( FORMAT_OPTIONS ); + + +// MAIN // + +/** +* Validates function options. +* +* @private +* @param {Object} opts - destination for function options +* @param {Options} options - function options +* @param {string} [options.releaseType] - release type +* @param {string} [options.format] - output format +* @param {string} [options.flags] - `git log` options used to retrieve commits +* @returns {(Error|null)} error or null +* +* @example +* var opts = {}; +* var options = { +* 'month': '01-2025' +* }; +* var err = validate( opts, options ); +* if ( err ) { +* throw err; +* } +*/ +function validate( opts, options ) { + if ( !isObject( options ) ) { + return new TypeError( format( 'invalid argument. Options argument must be an object. Value: `%s`.', options ) ); + } + if ( hasOwnProp( options, 'releaseType' ) ) { + opts.releaseType = options.releaseType; + if ( !isString( opts.releaseType ) ) { + return new TypeError( format( 'invalid option. `%s` option must be a string. Option: `%s`.', 'releaseType', opts.releaseType ) ); + } + } + if ( hasOwnProp( options, 'format' ) ) { + opts.format = options.format; + if ( !isFormat( opts.format ) ) { + return new TypeError( format( 'invalid option. `%s` option must be one of the following: "%s". Option: `%s`.', 'format', FORMAT_OPTIONS.join( '", "' ), opts.format ) ); + } + } + if ( hasOwnProp( options, 'flags' ) ) { + opts.flags = options.flags; + if ( !isPlainObject( opts.flags ) ) { + return new TypeError( format( 'invalid option. `%s` option must be an object. Option: `%s`.', 'flags', opts.flags ) ); + } + } + return null; +} + + +// EXPORTS // + +module.exports = validate; diff --git a/lib/node_modules/@stdlib/_tools/changelog/generate/test/test.js b/lib/node_modules/@stdlib/_tools/changelog/generate/test/test.js index 9e40c88bea17..2d59f61c72b9 100644 --- a/lib/node_modules/@stdlib/_tools/changelog/generate/test/test.js +++ b/lib/node_modules/@stdlib/_tools/changelog/generate/test/test.js @@ -101,7 +101,9 @@ tape( 'the function throws an error if provided an invalid release type', functi function badValue( value ) { return function badValue() { - generate( '@stdlib/utils/curry', value ); + generate( '@stdlib/utils/curry', { + 'releaseType': value + }); }; } }); @@ -126,17 +128,23 @@ tape( 'the function generates a changelog', function test( t ) { }); tape( 'the function generates a changelog for a new release', function test( t ) { - var out = generate( '@stdlib/utils/noop', 'patch' ); + var out = generate( '@stdlib/utils/noop', { + 'releaseType': 'patch' + }); t.strictEqual( isObject( out ), true, 'returns expected value' ); t.strictEqual( typeof out.content, 'string', 'returns expected value' ); t.strictEqual( out.releaseType, 'patch', 'returns expected value' ); - out = generate( '@stdlib/utils/noop', 'minor' ); + out = generate( '@stdlib/utils/noop', { + 'releaseType': 'minor' + }); t.strictEqual( isObject( out ), true, 'returns expected value' ); t.strictEqual( typeof out.content, 'string', 'returns expected value' ); t.strictEqual( out.releaseType, 'minor', 'returns expected value' ); - out = generate( '@stdlib/utils/noop', 'major' ); + out = generate( '@stdlib/utils/noop', { + 'releaseType': 'major' + }); t.strictEqual( isObject( out ), true, 'returns expected value' ); t.strictEqual( typeof out.content, 'string', 'returns expected value' ); t.strictEqual( out.releaseType, 'major', 'returns expected value' ); @@ -145,13 +153,28 @@ tape( 'the function generates a changelog for a new release', function test( t ) }); tape( 'the function generates a changelog for a new release (auto)', function test( t ) { - var out = generate( '@stdlib/utils/noop', 'auto' ); + var out = generate( '@stdlib/utils/noop', { + 'releaseType': 'auto' + }); t.strictEqual( isObject( out ), true, 'returns expected value' ); t.strictEqual( typeof out.content, 'string', 'returns expected value' ); - out = generate( '@stdlib/array/base/slice', 'auto' ); + out = generate( '@stdlib/array/base/slice', { + 'releaseType': 'auto' + }); t.strictEqual( isObject( out ), true, 'returns expected value' ); t.strictEqual( typeof out.content, 'string', 'returns expected value' ); t.end(); }); + +tape( 'the function generates a changelog for a specific author', function test( t ) { + var out = generate( '@stdlib/ndarray/base', { + 'flags': { + 'author': 'Athan Reines ' + } + }); + t.strictEqual( isObject( out ), true, 'returns expected value' ); + t.strictEqual( typeof out.content, 'string', 'returns expected value' ); + t.end(); +}); diff --git a/lib/node_modules/@stdlib/_tools/changelog/generate/test/test.validate.js b/lib/node_modules/@stdlib/_tools/changelog/generate/test/test.validate.js new file mode 100644 index 000000000000..d6eb7dc762ed --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/changelog/generate/test/test.validate.js @@ -0,0 +1,164 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var tape = require( 'tape' ); +var validate = require( './../lib/validate.js' ); + + +// TESTS // + +tape( 'main export is a function', function test( t ) { + t.ok( true, __filename ); + t.equal( typeof validate, 'function', 'main export is a function' ); + t.end(); +}); + +tape( 'the function returns an error if not provided an options object', function test( t ) { + var values; + var err; + var i; + + values = [ + '5', + 5, + NaN, + true, + null, + void 0, + [], + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + err = validate( {}, values[ i ] ); + t.ok( err instanceof TypeError, 'returns a type error when provided ' + values[ i ] ); + } + t.end(); +}); + +tape( 'the function returns an error if provided a `releaseType` option which is not a string', function test( t ) { + var values; + var err; + var i; + + values = [ + true, + 5, + NaN, + null, + void 0, + {}, + [], + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'releaseType': values[ i ] + }); + t.ok( err instanceof TypeError, 'returns a type error when provided ' + values[ i ] ); + } + t.end(); +}); + +tape( 'the function returns an error if provided a `format` option which is not a recognized format option', function test( t ) { + var values; + var err; + var i; + + values = [ + true, + 5, + NaN, + null, + void 0, + {}, + [], + function noop() {}, + 'abc' + ]; + + for ( i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'format': values[ i ] + }); + t.ok( err instanceof TypeError, 'returns a type error when provided ' + values[ i ] ); + } + t.end(); +}); + +tape( 'the function returns an error if provided a `flags` option which is not an object', function test( t ) { + var values; + var err; + var i; + + values = [ + true, + 5, + NaN, + null, + void 0, + 'abc', + [], + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'flags': values[ i ] + }); + t.ok( err instanceof TypeError, 'returns a type error when provided ' + values[ i ] ); + } + t.end(); +}); + +tape( 'the function returns `null` if all options are valid', function test( t ) { + var opts; + var err; + var obj; + + opts = { + 'releaseType': 'auto', + 'format': 'grouped' + }; + obj = {}; + err = validate( obj, opts ); + t.equal( err, null, 'returns null' ); + t.equal( obj.copy, opts.copy, 'sets copy option' ); + t.end(); +}); + +tape( 'the function ignores unrecognized options', function test( t ) { + var opts; + var err; + var obj; + + opts = { + 'beep': 'boop', + 'a': 'b' + }; + obj = {}; + err = validate( obj, opts ); + t.equal( err, null, 'returns null' ); + t.deepEqual( obj, {}, 'does not set any properties' ); + t.end(); +}); diff --git a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/README.md b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/README.md index 9a6ea4f2dd0b..e114ae1d8d03 100644 --- a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/README.md +++ b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/README.md @@ -44,6 +44,7 @@ The function accepts the following `options`: - **dir**: directory for which to parse commits. May be either an absolute path or a path relative to the current working directory. Default: current working directory. - **issueURL**: issue URL. Default: `https://github.com/stdlib-js/stdlib/issues/`. - **prURL**: pull request URL. Default: `https://github.com/stdlib-js/stdlib/pull/`. +- **flags**: options passed to `git log` when retrieving commit messages. @@ -62,7 +63,11 @@ The function accepts the following `options`: ```javascript var parseCommits = require( '@stdlib/_tools/changelog/parse-commits' ); -var commits = parseCommits(); +var commits = parseCommits({ + 'flags': { + 'since': 'last year' + } +}); // returns [...] commits = parseCommits({ diff --git a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/examples/index.js b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/examples/index.js index 838b1b835887..ca0831204faf 100644 --- a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/examples/index.js +++ b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/examples/index.js @@ -20,7 +20,11 @@ var parseCommits = require( './../lib' ); -var commits = parseCommits(); +var commits = parseCommits({ + 'flags': { + 'since': 'last year' + } +}); console.log( commits ); // => [...] diff --git a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/commits.js b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/commits.js index 7ae85551f7fd..feed0824bcd5 100644 --- a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/commits.js +++ b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/commits.js @@ -26,6 +26,7 @@ var spawn = require( 'child_process' ).spawnSync; // eslint-disable-line node/no var logger = require( 'debug' ); var parse = require( 'yaml' ).parse; var substringAfter = require( '@stdlib/string/substring-after' ); +var objectKeys = require( '@stdlib/utils/keys' ); var replace = require( '@stdlib/string/replace' ); var trim = require( '@stdlib/string/trim' ); var filterFiles = require( './filter_files.js' ); @@ -76,13 +77,15 @@ function extractYAMLBlock( str ) { * * @private * @param {string} dir - directory for which to extract commit details +* @param {Object} flags - `git log` options * @returns {Array} array of commit details * * @example * var commits = extractCommits( '.' ); * // returns [...] */ -function extractCommits( dir ) { +function extractCommits( dir, flags ) { + var flagNames; var commits; var author; var parsed; @@ -110,6 +113,12 @@ function extractCommits( dir ) { cmd = join( __dirname, '..', 'scripts', 'commits.sh' ); commits = []; args = [ resolve( dir ) ]; + if ( flags ) { + flagNames = objectKeys( flags ); + for ( i = 0; i < flagNames.length; i++ ) { + args.push( '--'+flagNames[ i ]+'='+flags[ flagNames[ i ] ] ); + } + } try { out = spawn( cmd, args, opts ); } catch ( err ) { @@ -117,6 +126,7 @@ function extractCommits( dir ) { return commits; } out = trim( out.stdout.toString() ); + lines = out.split( GIT_COMMIT_SEP ); for ( i = 0; i < lines.length; i++ ) { if ( !lines[ i ] ) { diff --git a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/defaults.js b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/defaults.js index 0783ec1d2621..b95ab82a62a4 100644 --- a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/defaults.js +++ b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/defaults.js @@ -29,7 +29,8 @@ function defaults() { return { 'issueURL': 'https://github.com/stdlib-js/stdlib/issues/', - 'prURL': 'https://github.com/stdlib-js/stdlib/pull/' + 'prURL': 'https://github.com/stdlib-js/stdlib/pull/', + 'flags': {} }; } diff --git a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/main.js b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/main.js index 8a2290d476de..113a0e7ebea4 100644 --- a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/main.js +++ b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/main.js @@ -45,12 +45,14 @@ var debug = logger( 'changelog:parse-commits' ); * @param {string} [options.dir] - root directory * @param {string} [options.issueURL] - issue URL * @param {string} [options.prURL] - PR URL +* @param {Object} [options.flags] - `git log` options * @throws {TypeError} options argument must be an object * @throws {TypeError} must provide valid options * @returns {Array} array of conventional changelog formatted commit message objects * * @example * var resolve = require( 'path' ).resolve; +* * var out = parseCommits({ * 'dir': resolve( __dirname, '..', '..' ) * }); @@ -79,7 +81,7 @@ function parseCommits( options ) { } else { dir = cwd(); } - commits = extractCommits( dir ); + commits = extractCommits( dir, opts.flags ); out = []; for ( i = 0; i < commits.length; i++ ) { msg = commits[ i ].message; diff --git a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/validate.js b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/validate.js index e0ac9d12c673..2319abc96f6c 100644 --- a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/validate.js +++ b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/lib/validate.js @@ -22,6 +22,7 @@ var isObject = require( '@stdlib/assert/is-plain-object' ); var isString = require( '@stdlib/assert/is-string' ).isPrimitive; +var isPlainObject = require( '@stdlib/assert/is-plain-object' ); var hasOwnProp = require( '@stdlib/assert/has-own-property' ); var format = require( '@stdlib/string/format' ); @@ -37,6 +38,7 @@ var format = require( '@stdlib/string/format' ); * @param {string} [options.dir] - root directory * @param {string} [options.issueURL] - issue URL * @param {string} [options.prURL] - PR URL +* @param {Options} [options.flags] - `git log` options * @returns {(null|Error)} null or an error */ function validate( opts, options ) { @@ -61,6 +63,12 @@ function validate( opts, options ) { return new TypeError( format( 'invalid option. `%s` option must be a string. Option: `%s`.', 'prURL', opts.prURL ) ); } } + if ( hasOwnProp( options, 'flags' ) ) { + opts.flags = options.flags; + if ( !isPlainObject( opts.flags ) ) { + return new TypeError( format( 'invalid option. `%s` option must be an object. Option: `%s`.', 'flags', opts.flags ) ); + } + } return null; } diff --git a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/scripts/commits.sh b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/scripts/commits.sh index ec2094057b68..fae7c888b62c 100755 --- a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/scripts/commits.sh +++ b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/scripts/commits.sh @@ -35,7 +35,7 @@ if [ -z "$1" ]; then fi if [ -z "$GIT_COMMIT_SEP" ]; then - GIT_COMMIT_SEP="^---^"; # Default separator + GIT_COMMIT_SEP="^---^"; # Default separator fi -git log --name-only --no-merges --notes --pretty=format:"%H|%ad|%aN <%aE>|%B|%N|" "$1" | awk -v sep="$GIT_COMMIT_SEP" '/^$/{p=1;next} /^[0-9a-f]{40}\|/{if (p==1) print sep; p=0} {print}'; +git log "${@:2}" --name-only --no-merges --notes --pretty=format:"%H|%ad|%aN <%aE>|%B|%N|" "$1" | awk -v sep="$GIT_COMMIT_SEP" '/^$/{p=1;next} /^[0-9a-f]{40}\|/{if (p==1) print sep; p=0} {print}'; diff --git a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/test/test.validate.js b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/test/test.validate.js index edec79b98f5c..bf4c5366e594 100644 --- a/lib/node_modules/@stdlib/_tools/changelog/parse-commits/test/test.validate.js +++ b/lib/node_modules/@stdlib/_tools/changelog/parse-commits/test/test.validate.js @@ -138,6 +138,31 @@ tape( 'if provided a `prURL` option which is not a `string`, the function return t.end(); }); +tape( 'the function returns an error if provided a `flags` option which is not an object', function test( t ) { + var values; + var err; + var i; + + values = [ + true, + 5, + NaN, + null, + void 0, + 'abc', + [], + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'flags': values[ i ] + }); + t.ok( err instanceof TypeError, 'returns a type error when provided ' + values[ i ] ); + } + t.end(); +}); + tape( 'the function returns `null` if all options are valid', function test( t ) { var opts; var obj; diff --git a/lib/node_modules/@stdlib/_tools/scripts/publish_packages.js b/lib/node_modules/@stdlib/_tools/scripts/publish_packages.js index 61e1e1a6b2e9..bf8e0410d414 100644 --- a/lib/node_modules/@stdlib/_tools/scripts/publish_packages.js +++ b/lib/node_modules/@stdlib/_tools/scripts/publish_packages.js @@ -685,7 +685,9 @@ function publish( pkg, clbk ) { return invokeCallback( null, 'skipped' ); } - changelog = generateChangelog( '@stdlib/'+pkg, flags[ 'release-type' ] ); + changelog = generateChangelog( '@stdlib/'+pkg, { + 'releaseType': flags[ 'release-type' ] + }); releaseType = changelog.releaseType; mainJSON = readJSON( join( mainDir, 'package.json' ) );