diff --git a/packages/git-mob-core/package.json b/packages/git-mob-core/package.json index d45ba5e7..3ad1b68b 100644 --- a/packages/git-mob-core/package.json +++ b/packages/git-mob-core/package.json @@ -8,7 +8,7 @@ "scripts": { "build": "rimraf dist && tsc --project tsconfig.test.json && bob -c=core", "pretest": "npm run build -- -t", - "test": "jest", + "test": "jest --coverage --verbose", "minifytest": "npm run build -- -m -t && npm run test", "prepack": "rimraf dist && tsc --project tsconfig.prod.json && bob -c=core -m", "checks": "npm run test && npm run lint", diff --git a/packages/git-mob-core/src/git-mob-api/author.ts b/packages/git-mob-core/src/git-mob-api/author.ts index f8d4af0b..3a14cf36 100644 --- a/packages/git-mob-core/src/git-mob-api/author.ts +++ b/packages/git-mob-core/src/git-mob-api/author.ts @@ -19,7 +19,7 @@ export class Author { } format() { - return `${this.trailer} ${this.toString()}`; + return `${this.trailer}: ${this.toString()}`; } toString() { diff --git a/packages/git-mob-core/src/git-mob-api/exec-command.ts b/packages/git-mob-core/src/git-mob-api/exec-command.ts index 7492c409..3568f0c2 100644 --- a/packages/git-mob-core/src/git-mob-api/exec-command.ts +++ b/packages/git-mob-core/src/git-mob-api/exec-command.ts @@ -38,6 +38,14 @@ export async function getAllConfig(key: string) { } } +export async function getRegexpConfig(key: string) { + try { + return await execCommand(`git config --get-regexp ${key}`); + } catch { + return undefined; + } +} + export async function setConfig(key: string, value: string) { try { await execCommand(`git config ${key} "${value}"`); diff --git a/packages/git-mob-core/src/git-mob-api/git-message/message-formatter.spec.ts b/packages/git-mob-core/src/git-mob-api/git-message/message-formatter.spec.ts index d441c0a4..fca9ae40 100644 --- a/packages/git-mob-core/src/git-mob-api/git-message/message-formatter.spec.ts +++ b/packages/git-mob-core/src/git-mob-api/git-message/message-formatter.spec.ts @@ -1,6 +1,6 @@ import { EOL } from 'node:os'; import { Author } from '../author'; -import { messageFormatter } from './message-formatter'; +import { AuthorTrailers, messageFormatter } from './message-formatter'; test('MessageFormatter: No authors to append to git message', () => { const txt = `git message`; @@ -28,6 +28,32 @@ test('MessageFormatter: Append co-authors to git message', () => { ); }); +test('MessageFormatter: Append mixed trailers to git message', () => { + const txt = `git message`; + const message = messageFormatter(txt, [ + new Author('jd', 'Jane Doe', 'jane@gitmob.com', AuthorTrailers.CoAuthorBy), + new Author( + 'fb', + 'Frances Bar', + 'frances-bar@gitmob.com', + AuthorTrailers.SignedOffBy + ), + new Author('ab', 'Alex Baz', 'alex-baz@gitmob.com', AuthorTrailers.ReviewedBy), + ]); + expect(message).toBe( + [ + txt, + EOL, + EOL, + 'Co-authored-by: Jane Doe ', + EOL, + 'Signed-off-by: Frances Bar ', + EOL, + 'Reviewed-by: Alex Baz ', + ].join('') + ); +}); + test('MessageFormatter: Replace co-author in the git message', () => { const firstLine = 'git message'; const txt = [ @@ -50,6 +76,37 @@ test('MessageFormatter: Replace co-author in the git message', () => { ); }); +test('MessageFormatter: Replace mixed trailers in the git message', () => { + const firstLine = 'git message'; + const txt = [ + firstLine, + EOL, + EOL, + 'Co-authored-by: Jane Doe ', + EOL, + 'Signed-off-by: Jane Doe ', + ].join(''); + const message = messageFormatter(txt, [ + new Author( + 'fb', + 'Frances Bar', + 'frances-bar@gitmob.com', + AuthorTrailers.SignedOffBy + ), + new Author('ab', 'Alex Baz', 'alex-baz@gitmob.com', AuthorTrailers.CoAuthorBy), + ]); + expect(message).toBe( + [ + firstLine, + EOL, + EOL, + 'Signed-off-by: Frances Bar ', + EOL, + 'Co-authored-by: Alex Baz ', + ].join('') + ); +}); + test('MessageFormatter: Replace co-author in the git message with no line break', () => { const firstLine = 'git message'; const txt = [firstLine, 'Co-authored-by: Jane Doe '].join(''); diff --git a/packages/git-mob-core/src/git-mob-api/git-message/message-formatter.ts b/packages/git-mob-core/src/git-mob-api/git-message/message-formatter.ts index dd9fb67a..37cd54c8 100644 --- a/packages/git-mob-core/src/git-mob-api/git-message/message-formatter.ts +++ b/packages/git-mob-core/src/git-mob-api/git-message/message-formatter.ts @@ -2,13 +2,19 @@ import { EOL } from 'node:os'; import { type Author } from '../author'; export enum AuthorTrailers { - CoAuthorBy = 'Co-authored-by:', + CoAuthorBy = 'Co-authored-by', + SignedOffBy = 'Signed-off-by', + ReviewedBy = 'Reviewed-by', } -export function messageFormatter(txt: string, authors: Author[]): string { - const trailers = AuthorTrailers.CoAuthorBy; +function removeTrailers(txt: string): string { + const trailers = Object.values(AuthorTrailers).join('|'); const regex = new RegExp(`(\r\n|\r|\n){0,2}(${trailers}).*`, 'g'); - const message = txt.replaceAll(regex, ''); + return txt.replaceAll(regex, ''); +} + +export function messageFormatter(txt: string, authors: Author[]): string { + const message = removeTrailers(txt); if (authors && authors.length > 0) { const authorTrailerTxt = authors.map(author => author.format()).join(EOL); diff --git a/packages/git-mob-core/src/git-mob-api/git-mob-config.ts b/packages/git-mob-core/src/git-mob-api/git-mob-config.ts index 672446c5..59b7a636 100644 --- a/packages/git-mob-core/src/git-mob-api/git-mob-config.ts +++ b/packages/git-mob-core/src/git-mob-api/git-mob-config.ts @@ -1,4 +1,4 @@ -import { getConfig, getAllConfig, execCommand } from './exec-command.js'; +import { getConfig, execCommand, getRegexpConfig } from './exec-command.js'; export async function localTemplate() { const localTemplate = await getConfig('--local git-mob-config.use-local-template'); @@ -11,11 +11,11 @@ export async function fetchFromGitHub() { } export async function getSetCoAuthors() { - return getAllConfig('--global git-mob.co-author'); + return getRegexpConfig(`--global 'git-mob.*'`); } -export async function addCoAuthor(coAuthor: string) { - const addAuthorQuery = `git config --add --global git-mob.co-author "${coAuthor}"`; +export async function addCoAuthor(coAuthor: string, trailerKey = 'co-author') { + const addAuthorQuery = `git config --add --global git-mob.${trailerKey} "${coAuthor}"`; return execCommand(addAuthorQuery); } diff --git a/packages/git-mob-core/src/index.spec.ts b/packages/git-mob-core/src/index.spec.ts index d56cc7b8..71fbc8eb 100644 --- a/packages/git-mob-core/src/index.spec.ts +++ b/packages/git-mob-core/src/index.spec.ts @@ -1,3 +1,4 @@ +import { EOL } from 'node:os'; import { gitAuthors } from './git-mob-api/git-authors'; import { gitMessage } from './git-mob-api/git-message'; import { AuthorNotFound } from './git-mob-api/errors/author-not-found'; @@ -8,11 +9,13 @@ import { getSetCoAuthors, removeGitMobSection, } from './git-mob-api/git-mob-config'; +import { AuthorTrailers } from './git-mob-api/git-message/message-formatter'; import { getPrimaryAuthor, getSelectedCoAuthors, setCoAuthors, setPrimaryAuthor, + setSelectedAuthors, updateGitTemplate, } from '.'; @@ -28,6 +31,7 @@ const mockedGitMessage = jest.mocked(gitMessage); const mockedRemoveGitMobSection = jest.mocked(removeGitMobSection); const mockedGitConfig = jest.mocked(gitConfig); const mockedGetSetCoAuthors = jest.mocked(getSetCoAuthors); +const mockedAddCoAuthor = jest.mocked(addCoAuthor); describe('Git Mob core API', () => { afterEach(() => { @@ -35,6 +39,7 @@ describe('Git Mob core API', () => { mockedGetSetCoAuthors.mockReset(); mockedGitConfig.getGlobalCommitTemplate.mockReset(); mockedGitConfig.getLocalCommitTemplate.mockReset(); + mockedAddCoAuthor.mockReset(); }); it('missing author to pick for list throws error', async () => { @@ -45,7 +50,7 @@ describe('Git Mob core API', () => { mockedGitMessage.mockReturnValue({ writeCoAuthors: mockWriteCoAuthors, - readCoAuthors: () => '', + readCoAuthors: async () => '', removeCoAuthors: mockRemoveCoAuthors, }); @@ -65,7 +70,7 @@ describe('Git Mob core API', () => { mockedGitMessage.mockReturnValue({ writeCoAuthors: mockWriteCoAuthors, - readCoAuthors: () => '', + readCoAuthors: async () => '', removeCoAuthors: mockRemoveCoAuthors, }); @@ -73,7 +78,42 @@ describe('Git Mob core API', () => { expect(mockedRemoveGitMobSection).toHaveBeenCalledTimes(1); expect(mockRemoveCoAuthors).toHaveBeenCalledTimes(1); - expect(addCoAuthor).toHaveBeenCalledTimes(2); + expect(mockedAddCoAuthor).toHaveBeenCalledTimes(2); + expect(mockWriteCoAuthors).toHaveBeenCalledWith(authorList); + expect(coAuthors).toEqual(authorList); + }); + + it('apply co-authors to git config and git message with custom trailers', async () => { + const authorKeys = ['ab', 'cd']; + const authorTrailers = [AuthorTrailers.CoAuthorBy, AuthorTrailers.ReviewedBy]; + const authorList = buildAuthorList(authorKeys, authorTrailers); + const mockWriteCoAuthors = jest.fn(async () => undefined); + const mockRemoveCoAuthors = jest.fn(async () => ''); + mockedGitAuthors.mockReturnValue(mockGitAuthors([...authorKeys, 'ef'])); + + mockedGitMessage.mockReturnValue({ + writeCoAuthors: mockWriteCoAuthors, + readCoAuthors: async () => '', + removeCoAuthors: mockRemoveCoAuthors, + }); + + const coAuthors = await setSelectedAuthors([ + ['ab', AuthorTrailers.CoAuthorBy], + ['cd', AuthorTrailers.ReviewedBy], + ]); + + expect(mockedRemoveGitMobSection).toHaveBeenCalledTimes(1); + expect(mockRemoveCoAuthors).toHaveBeenCalledTimes(1); + expect(mockedAddCoAuthor).toHaveBeenNthCalledWith( + 1, + authorList[0].toString(), + AuthorTrailers.CoAuthorBy + ); + expect(mockedAddCoAuthor).toHaveBeenNthCalledWith( + 2, + authorList[1].toString(), + AuthorTrailers.ReviewedBy + ); expect(mockWriteCoAuthors).toHaveBeenCalledWith(authorList); expect(coAuthors).toEqual(authorList); }); @@ -133,24 +173,48 @@ describe('Git Mob core API', () => { expect(mockWriteCoAuthors).not.toHaveBeenCalled(); }); - it('Get the selected co-authors', async () => { - const listAll = buildAuthorList(['ab', 'cd']); - const selectedAuthor = listAll[1]; - mockedGetSetCoAuthors.mockResolvedValueOnce(selectedAuthor.toString()); + it('Use exact email for selected co-authors', async () => { + const listAll = buildAuthorList(['ab', 'efcd', 'cd']); + const selectedAuthor = `git-mob.co-author ${listAll[1].toString()}`; + mockedGetSetCoAuthors.mockResolvedValueOnce(selectedAuthor); const selected = await getSelectedCoAuthors(listAll); expect(mockedGetSetCoAuthors).toHaveBeenCalledTimes(1); - expect(selected).toEqual([selectedAuthor]); + expect(selected).toEqual([listAll[1]]); }); - it('Use exact email for selected co-authors', async () => { - const listAll = buildAuthorList(['ab', 'efcd', 'cd']); - const selectedAuthor = listAll[1]; - mockedGetSetCoAuthors.mockResolvedValueOnce(selectedAuthor.toString()); + it('Backward compatibility get the selected co-author using "git-mob.co-author"', async () => { + const listAll = buildAuthorList(['ab', 'cd', 'ef', 'gh']); + const selectedAuthors = [ + `git-mob.co-author ${listAll[1].toString()}`, + `git-mob.${AuthorTrailers.ReviewedBy} ${listAll[2].toString()}`, + ].join(EOL); + + mockedGetSetCoAuthors.mockResolvedValueOnce(selectedAuthors); + const selected = await getSelectedCoAuthors(listAll); + + expect(mockedGetSetCoAuthors).toHaveBeenCalledTimes(1); + expect(selected.length).toEqual(2); + expect(selected[0]?.trailer).toEqual(AuthorTrailers.CoAuthorBy); + expect(selected[1]?.trailer).toEqual(AuthorTrailers.ReviewedBy); + }); + + it('Get the selected co-authors and update respective trailers', async () => { + const listAll = buildAuthorList(['ab', 'cd', 'ef', 'gh']); + const selectedAuthors = [ + `git-mob.${AuthorTrailers.CoAuthorBy} ${listAll[1].toString()}`, + `git-mob.${AuthorTrailers.SignedOffBy} ${listAll[2].toString()}`, + `git-mob.${AuthorTrailers.ReviewedBy} ${listAll[3].toString()}`, + ].join(EOL); + + mockedGetSetCoAuthors.mockResolvedValueOnce(selectedAuthors); const selected = await getSelectedCoAuthors(listAll); expect(mockedGetSetCoAuthors).toHaveBeenCalledTimes(1); - expect(selected).toEqual([selectedAuthor]); + expect(selected.length).toEqual(3); + expect(selected[0]?.trailer).toEqual(AuthorTrailers.CoAuthorBy); + expect(selected[1]?.trailer).toEqual(AuthorTrailers.SignedOffBy); + expect(selected[2]?.trailer).toEqual(AuthorTrailers.ReviewedBy); }); it('Get the Git primary author', async () => { diff --git a/packages/git-mob-core/src/index.ts b/packages/git-mob-core/src/index.ts index d099a20c..c165efc8 100644 --- a/packages/git-mob-core/src/index.ts +++ b/packages/git-mob-core/src/index.ts @@ -1,3 +1,4 @@ +import { EOL } from 'node:os'; import { Author } from './git-mob-api/author.js'; import { AuthorNotFound } from './git-mob-api/errors/author-not-found.js'; import { gitAuthors } from './git-mob-api/git-authors/index.js'; @@ -23,6 +24,7 @@ import { } from './git-mob-api/resolve-git-message-path.js'; import { insideWorkTree, topLevelDirectory } from './git-mob-api/git-rev-parse.js'; import { getConfig } from './git-mob-api/exec-command.js'; +import { AuthorTrailers } from './git-mob-api/git-message/message-formatter.js'; async function getAllAuthors() { const gitMobAuthors = gitAuthors(); @@ -42,6 +44,28 @@ async function setCoAuthors(keys: string[]): Promise { return selectedAuthors; } +async function setSelectedAuthors( + keysTrailers: Array<[string, AuthorTrailers]> +): Promise { + const allAuthors = await getAllAuthors(); + const selectedAuthors = keysTrailers.map(([key, trailer]) => { + const author = allAuthors.find(author => author.key === key); + if (!author) throw new AuthorNotFound(key); + author.trailer = trailer; + return author; + }); + + await solo(); + + for (const author of selectedAuthors) { + // eslint-disable-next-line no-await-in-loop + await addCoAuthor(author.toString(), author.trailer); + } + + await updateGitTemplate(selectedAuthors); + return selectedAuthors; +} + async function updateGitTemplate(selectedAuthors?: Author[]) { const [usingLocal, templatePath] = await Promise.all([ getLocalCommitTemplate(), @@ -79,12 +103,35 @@ function pickSelectedAuthors(keys: string[], authorMap: Author[]): Author[] { return selectedAuthors; } +function convertToAuthorTrailers(value: string): AuthorTrailers | undefined { + if (value === 'co-author') return AuthorTrailers.CoAuthorBy; + + return (Object.values(AuthorTrailers) as string[]).includes(value) + ? (value as AuthorTrailers) + : undefined; +} + async function getSelectedCoAuthors(allAuthors: Author[]) { - let coAuthorsString = ''; - const coAuthorConfigValue = await getSetCoAuthors(); - if (coAuthorConfigValue) coAuthorsString = coAuthorConfigValue; + const coAuthorsString = (await getSetCoAuthors()) ?? ''; - return allAuthors.filter(author => coAuthorsString.includes('<' + author.email)); + return coAuthorsString + .split(EOL) + .map(line => { + const [key, ...rest] = line.split(' '); + const authorString = rest.join(' '); + + const trailer = convertToAuthorTrailers(key.split('.')[1]); + if (!trailer) return null; + + const author = allAuthors.find(author => + authorString.includes('<' + author.email) + ); + if (!author) return null; + + author.trailer = trailer; + return author; + }) + .filter(author => author !== null); } async function solo() { @@ -112,6 +159,7 @@ export { getPrimaryAuthor, getSelectedCoAuthors, setCoAuthors, + setSelectedAuthors, setPrimaryAuthor, solo, updateGitTemplate, @@ -143,4 +191,7 @@ export { } from './git-mob-api/git-authors/fetch-github-authors.js'; export { getConfig, updateConfig } from './config-manager.js'; export { Author } from './git-mob-api/author.js'; -export { messageFormatter } from './git-mob-api/git-message/message-formatter.js'; +export { + messageFormatter, + AuthorTrailers, +} from './git-mob-api/git-message/message-formatter.js'; diff --git a/packages/git-mob-core/src/test-helpers/author-mocks.ts b/packages/git-mob-core/src/test-helpers/author-mocks.ts index b223d00d..fb3fe8d8 100644 --- a/packages/git-mob-core/src/test-helpers/author-mocks.ts +++ b/packages/git-mob-core/src/test-helpers/author-mocks.ts @@ -1,15 +1,25 @@ import { Author } from '../git-mob-api/author'; +import { CoAuthorSchema } from '../git-mob-api/git-authors'; +import { AuthorTrailers } from '../git-mob-api/git-message/message-formatter'; -export function buildAuthorList(keys: string[]): Author[] { - return keys.map(key => new Author(key, key + ' lastName', key + '@email.com')); +export function buildAuthorList( + keys: string[], + trailers: AuthorTrailers[] = [] +): Author[] { + return keys.map( + (key, i) => + new Author( + key, + key + ' lastName', + key + '@email.com', + trailers[i] || AuthorTrailers.CoAuthorBy + ) + ); } -export function buildCoAuthorObject(keys: string[]) { +export function buildCoAuthorObject(keys: string[]): CoAuthorSchema { const authorList = buildAuthorList(keys); - const coAuthorList: Record< - string, - Record - > = { coauthors: {} }; + const coAuthorList: CoAuthorSchema = { coauthors: {} }; for (const author of authorList) { coAuthorList.coauthors[author.key] = { name: author.name, email: author.email }; @@ -18,8 +28,8 @@ export function buildCoAuthorObject(keys: string[]) { return coAuthorList; } -export function mockGitAuthors(keys: string[]) { - const authors = buildAuthorList(keys); +export function mockGitAuthors(keys: string[], trailers: AuthorTrailers[] = []) { + const authors = buildAuthorList(keys, trailers); const coAuthors = buildCoAuthorObject(keys); return { read: jest.fn(async () => coAuthors), diff --git a/packages/git-mob/src/git-mob.spec.ts b/packages/git-mob/src/git-mob.spec.ts index ae7daeaa..ecfb49d9 100644 --- a/packages/git-mob/src/git-mob.spec.ts +++ b/packages/git-mob/src/git-mob.spec.ts @@ -258,6 +258,69 @@ test('appends co-authors to a new commit template', t => { unsetCommitTemplate(); }); +test('sets signed-off-by Jane Doe trailer via git mob CLI', t => { + deleteGitMessageFile(); + addAuthor('Thomas Anderson', 'neo@example.com'); + + exec('git mob --sb jd'); + + const actualGitMessage = readGitMessageFile(); + const expectedGitMessage = auto( + [EOL, EOL, 'Signed-off-by: Jane Doe '].join('') + ); + + t.is(actualGitMessage, expectedGitMessage); + + removeCoAuthors(); + unsetCommitTemplate(); +}); + +test('sets two signed-off-by trailers via git mob CLI', t => { + deleteGitMessageFile(); + addAuthor('Thomas Anderson', 'neo@example.com'); + + exec('git mob --sb jd --sb ea'); + + const actualGitMessage = readGitMessageFile(); + const expectedGitMessage = auto( + [ + EOL, + EOL, + 'Signed-off-by: Jane Doe ', + EOL, + 'Signed-off-by: Elliot Alderson ', + ].join('') + ); + + t.is(actualGitMessage, expectedGitMessage); + + removeCoAuthors(); + unsetCommitTemplate(); +}); + +test('sets default co-authored-by and signed-off-by trailers via git mob CLI', t => { + deleteGitMessageFile(); + addAuthor('Thomas Anderson', 'neo@example.com'); + + exec('git mob ea --sb jd'); + + const actualGitMessage = readGitMessageFile(); + const expectedGitMessage = auto( + [ + EOL, + EOL, + 'Co-authored-by: Elliot Alderson ', + EOL, + 'Signed-off-by: Jane Doe ', + ].join('') + ); + + t.is(actualGitMessage, expectedGitMessage); + + removeCoAuthors(); + unsetCommitTemplate(); +}); + test('warns when used outside of a git repo', t => { const repoDir = process.cwd(); const temporaryDir = temporaryDirectory(); diff --git a/packages/git-mob/src/git-mob.ts b/packages/git-mob/src/git-mob.ts index ef94102b..1f980190 100644 --- a/packages/git-mob/src/git-mob.ts +++ b/packages/git-mob/src/git-mob.ts @@ -8,11 +8,12 @@ import { gitMobConfig, gitConfig, gitRevParse, - setCoAuthors, setPrimaryAuthor, updateGitTemplate, Author, pathToCoAuthors, + setSelectedAuthors, + AuthorTrailers, } from 'git-mob-core'; import { checkForUpdates, runHelp, runVersion, printList } from './helpers.js'; import { configWarning } from './check-author.js'; @@ -23,6 +24,7 @@ checkForUpdates(); const argv = minimist(process.argv.slice(2), { boolean: ['h', 'v', 'l', 'o', 'p'], + string: ['sb', 'rb'], alias: { h: 'help', @@ -68,13 +70,52 @@ async function execute(args: minimist.ParsedArgs) { await setAuthor(initial); } - await runMob(args._); + await runMob(mapTrailersToInitials(args)); } else { - await runMob(args._); + await runMob(mapTrailersToInitials(args)); } } -async function runMob(args: string[]) { +function normaliseToArray(input: T | T[]): T[] { + if (Array.isArray(input)) { + return input; + } + + if (input === undefined || input === null || input === '') { + return []; + } + + return [input]; +} + +function mapTrailersToInitials( + args: minimist.ParsedArgs +): Array<[string, AuthorTrailers]> { + const sb = normaliseToArray(args.sb as string[]); + const rb = normaliseToArray(args.rb as string[]); + + const remainingArgs = { cb: args._ || [], sb, rb }; + const mapTrailers: Record = { + cb: AuthorTrailers.CoAuthorBy, + sb: AuthorTrailers.SignedOffBy, + rb: AuthorTrailers.ReviewedBy, + }; + + const result: Array<[string, AuthorTrailers]> = []; + for (const [accept, value] of Object.entries(remainingArgs)) { + if (Array.isArray(value) && value.length > 0) { + for (const author of value) { + if (typeof author === 'string') { + result.push([author, mapTrailers[accept]]); + } + } + } + } + + return result; +} + +async function runMob(args: Array<[string, AuthorTrailers]>) { if (args.length === 0) { const gitAuthor = await getPrimaryAuthor(); const [authorList, useLocalTemplate, template] = await Promise.all([ @@ -134,11 +175,14 @@ async function listCoAuthors() { } } -async function setMob(initials: string[]) { +async function setMob(initials: Array<[string, AuthorTrailers]>) { try { const authorList = await getAllAuthors(); - await saveMissingAuthors(initials, authorList); - const selectedCoAuthors = await setCoAuthors(initials); + await saveMissingAuthors( + initials.map(([key]) => key), + authorList + ); + const selectedCoAuthors = await setSelectedAuthors(initials); const [useLocalTemplate, template] = await Promise.all([ gitMobConfig.localTemplate(), diff --git a/packages/git-mob/test-helpers/env.cjs b/packages/git-mob/test-helpers/env.cjs index cc8be4a9..6e575e66 100644 --- a/packages/git-mob/test-helpers/env.cjs +++ b/packages/git-mob/test-helpers/env.cjs @@ -1,4 +1,3 @@ -const { spawnSync } = require('child_process'); const path = require('path'); const testHelperPath = path.join(process.cwd(), '/test-helpers');