Skip to content

Commit

Permalink
General improvements:
Browse files Browse the repository at this point in the history
Fix #2

- Improve async handling
- Add progress bar
- Add custom text message option
  • Loading branch information
cezaraugusto committed Feb 20, 2024
1 parent eadee49 commit 1c4ad29
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 134 deletions.
10 changes: 8 additions & 2 deletions README.md
Expand Up @@ -75,7 +75,7 @@ goGitIt('https://github.com/username/repository', 'path/to/outputDir')

### API

#### goGitIt(url, outputDir?)
#### goGitIt(url, outputDir?, text?)

##### url

Expand All @@ -87,7 +87,13 @@ The URL to the path you want to download. If a folder, will download its content

Type: `string`

Custom path to the outputDir (defaults to the working directorya)
Custom path to the outputDir (defaults to the working directory)

##### text

Type: `string`

Adds a custom text message instead of default config. This option overrides the success message as well.

## License

Expand Down
48 changes: 48 additions & 0 deletions addProgressBar.ts
@@ -0,0 +1,48 @@
// @ts-ignore
import ProgressBar from 'progress'

export default function addProgressBar(
text: string,
completionCallback: () => Promise<void>
): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
const contentLength = 2048 * 1024 // 2MB
const bar = new ProgressBar(`${text} [:bar] :percent :etas`, {
complete: '=',
incomplete: ' ',
width: 50,
total: contentLength
})

// Simulate progress
let progress = 0

const timer = setInterval(() => {
const chunk = Math.random() * 10 * 1024
progress += chunk
bar.tick(chunk)
if (progress >= contentLength || bar.complete) {
// Clear interval when progress is complete
clearInterval(timer)
}
}, 50)

try {
// Wait for the cloning operation to complete
await completionCallback()

// Ensure the interval is cleared after operation completes
clearInterval(timer)

// Force-complete the progress bar
bar.tick(contentLength)
resolve()
} catch (error) {
// Ensure the interval is cleared in case of error
clearInterval(timer)

console.error('[go-git-it] An error occurred:', error)
reject(error)
}
})
}
69 changes: 34 additions & 35 deletions downloadMainRepo.ts
@@ -1,50 +1,49 @@
import path from 'path'
import util from 'util'
import shell from 'shelljs'
import pullSource from './pullSource'

const exec = util.promisify(shell.exec)

interface DownloadMainRepo {
owner: string
project: string
}

export default function downloadMainRepo(
export default async function downloadMainRepo(
outputDirectory: string,
options: DownloadMainRepo
{owner, project}: DownloadMainRepo
) {
const {owner, project} = options
shell.mkdir('-p', path.join(outputDirectory, project))
shell.cd(path.join(outputDirectory, project))
shell.exec('git init --quiet')
shell.exec(`git remote add origin https://github.com/${owner}/${project}`)

// User is in the project root directory, try pulling from `main`
shell.exec(pullSource('main'))

// Nothing added on `main`, try the old `master`
const pullExit = shell.exec(`[ "$(ls -A .)" ] || ${pullSource('master')}`)

// Nothing added. We need a branch so we exit with error
const errorMessage =
'No default branch found. Ensure you are pulling from `main` or `master` branch.'
const projectPath = path.join(outputDirectory, project)
shell.mkdir('-p', projectPath)
shell.cd(projectPath)
await exec('git init --quiet')
await exec(`git remote add origin https://github.com/${owner}/${project}`)

// Try to pull from 'main' first, then fallback to 'master'
const branches = ['main', 'master']
let success = false

for (const branch of branches) {
try {
await exec(pullSource(branch))
success = true
console.log(`Successfully pulled from branch '${branch}'.`)
break // Exit the loop on success
} catch (error) {
console.log(
`Failed to pull using branch '${branch}'. Trying next option...`
)
}
}

const copyExit = shell.exec(`[ "$(ls -A .)" ] || echo ${errorMessage}`)
if (!success) {
console.error(
'Error: Could not determine the default branch or failed to pull from it.'
)
process.exit(1)
}

// Remove the git folder as we don't need it
// Clean up .git directory
shell.rm('-rf', '.git')

// Git data pull failed
if (pullExit.code !== 0) {
console.log('Error when trying to pull git data', pullExit.stderr)
process.exit(pullExit.code)

// The attempt to copy files failed
} else if (copyExit.code !== 0) {
console.log('Error when trying to copy git data', copyExit.stderr)
process.exit(copyExit.code)

// All good, project downloaded
} else {
console.log('')
console.log(`Success! \`${project}\` downloaded to \`${outputDirectory}\`.`)
}
}
82 changes: 24 additions & 58 deletions downloadPartialRepo.ts
@@ -1,76 +1,42 @@
import path from 'path'
import fs from 'fs'
import util from 'util'
import shell from 'shelljs'
import pullSource from './pullSource'

export default function downloadPartialRepo(
const exec = util.promisify(shell.exec)

export default async function downloadPartialRepo(
outputDirectory: string,
options: any
{owner, project, filePath, branch}: any
) {
const {owner, project, filePath, branch} = options

// Use sparse checkout to grab contents of a specific folders
const tempDownloadName = '.go-git-it-temp-folder'

// Create a temp directory to add a git repo. This is needed
// so we can have the right config when pulling data, as we only
// want the specific folder path defined by user.
shell.mkdir('-p', path.join(outputDirectory, tempDownloadName))
shell.cd(path.join(outputDirectory, tempDownloadName))
const tempDownloadName = '.go-git-it-temp-folder'

// Start git
shell.exec('git init --quiet')
const tempDownloadPath = path.join(outputDirectory, tempDownloadName)

// Add sparse checkout to git so we can download specific files only
shell.exec(`git remote add origin https://github.com/${owner}/${project}`)
shell.mkdir('-p', tempDownloadPath)
shell.cd(tempDownloadPath)

shell.exec('git config core.sparsecheckout true')
await exec('git init --quiet')
await exec(`git remote add origin https://github.com/${owner}/${project}`)

// Assume a dot in the filePath means a file and not a folder
const isFile = filePath
.split('/')
[filePath.split('/').length - 1].includes('.')
await exec('git config core.sparseCheckout true')

const isFile = filePath.includes('.')
const sparsePath = isFile ? filePath : `${filePath}/*`

// Write to git the asset path user is trying to download
shell.exec(`echo ${sparsePath} >> .git/info/sparse-checkout`)

// User is in the project root directory, try pulling from `main`
shell.exec(pullSource(branch))

// Nothing added on `main`, try the old `master`
const pullExit = shell.exec(pullSource(branch))

// Move assets to the final output directory
shell.mv(filePath, '..')

// Go back to root directory so we can delete the temp folder
shell.cd('..')

// Remove the temp folder
shell.rm('-rf', tempDownloadName)

// Git data pull failed
if (pullExit.code !== 0) {
// Nothing added. We need a branch so we exit with error
const errorMessage =
'No branch found. Ensure the remote branch you are pulling exists.\n' +
'If you think this is a bug, please report to ' +
'https://github.com/cezaraugusto/go-git-it/issues.\n' +
`Error: ${pullExit.stderr}`

console.log(errorMessage)
process.exit(pullExit.code)
// All good, project downloaded
} else {
const filePathArray = filePath.split('/')
const asset = filePathArray[filePathArray.length - 1]
const isDirectory = fs.lstatSync(asset).isDirectory()
const assetType = isDirectory ? 'Folder' : 'File'

console.log(`
\nSuccess! ${assetType} \`${asset}\` downloaded to \`${outputDirectory}\`.
`)
fs.writeFileSync('.git/info/sparse-checkout', sparsePath)

try {
await exec(pullSource(branch))
shell.mv(filePath, path.dirname(tempDownloadPath))
} catch (error) {
console.error('Error pulling git repository:', error)
process.exit(1)
} finally {
shell.cd('..')
shell.rm('-rf', tempDownloadName)
}
}
49 changes: 22 additions & 27 deletions module.test.js
Expand Up @@ -7,80 +7,75 @@ const goGitIt = require('./dist/module').default

const repoURL = 'https://github.com/cezaraugusto/extension-create'
const folderURL =
'https://github.com/cezaraugusto/extension-create/tree/main/create'
'https://github.com/cezaraugusto/extension-create/tree/main/packages'
const fileURL =
'https://github.com/cezaraugusto/extension-create/tree/main/create/cli.js'
'https://github.com/cezaraugusto/extension-create/tree/main/package.json'
const customPath = path.resolve(__dirname, 'some/extraordinary/folder')

// Disable console.logs as we don't care about them
for (const func in console) {
console[func] = function () {}
}

describe('go-git-it', () => {
describe('working with directories', () => {
describe('working with full URLs', () => {
afterEach(() => {
shell.rm('-rf', path.resolve(__dirname, path.basename(repoURL)))
shell.rm('-rf', path.resolve(__dirname, 'some'))
})
test('works with repositories', async () => {
goGitIt(repoURL)
test('works with default path', async () => {
await goGitIt(repoURL)

const pathName = path.resolve(__dirname, path.basename(repoURL))

expect(await fs.pathExists(pathName)).toBe(true)
})

test('using a custom path works with repositories', async () => {
goGitIt(repoURL, customPath)
test('works with a custom path', async () => {
await goGitIt(repoURL, customPath)

const pathName = path.resolve(customPath, path.basename(repoURL))

expect(await fs.pathExists(pathName)).toBe(true)
})
})

describe('files', () => {
describe('working with partial URLs (basename is file)', () => {
afterEach(() => {
shell.rm('-rf', path.basename(fileURL))
shell.rm('-rf', path.resolve(__dirname, 'some'))
})

test('works with files', async () => {
goGitIt(fileURL)
test('works with default path', async () => {
await goGitIt(fileURL)

const pathName = path.resolve(process.cwd(), 'cli.js')
const pathName = path.resolve(process.cwd(), 'package.json')

expect(await fs.pathExists(pathName)).toBe(true)
})

test('using a custom path works with files', async () => {
goGitIt(fileURL, customPath)
test('works with a custom path', async () => {
await goGitIt(fileURL, customPath)

const pathName = path.resolve(customPath, 'cli.js')
const pathName = path.resolve(customPath, 'package.json')

expect(await fs.pathExists(pathName)).toBe(true)
})
})

describe('folder', () => {
describe('working with partial URLs (basename is folder)', () => {
afterEach(() => {
shell.rm('-rf', path.resolve(__dirname, 'create'))
shell.rm('-rf', path.resolve(__dirname, 'packages'))
shell.rm('-rf', path.resolve(__dirname, 'some'))
})

test('works with folders', async () => {
goGitIt(folderURL)
test('works with default path', async () => {
await goGitIt(folderURL)

const pathName = path.resolve(process.cwd(), 'create')
const pathName = path.resolve(process.cwd(), 'packages')

expect(await fs.pathExists(pathName)).toBe(true)
})

test('using a custom path works with folders', async () => {
goGitIt(folderURL, customPath)
test('works with a custom path', async () => {
await goGitIt(folderURL, customPath)

const pathName = path.resolve(customPath, 'create')
const pathName = path.resolve(customPath, 'packages')

expect(await fs.pathExists(pathName)).toBe(true)
})
Expand Down

0 comments on commit 1c4ad29

Please sign in to comment.