diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test-and-release.yml index 52c7fb5..ae61da3 100644 --- a/.github/workflows/test-and-release.yml +++ b/.github/workflows/test-and-release.yml @@ -25,10 +25,12 @@ jobs: - name: Install Dependencies run: | npm install --no-progress - - name: Run tests + - name: Check build is up to date run: | - npm config set script-shell bash - npm run test:ci + npm run build + git diff --exit-code || (echo "::error::Build artifacts not committed. Run 'npm run build' and commit the changes." && exit 1) + - name: Run tests + run: npm test release: name: Release permissions: diff --git a/.gitignore b/.gitignore index d5f19d8..89521e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules package-lock.json +*.tsbuildinfo diff --git a/README.md b/README.md index 45ab858..edd80e7 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,13 @@ **Turn a `git log` into a stream of commit objects** -[![npm](https://nodei.co/npm/commit-stream.png?downloads=true&downloadRank=true)](https://nodei.co/npm/commit-stream/) -[![npm](https://nodei.co/npm-dl/commit-stream.png?months=6&height=3)](https://nodei.co/npm/commit-stream/) +[![NPM](https://nodei.co/npm/commit-stream.svg?style=flat&data=n,v&color=blue)](https://nodei.co/npm/commit-stream/) ## API **`commitStream([defaultGithubUser, defaultGithubProject])`** -Returns an object stream that you can pipe a stream of newlines from the output of `git log` to. The output of the stream is a series of objects, each representing a commit from the log.` +Returns an object stream that you can pipe a stream of newlines from the output of `git log` to. The output of the stream is a series of objects, each representing a commit from the log. Intended to be used on a standard log format (not a simplified one using one of the simplifying `--format` options) but it will also process change stats if you `git log --stat`. @@ -18,18 +17,20 @@ Typical usage: ```js import { spawn } from 'node:child_process' import { createInterface } from 'node:readline' -import { Readable } from 'node:stream' -import listStream from 'list-stream' - -const { stdout } = spawn('bash', ['-c', 'git log']) - -Readable.from(createInterface({ input: stdout, crlfDelay: Infinity })) // break up by newline characters - .pipe(commitStream('rvagg', 'commit-stream')) - .pipe(listStream.obj(onCommitList)) - -function onCommitList (err, list) { - // list is an array of objects -} +import { pipeline } from 'node:stream/promises' +import commitStream from 'commit-stream' + +const { stdout } = spawn('git', ['log']) +const commits = [] + +await pipeline( + createInterface({ input: stdout, crlfDelay: Infinity }), // break up by newline characters + commitStream('rvagg', 'commit-stream'), + async (source) => { + for await (const commit of source) commits.push(commit) + } +) +// commits is an array of objects ``` Commit object properties: diff --git a/commit-stream.js b/commit-stream.js index b793898..8b7c07e 100644 --- a/commit-stream.js +++ b/commit-stream.js @@ -1,62 +1,101 @@ import { Transform } from 'node:stream' import { stripVTControlCharacters } from 'node:util' +/** + * @typedef {Object} Person + * @property {string} name + * @property {string} email + */ + +/** + * @typedef {Object} Changes + * @property {number} files + * @property {number} insertions + * @property {number} deletions + */ + +/** + * @typedef {Object} Commit + * @property {string} sha + * @property {Person} [author] + * @property {Person[]} [authors] + * @property {string} [authorDate] + * @property {string} [commitDate] + * @property {string} [summary] + * @property {string[]} [description] + * @property {Person[]} [reviewers] + * @property {Changes} [changes] + * @property {string} [prUrl] + * @property {string} [ghUser] + * @property {string} [ghProject] + * @property {number} [ghIssue] + * @property {string} [cveId] + */ + +/** + * Create an object stream that turns lines of `git log` output into commit objects. + * + * @param {string} [ghUser] - GitHub user used to expand short `#XX` PR references + * @param {string} [ghProject] - GitHub project used to expand short `#XX` PR references + * @returns {Transform} + */ export default function commitStream (ghUser, ghProject) { + /** @type {Commit | undefined} */ let commit - return new Transform({ objectMode: true, transform: onLine, flush: onEnd }) + return new Transform({ + objectMode: true, + transform (line, _, callback) { + emit(this, addLine(line)) + callback() + }, + flush (callback) { + emit(this, commit) + callback() + } + }) + /** + * @param {string} line + * @returns {Commit | undefined} the previous commit, when `line` starts a new one + */ function addLine (line) { line = stripVTControlCharacters(line) if (line.length === 0) return - let old, m - if (/^commit \w+$/.test(line)) { - old = commit + const old = commit commit = { sha: line.split(' ')[1] } - } else if ((m = line.match(/^CommitDate:\s+(.*)$/)) !== null) { - if (commit === undefined || commit === null) { - throw new Error('Something unexpected occurred') - } + return old + } + + let m + + if (!commit) { + throw new Error('Something unexpected occurred') + } + + if ((m = line.match(/^CommitDate:\s+(.*)$/)) !== null) { commit.commitDate = m[1].trim() } else if ( (m = line.match( /^\s*(?:Author|Co[- ]?authored[- ]?by):?\s*([^<]+) <([^>]+)>\s*$/i )) !== null ) { - if (commit === undefined || commit === null) { - throw new Error('Something unexpected occurred') - } - if (commit.authors === undefined || commit.authors === null) { - commit.authors = [] - } - commit.authors.push({ name: m[1], email: m[2] }) + (commit.authors ??= []).push({ name: m[1], email: m[2] }) } else if ((m = line.match(/^(?:AuthorDate|Date):\s+(.*)$/)) !== null) { - if (commit === undefined || commit === null) { - throw new Error('Something unexpected occurred') - } commit.authorDate = m[1].trim() } else if ( (m = line.match(/^\s+Reviewed[- ]?By:?\s*([^<]+) <([^>]+)>\s*$/i)) !== null ) { - if (commit.reviewers === undefined || commit.reviewers === null) { - commit.reviewers = [] - } - commit.reviewers.push({ name: m[1], email: m[2] }) + (commit.reviewers ??= []).push({ name: m[1], email: m[2] }) } else if ((m = line.match(/\bCVE-ID:\s+(CVE-\d{4}-\d{4,})\b/)) !== null) { commit.cveId = m[1] } else if ((m = line.match(/^\s+PR(?:[- ]?URL)?:?\s*(.+)\s*$/) || line.match(/\(#(\d+)\)$/)) !== null) { commit.prUrl = m[1] - if ( - typeof ghUser === 'string' && - ghUser.length !== 0 && - typeof ghProject === 'string' && - ghProject.length !== 0 && - (m = commit.prUrl.match(/^\s*#?(\d+)\s*$/)) !== null - ) { + if (ghUser && ghProject && (m = commit.prUrl.match(/^\s*#?(\d+)\s*$/)) !== null) { commit.prUrl = `https://github.com/${ghUser}/${ghProject}/pull/${m[1]}` } if ( @@ -64,7 +103,7 @@ export default function commitStream (ghUser, ghProject) { /^(https?:\/\/.+\/([^/]+)\/([^/]+))\/\w+\/(\d+)(\/)?$/i )) !== null ) { - commit.ghIssue = parseInt(m[4]) + commit.ghIssue = Number(m[4]) commit.ghUser = m[2] commit.ghProject = m[3] } @@ -72,13 +111,10 @@ export default function commitStream (ghUser, ghProject) { commit.summary = m[1] } } else if (/^ {4}/.test(line) && (line = line.trim()).length !== 0) { - if (commit.summary === undefined || commit.summary === null) { + if (commit.summary === undefined) { commit.summary = line } else { - if (commit.description === undefined || commit.description === null) { - commit.description = [] - } - commit.description.push(line) + (commit.description ??= []).push(line) } } else if ( /^ [^ ]/.test(line) && @@ -88,34 +124,23 @@ export default function commitStream (ghUser, ghProject) { )) !== null ) { commit.changes = { - files: parseInt(m[1]), - insertions: parseInt(m[2] ?? 0), - deletions: parseInt(m[3] ?? 0) + files: Number(m[1]), + insertions: Number(m[2] ?? 0), + deletions: Number(m[3] ?? 0) } } - - return old - } - - function onLine (line, _, callback) { - const commit = addLine(line) - - if (commit?.authors?.length > 0) { - commit.author = commit.authors[0] - - this.push(commit) - } - - callback() } +} - function onEnd (callback) { - if (commit?.authors?.length > 0) { - commit.author = commit.authors[0] - - this.push(commit) - } - - callback() +/** + * Push a completed commit, skipping any without an author. + * + * @param {Transform} stream + * @param {Commit | undefined} commit + */ +function emit (stream, commit) { + if (commit?.authors?.length) { + commit.author = commit.authors[0] + stream.push(commit) } } diff --git a/package.json b/package.json index 2c3fdee..9b78abe 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,21 @@ "version": "2.2.6", "description": "Turn a `git log` into a stream of commit objects", "main": "commit-stream.js", + "types": "types/commit-stream.d.ts", + "exports": { + ".": { + "types": "./types/commit-stream.d.ts", + "default": "./commit-stream.js" + } + }, "type": "module", "scripts": { - "build": "true", - "test:ci": "npm run test", - "test": "node --test test.js" + "lint": "standard", + "build": "npm run build:types", + "build:types": "tsc --build", + "prepublishOnly": "npm run build", + "test:unit": "node --test test.js", + "test": "npm run lint && npm run test:unit" }, "repository": { "type": "git", @@ -21,10 +31,12 @@ "homepage": "https://github.com/nodejs/commit-stream", "devDependencies": { "@semantic-release/changelog": "^7.0.0", - "@semantic-release/git": "^11.0.0", + "@semantic-release/git": "^11.0.1", + "@types/node": "^24.19.2", "conventional-changelog-conventionalcommits": "^9.3.1", - "list-stream": "^2.1.0", - "semantic-release": "^25.0.3" + "semantic-release": "^25.0.9", + "standard": "^17.1.2", + "typescript": "^7.0.2" }, "release": { "branches": [ diff --git a/test.js b/test.js index 5ada4e4..0ffa43f 100644 --- a/test.js +++ b/test.js @@ -1,264 +1,225 @@ -import commitStream from './commit-stream.js' -import { spawn, exec } from 'node:child_process' import assert from 'node:assert/strict' -import nodeTest from 'node:test' +import { execFile, spawn } from 'node:child_process' +import { once } from 'node:events' import { createInterface } from 'node:readline' -import listStream from 'list-stream' -import { pipeline, Readable } from 'node:stream' +import { pipeline } from 'node:stream/promises' +import { test } from 'node:test' +import { promisify } from 'node:util' +import commitStream from './commit-stream.js' -function test (name, fn) { - return nodeTest(name, (_, done) => fn({ - ...assert, - end: done, - error: assert.ifError - })) +async function toCommits (lines, user, repo) { + const commits = [] + await pipeline( + lines, + commitStream(user, repo), + async (source) => { + for await (const commit of source) commits.push(commit) + } + ) + return commits } -function gitToList (t, gitCmd, user, repo, callback) { - if (typeof user === 'function') { - callback = user - user = undefined - repo = undefined - } - const child = spawn('bash', ['-c', gitCmd]) - let pending = 2 - const done = () => { - if (--pending === 0) setImmediate(t.end.bind(t)) - } +async function gitToList (args, user, repo) { + const child = spawn('git', args) let stderr = '' child.stderr.setEncoding('utf8').on('data', (chunk) => { stderr += chunk }) - child.on('error', (err) => { - t.error(err, 'error from child') - }) - child.on('close', (code) => { - t.strictEqual(stderr, '') - t.strictEqual(code, 0, 'exit code zero') - done() - }) - pipeline( - Readable.from(createInterface({ input: child.stdout, crlfDelay: Infinity })), - commitStream(user, repo), - listStream.obj((err, list) => { - callback(err, list) - done() - }), - () => {} - ) -} -test('git is runnable', (t) => { - exec('git version', (error, stdout, stderr) => { - if (error) { - t.fail(`command failed: ${error}`) - } else if (stderr) { - t.fail(`command output to stderr: ${stderr}`) - } else { - t.match(stdout, /^git version 2/) - } - t.end() - }) -}) + const [[code], list] = await Promise.all([ + once(child, 'close'), + toCommits(createInterface({ input: child.stdout, crlfDelay: Infinity }), user, repo) + ]) -test('current plain commit log', (t) => { - gitToList(t, 'git log', (err, list) => { - t.error(err, 'no error') + assert.strictEqual(stderr, '') + assert.strictEqual(code, 0, 'exit code zero') + return list +} - t.ok(list?.length > 0, 'got a list') - if (!list || !list.length) { - return - } +test('git is runnable', async () => { + const { stdout, stderr } = await promisify(execFile)('git', ['version']) + assert.strictEqual(stderr, '') + assert.match(stdout, /^git version 2/) +}) - t.deepEqual( - list[list.length - 9], - { - author: { email: 'ralphtheninja@riseup.net', name: 'Lars-Magnus Skog' }, - authors: [ - { email: 'ralphtheninja@riseup.net', name: 'Lars-Magnus Skog' } - ], - authorDate: 'Tue Feb 9 15:46:46 2016 +0100', - description: [ - 'Fixes: https://github.com/rvagg/changelog-maker/issues/35' - ], - ghIssue: 1, - ghProject: 'commit-stream', - ghUser: 'rvagg', - prUrl: 'https://github.com/rvagg/commit-stream/pull/1', - sha: '1bcfd072fd74399808fbcd5cf31ce5342dd6d70c', - summary: 'process: this should not match PR-URL', - reviewers: [{ email: 'rod@vagg.org', name: 'Rod Vagg' }] - }, - 'got correct ninth commit' - ) +test('current plain commit log', async () => { + const list = await gitToList(['log']) + assert.ok(list.length > 0, 'got a list') + assert.deepStrictEqual( + list[list.length - 9], + { + author: { email: 'ralphtheninja@riseup.net', name: 'Lars-Magnus Skog' }, + authors: [ + { email: 'ralphtheninja@riseup.net', name: 'Lars-Magnus Skog' } + ], + authorDate: 'Tue Feb 9 15:46:46 2016 +0100', + description: [ + 'Fixes: https://github.com/rvagg/changelog-maker/issues/35' + ], + ghIssue: 1, + ghProject: 'commit-stream', + ghUser: 'rvagg', + prUrl: 'https://github.com/rvagg/commit-stream/pull/1', + sha: '1bcfd072fd74399808fbcd5cf31ce5342dd6d70c', + summary: 'process: this should not match PR-URL', + reviewers: [{ email: 'rod@vagg.org', name: 'Rod Vagg' }] + }, + 'got correct ninth commit' + ) - t.deepEqual( - list[list.length - 4], - { - author: { email: 'rod@vagg.org', name: 'Rod Vagg' }, - authors: [{ email: 'rod@vagg.org', name: 'Rod Vagg' }], - authorDate: 'Fri Apr 17 11:16:51 2015 +1000', - sha: 'f92b93c3c7175b07f847dd45058b121cea6b3a20', - summary: 'deleted package.json line' - }, - 'got correct fourth commit' - ) + assert.deepStrictEqual( + list[list.length - 4], + { + author: { email: 'rod@vagg.org', name: 'Rod Vagg' }, + authors: [{ email: 'rod@vagg.org', name: 'Rod Vagg' }], + authorDate: 'Fri Apr 17 11:16:51 2015 +1000', + sha: 'f92b93c3c7175b07f847dd45058b121cea6b3a20', + summary: 'deleted package.json line' + }, + 'got correct fourth commit' + ) - t.deepEqual( - list[list.length - 3], - { - author: { email: 'rod@vagg.org', name: 'Rod Vagg' }, - authors: [{ email: 'rod@vagg.org', name: 'Rod Vagg' }], - authorDate: 'Fri Apr 17 11:13:06 2015 +1000', - description: ['comment', 'Reviewed-By: Nobody'], - sha: 'db34ce2af09a6a9fb70241d43965a2bc48b90b4c', - summary: 'squished package.json' - }, - 'got correct third commit' - ) + assert.deepStrictEqual( + list[list.length - 3], + { + author: { email: 'rod@vagg.org', name: 'Rod Vagg' }, + authors: [{ email: 'rod@vagg.org', name: 'Rod Vagg' }], + authorDate: 'Fri Apr 17 11:13:06 2015 +1000', + description: ['comment', 'Reviewed-By: Nobody'], + sha: 'db34ce2af09a6a9fb70241d43965a2bc48b90b4c', + summary: 'squished package.json' + }, + 'got correct third commit' + ) - t.deepEqual( - list[list.length - 2], - { - author: { email: 'rod@vagg.org', name: 'Rod Vagg' }, - authors: [{ email: 'rod@vagg.org', name: 'Rod Vagg' }], - authorDate: 'Fri Apr 17 10:52:16 2015 +1000', - description: [ - 'Some extra summary information here', - 'newline', - 'Fixes: https://github.com/rvagg/commit-stream/issues/0' - ], - ghIssue: 0, - ghProject: 'commit-stream', - ghUser: 'rvagg', - prUrl: 'https://github.com/rvagg/commit-stream/pulls/0', - reviewers: [{ email: 'roger@vagg.org', name: 'Roger Vagg' }], - sha: '6275758f597ae579202fbe4eccca1407f2c67ac1', - summary: 'added test.js' - }, - 'got correct second commit' - ) + assert.deepStrictEqual( + list[list.length - 2], + { + author: { email: 'rod@vagg.org', name: 'Rod Vagg' }, + authors: [{ email: 'rod@vagg.org', name: 'Rod Vagg' }], + authorDate: 'Fri Apr 17 10:52:16 2015 +1000', + description: [ + 'Some extra summary information here', + 'newline', + 'Fixes: https://github.com/rvagg/commit-stream/issues/0' + ], + ghIssue: 0, + ghProject: 'commit-stream', + ghUser: 'rvagg', + prUrl: 'https://github.com/rvagg/commit-stream/pulls/0', + reviewers: [{ email: 'roger@vagg.org', name: 'Roger Vagg' }], + sha: '6275758f597ae579202fbe4eccca1407f2c67ac1', + summary: 'added test.js' + }, + 'got correct second commit' + ) - t.deepEqual( - list[list.length - 1], - { - sha: 'd94841274e2979e7758413a0f48fa37560d0dde6', - authorDate: 'Thu Apr 16 20:49:21 2015 +1000', - author: { name: 'Rod Vagg', email: 'rod@vagg.org' }, - authors: [{ email: 'rod@vagg.org', name: 'Rod Vagg' }], - summary: 'make it so' - }, - 'got correct first commit' - ) + assert.deepStrictEqual( + list[list.length - 1], + { + sha: 'd94841274e2979e7758413a0f48fa37560d0dde6', + authorDate: 'Thu Apr 16 20:49:21 2015 +1000', + author: { name: 'Rod Vagg', email: 'rod@vagg.org' }, + authors: [{ email: 'rod@vagg.org', name: 'Rod Vagg' }], + summary: 'make it so' + }, + 'got correct first commit' + ) - t.deepEqual( - list[list.length - 16], - { - sha: list[list.length - 16]?.sha, // unknown at time of writing :) - authorDate: 'Tue Jun 12 23:41:35 2018 +0200', - author: { name: 'Anna Henningsen', email: 'anna@addaleax.net' }, - authors: [ - { name: 'Anna Henningsen', email: 'anna@addaleax.net' }, - { name: 'nobody', email: 'nobody@nowhere' } - ], - summary: 'add support for co-authored-by' - }, - 'got correct co-authored-by commit' - ) - }) + assert.deepStrictEqual( + list[list.length - 16], + { + sha: list[list.length - 16]?.sha, // unknown at time of writing :) + authorDate: 'Tue Jun 12 23:41:35 2018 +0200', + author: { name: 'Anna Henningsen', email: 'anna@addaleax.net' }, + authors: [ + { name: 'Anna Henningsen', email: 'anna@addaleax.net' }, + { name: 'nobody', email: 'nobody@nowhere' } + ], + summary: 'add support for co-authored-by' + }, + 'got correct co-authored-by commit' + ) }) -test('current commit log with changes', (t) => { - gitToList(t, 'git log --stat', (err, list) => { - t.error(err, 'no errors') - - t.ok(list?.length > 0, 'got a list') - if (!list || !list.length) { - return - } - - t.deepEqual( - list[list.length - 4]?.changes, - { - files: 1, - insertions: 0, - deletions: 1 - }, - 'got correct second commit changes' - ) +test('current commit log with changes', async () => { + const list = await gitToList(['log', '--stat']) + assert.ok(list.length > 0, 'got a list') + assert.deepStrictEqual( + list[list.length - 4]?.changes, + { + files: 1, + insertions: 0, + deletions: 1 + }, + 'got correct second commit changes' + ) - t.deepEqual( - list[list.length - 3]?.changes, - { - files: 1, - insertions: 1, - deletions: 28 - }, - 'got correct second commit changes' - ) + assert.deepStrictEqual( + list[list.length - 3]?.changes, + { + files: 1, + insertions: 1, + deletions: 28 + }, + 'got correct second commit changes' + ) - t.deepEqual( - list[list.length - 2]?.changes, - { - files: 1, - insertions: 49, - deletions: 0 - }, - 'got correct second commit changes' - ) + assert.deepStrictEqual( + list[list.length - 2]?.changes, + { + files: 1, + insertions: 49, + deletions: 0 + }, + 'got correct second commit changes' + ) - t.deepEqual( - list[list.length - 1]?.changes, - { - files: 1, - insertions: 28, - deletions: 0 - }, - 'got correct first commit changes' - ) - }) + assert.deepStrictEqual( + list[list.length - 1]?.changes, + { + files: 1, + insertions: 28, + deletions: 0 + }, + 'got correct first commit changes' + ) }) -test('current commit log with ghUser and ghRepo passed', function (t) { - gitToList(t, 'git log', 'rvagg', 'commit-stream', function (err, list) { - t.error(err, 'no error') - - t.ok(list && list.length > 1, 'got a list') - - t.deepEqual(list[list.length - 18], { - sha: 'b23208796d7e3fd08b36f6106aa7f027aa827137', - authors: [ - { name: 'Rich Trott', email: 'rtrott@gmail.com' } - ], - authorDate: 'Mon Oct 11 19:29:18 2021 -0700', - prUrl: 'https://github.com/rvagg/commit-stream/pull/5', - ghIssue: 5, - ghUser: 'rvagg', - ghProject: 'commit-stream', - author: { name: 'Rich Trott', email: 'rtrott@gmail.com' }, - summary: 'chore: update strip-ansi to 6.x' - }, 'got correct pr url for green-button merge') - }) +test('current commit log with ghUser and ghRepo passed', async () => { + const list = await gitToList(['log'], 'rvagg', 'commit-stream') + assert.ok(list.length > 1, 'got a list') + + assert.deepStrictEqual(list[list.length - 18], { + sha: 'b23208796d7e3fd08b36f6106aa7f027aa827137', + authors: [ + { name: 'Rich Trott', email: 'rtrott@gmail.com' } + ], + authorDate: 'Mon Oct 11 19:29:18 2021 -0700', + prUrl: 'https://github.com/rvagg/commit-stream/pull/5', + ghIssue: 5, + ghUser: 'rvagg', + ghProject: 'commit-stream', + author: { name: 'Rich Trott', email: 'rtrott@gmail.com' }, + summary: 'chore: update strip-ansi to 6.x' + }, 'got correct pr url for green-button merge') }) -test('cve id', function (t) { - gitToList(t, 'git log', 'rvagg', 'commit-stream', function (err, list) { - t.error(err, 'no error') - - t.ok(list && list.length > 1, 'got a list') - - t.deepEqual(list[list.length - 30], { - sha: '6c82ccd24e44167433126ba2bf4e95c8c4f41297', - authors: [ - { name: 'RafaelGSS', email: 'rafael.nunu@hotmail.com' } - ], - authorDate: 'Fri Nov 22 00:19:29 2024 -0300', - author: { name: 'RafaelGSS', email: 'rafael.nunu@hotmail.com' }, - summary: 'feat: add cveId support', - cveId: 'CVE-2024-12345' - }, 'got correct pr url for green-button merge') - }) +test('cve id', async () => { + const list = await gitToList(['log'], 'rvagg', 'commit-stream') + assert.ok(list.length > 1, 'got a list') + + assert.deepStrictEqual(list[list.length - 30], { + sha: '6c82ccd24e44167433126ba2bf4e95c8c4f41297', + authors: [ + { name: 'RafaelGSS', email: 'rafael.nunu@hotmail.com' } + ], + authorDate: 'Fri Nov 22 00:19:29 2024 -0300', + author: { name: 'RafaelGSS', email: 'rafael.nunu@hotmail.com' }, + summary: 'feat: add cveId support', + cveId: 'CVE-2024-12345' + }, 'got correct pr url for green-button merge') }) -test('cve id with 4 or more sequence digits', function (t) { +test('cve id with 4 or more sequence digits', async () => { const log = (cveId) => [ 'commit 0000000000000000000000000000000000000000', 'Author: RafaelGSS ', @@ -269,15 +230,6 @@ test('cve id with 4 or more sequence digits', function (t) { ` CVE-ID: ${cveId}` ] const ids = ['CVE-2026-9358', 'CVE-2024-12345', 'CVE-2017-1000117'] - const stream = commitStream() - const commits = [] - stream.on('data', (commit) => commits.push(commit)) - stream.on('end', () => { - t.deepEqual(commits.map((c) => c.cveId), ids) - t.end() - }) - for (const id of ids) { - for (const line of log(id)) stream.write(line) - } - stream.end() + const commits = await toCommits(ids.flatMap(log)) + assert.deepStrictEqual(commits.map((c) => c.cveId), ids) }) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..98dce79 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": false, + "noImplicitAny": true, + "noImplicitThis": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strictFunctionTypes": false, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "strictBindCallApply": true, + "strict": true, + "alwaysStrict": true, + "esModuleInterop": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "declaration": true, + "declarationMap": true, + "outDir": "types", + "types": ["node"], + "skipLibCheck": true, + "stripInternal": true, + "resolveJsonModule": true, + "emitDeclarationOnly": true, + "paths": { + "commit-stream": ["./commit-stream.js"] + } + }, + "include": ["commit-stream.js"], + "exclude": ["node_modules"], + "compileOnSave": false +} diff --git a/types/commit-stream.d.ts b/types/commit-stream.d.ts new file mode 100644 index 0000000..9291359 --- /dev/null +++ b/types/commit-stream.d.ts @@ -0,0 +1,63 @@ +import { Transform } from 'node:stream'; +export type Person = { + name: string; + email: string; +}; +export type Changes = { + files: number; + insertions: number; + deletions: number; +}; +export type Commit = { + sha: string; + author?: Person; + authors?: Person[]; + authorDate?: string; + commitDate?: string; + summary?: string; + description?: string[]; + reviewers?: Person[]; + changes?: Changes; + prUrl?: string; + ghUser?: string; + ghProject?: string; + ghIssue?: number; + cveId?: string; +}; +/** + * @typedef {Object} Person + * @property {string} name + * @property {string} email + */ +/** + * @typedef {Object} Changes + * @property {number} files + * @property {number} insertions + * @property {number} deletions + */ +/** + * @typedef {Object} Commit + * @property {string} sha + * @property {Person} [author] + * @property {Person[]} [authors] + * @property {string} [authorDate] + * @property {string} [commitDate] + * @property {string} [summary] + * @property {string[]} [description] + * @property {Person[]} [reviewers] + * @property {Changes} [changes] + * @property {string} [prUrl] + * @property {string} [ghUser] + * @property {string} [ghProject] + * @property {number} [ghIssue] + * @property {string} [cveId] + */ +/** + * Create an object stream that turns lines of `git log` output into commit objects. + * + * @param {string} [ghUser] - GitHub user used to expand short `#XX` PR references + * @param {string} [ghProject] - GitHub project used to expand short `#XX` PR references + * @returns {Transform} + */ +export default function commitStream(ghUser?: string, ghProject?: string): Transform; +//# sourceMappingURL=commit-stream.d.ts.map \ No newline at end of file diff --git a/types/commit-stream.d.ts.map b/types/commit-stream.d.ts.map new file mode 100644 index 0000000..f5cbe0b --- /dev/null +++ b/types/commit-stream.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"commit-stream.d.ts","sourceRoot":"","sources":["../commit-stream.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAIpC,YAAkB,MAAM,GACxB;IAAmB,IAAI,EAAZ,MAAM,CACjB;IAAmB,KAAK,EAAb,MAAM,CACnB;CAAA,CAAA;AAGE,YAAkB,OAAO,GACzB;IAAmB,KAAK,EAAb,MAAM,CACjB;IAAmB,UAAU,EAAlB,MAAM,CACjB;IAAmB,SAAS,EAAjB,MAAM,CACnB;CAAA,CAAA;AAGE,YAAkB,MAAM,GACxB;IAAmB,GAAG,EAAX,MAAM,CACjB;IAAoB,MAAM,AAA1B,CACA,EADW,MAAM,CACjB;IAAsB,OAAO,AAA7B,CACA,EADW,MAAM,EAAE,CACnB;IAAoB,UAAU,AAA9B,CACA,EADW,MAAM,CACjB;IAAoB,UAAU,AAA9B,CACA,EADW,MAAM,CACjB;IAAoB,OAAO,AAA3B,CACA,EADW,MAAM,CACjB;IAAsB,WAAW,AAAjC,CACA,EADW,MAAM,EAAE,CACnB;IAAsB,SAAS,AAA/B,CACA,EADW,MAAM,EAAE,CACnB;IAAqB,OAAO,AAA5B,CACA,EADW,OAAO,CAClB;IAAoB,KAAK,AAAzB,CACA,EADW,MAAM,CACjB;IAAoB,MAAM,AAA1B,CACA,EADW,MAAM,CACjB;IAAoB,SAAS,AAA7B,CACA,EADW,MAAM,CACjB;IAAoB,OAAO,AAA3B,CACA,EADW,MAAM,CACjB;IAAoB,KAAK,AAAzB,CAA0B,EAAf,MAAM,CAAS;CAAA,CAAA;AA5B7B;;;;GAIG;AAEH;;;;;GAKG;AAEH;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAE,MAAM,AAJzC,CACA,EADQ,MAIiC,EAAE,SAAS,AAHpD,CACA,EADQ,MAG4C,GAF1C,SAAS,CA6FrB"} \ No newline at end of file