Skip to content

Commit

Permalink
Add addToGitIgnore to cli-kit to append entries to existing `.gitig…
Browse files Browse the repository at this point in the history
…nore` files
  • Loading branch information
karreiro committed Jan 23, 2025
1 parent 7b3ba77 commit ab407f3
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/thick-flies-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/cli-kit': patch
---

Add `addToGitIgnore` to cli-kit to append entries to existing `.gitignore` files
76 changes: 73 additions & 3 deletions packages/cli-kit/src/public/node/git.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as git from './git.js'
import {appendFileSync} from './fs.js'
import {appendFileSync, fileExistsSync, inTemporaryDirectory, readFileSync, writeFileSync} from './fs.js'
import {hasGit} from './context/local.js'
import {beforeEach, describe, expect, test, vi} from 'vitest'
import simpleGit from 'simple-git'
Expand All @@ -25,9 +25,15 @@ const simpleGitProperties = {
status: mockedGitStatus,
}

vi.mock('./context/local.js')
vi.mock('./fs.js')
vi.mock('simple-git')
vi.mock('./context/local.js')
vi.mock('./fs.js', async () => {
const fs = await vi.importActual('./fs.js')
return {
...fs,
appendFileSync: vi.fn(),
}
})

beforeEach(() => {
vi.mocked(hasGit).mockResolvedValue(true)
Expand Down Expand Up @@ -375,3 +381,67 @@ describe('isGitClean()', () => {
expect(simpleGit).toHaveBeenCalledWith({baseDir: directory})
})
})

describe('addToGitIgnore()', () => {
test('does nothing when .gitignore does not exist', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const gitIgnorePath = `${tmpDir}/.gitignore`

// When
git.addToGitIgnore(tmpDir, '.shopify')

// Then
expect(fileExistsSync(gitIgnorePath)).toBe(false)
})
})

test('does nothing when pattern already exists in .gitignore', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const gitIgnorePath = `${tmpDir}/.gitignore`
const gitIgnoreContent = ' .shopify \nnode_modules\n'

writeFileSync(gitIgnorePath, gitIgnoreContent)

// When
git.addToGitIgnore(tmpDir, '.shopify')

// Then
const actualContent = readFileSync(gitIgnorePath).toString()
expect(actualContent).toBe(gitIgnoreContent)
})
})

test('appends pattern to .gitignore when file exists and pattern not present', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const gitIgnorePath = `${tmpDir}/.gitignore`

writeFileSync(gitIgnorePath, 'node_modules\ndist')

// When
git.addToGitIgnore(tmpDir, '.shopify')

// Then
const gitIgnoreContent = readFileSync(gitIgnorePath).toString()
expect(gitIgnoreContent).toBe('node_modules\ndist\n.shopify\n')
})
})

test('appends pattern to .gitignore when file exists and pattern not present without duplicating the last empty line', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const gitIgnorePath = `${tmpDir}/.gitignore`

writeFileSync(gitIgnorePath, 'node_modules\ndist\n')

// When
git.addToGitIgnore(tmpDir, '.shopify')

// Then
const gitIgnoreContent = readFileSync(gitIgnorePath).toString()
expect(gitIgnoreContent).toBe('node_modules\ndist\n.shopify\n')
})
})
})
36 changes: 34 additions & 2 deletions packages/cli-kit/src/public/node/git.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-base-to-string */
import {hasGit, isTerminalInteractive} from './context/local.js'
import {appendFileSync} from './fs.js'
import {appendFileSync, detectEOL, fileExistsSync, readFileSync, writeFileSync} from './fs.js'
import {AbortError} from './error.js'
import {cwd} from './path.js'
import {cwd, joinPath} from './path.js'
import {runWithTimer} from './metadata.js'
import {outputContent, outputToken, outputDebug} from '../../public/node/output.js'
import git, {TaskOptions, SimpleGitProgressEvent, DefaultLogFields, ListLogLine, SimpleGit} from 'simple-git'
Expand Down Expand Up @@ -63,6 +63,38 @@ export function createGitIgnore(directory: string, template: GitIgnoreTemplate):
appendFileSync(filePath, fileContent)
}

/**
* Add an entry to an existing .gitignore file.
*
* If the .gitignore file doesn't exist, or if the entry is already present,
* no changes will be made.
*
* @param root - The directory containing the .gitignore file.
* @param entry - The entry to add to the .gitignore file.
*/
export function addToGitIgnore(root: string, entry: string): void {
const gitIgnorePath = joinPath(root, '.gitignore')

if (!fileExistsSync(gitIgnorePath)) {
// When the .gitignore file does not exist, the CLI should not be opinionated about creating it
return
}

const gitIgnoreContent = readFileSync(gitIgnorePath).toString()
const eol = detectEOL(gitIgnoreContent)

if (gitIgnoreContent.split(eol).some((line) => line.trim() === entry.trim())) {
// The file already existing in the .gitignore
return
}

if (gitIgnoreContent.endsWith(eol)) {
writeFileSync(gitIgnorePath, `${gitIgnoreContent}${entry}${eol}`)
} else {
writeFileSync(gitIgnorePath, `${gitIgnoreContent}${eol}${entry}${eol}`)
}
}

/**
* Options to use when cloning a git repository.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/theme/src/cli/services/metafields-pull.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ describe('metafields-pull', () => {

// Then
await expect(fileExists(gitIgnorePath)).resolves.toBe(true)
await expect(readFile(gitIgnorePath)).resolves.toBe(`.DS_Store\n.shopify/secrets.json\n.shopify`)
await expect(readFile(gitIgnorePath)).resolves.toBe(`.DS_Store\n.shopify/secrets.json\n.shopify\n`)
})

expect(capturedOutput.info()).toContain('Metafield definitions have been successfully downloaded.')
Expand Down
22 changes: 2 additions & 20 deletions packages/theme/src/cli/services/metafields-pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import {AdminSession, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/ses
import {cwd, joinPath} from '@shopify/cli-kit/node/path'
import {metafieldDefinitionsByOwnerType} from '@shopify/cli-kit/node/themes/api'
import {renderError, renderSuccess} from '@shopify/cli-kit/node/ui'
import {detectEOL, fileExistsSync, mkdirSync, readFileSync, writeFileSync} from '@shopify/cli-kit/node/fs'
import {fileExistsSync, mkdirSync, writeFileSync} from '@shopify/cli-kit/node/fs'
import {outputDebug} from '@shopify/cli-kit/node/output'
import {addToGitIgnore} from '@shopify/cli-kit/node/git'

interface MetafieldsPullOptions {
path: string
Expand Down Expand Up @@ -169,22 +170,3 @@ function writeMetafieldDefinitionsToFile(path: string, content: unknown) {

writeFileSync(filePath, fileContent)
}

function addToGitIgnore(root: string, entry: string) {
const gitIgnorePath = joinPath(root, '.gitignore')

if (!fileExistsSync(gitIgnorePath)) {
// When the .gitignore file does not exist, the CLI should not be opinionated about creating it
return
}

const gitIgnoreContent = readFileSync(gitIgnorePath).toString()
const eol = detectEOL(gitIgnoreContent)

if (gitIgnoreContent.split(eol).includes(entry)) {
// The file already existing in the .gitignore
return
}

writeFileSync(gitIgnorePath, `${gitIgnoreContent}${eol}${entry}`)
}

0 comments on commit ab407f3

Please sign in to comment.