Skip to content

Commit

Permalink
Integrate version resolver
Browse files Browse the repository at this point in the history
* You can now download multiple packages at once.
* Improved UX.
  • Loading branch information
kasipavankumar committed Dec 31, 2021
2 parents 953ac49 + 7a48e37 commit cf15804
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 121 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.jscratesrc.json
.jscrates-cache
bin
node_modules
tars
.jscratesrc.json
jscrates
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,23 @@ docker run -e HOME=/tmp -v $HOME/.jscrates/docker:/tmp/.jscrates -it --rm jscrat

## Commands

1. `jscrates download`
1. `unload`

#### Description

Downloads the specified package from the official repository of JSCrates.
Downloads the specified package(s) from official repository of JSCrates.

#### Usage

```bash
$ jscrates download <package-name> [version]
jscrates unload <packages>
```

### Example

```bash
jscrates unload physics bodmas@1.0.0
jscrates unload @jscrates/cli @jscrates/unload@1.0.0
```

2. `publish`
Expand All @@ -66,7 +73,7 @@ Have a package that you want to share with the world? This command will help you
This command requires you to set or open the terminal in your project directory.

```bash
$ jscrates publish
jscrates publish
```

---
Expand Down
60 changes: 0 additions & 60 deletions actions/download.js

This file was deleted.

126 changes: 126 additions & 0 deletions actions/packages/unload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// @ts-check

import https from 'https'
import { createWriteStream, createReadStream } from 'fs'
import Spinner from 'mico-spinner'
import tempDirectory from 'temp-dir'
import chalk from 'chalk'
import tar from 'tar'
import { getPackages } from '../../lib/api/actions.js'
import { logError } from '../../utils/loggers.js'
import upsertDir from '../../utils/upsert-dir.js'

// This is the directory on the OS's temp location where
// crates will be cached to enable offline operations.
const cacheDir = tempDirectory + '/.jscrates-cache'
// Directory in the current project where packages will
// be installed (unzipped). Consider this as `node_modules`
// for JSCrates
const installDir = './jscrates'

// Generates directory path suffixed with the package name.
const suffixPackageName = (baseDir, packageName) => baseDir + '/' + packageName

// Used for storing packages in cache.
const generateCacheDirPath = (packageName = '') =>
suffixPackageName(cacheDir, packageName)

// Used for unzipping packages in the CWD.
const generateCratesInstallDir = (packageName = '') =>
suffixPackageName(installDir, packageName)

// Extracts tarball name from the provided URL.
const getTarballName = (tarballURL) => {
return tarballURL.substring(tarballURL.lastIndexOf('/') + 1)
}

/**
* Action to download packages from repository.
*
* TODO: Implement logic to check packages in cache before
* requesting the API.
*
* @param {string[]} packages
*/
async function unloadPackages(packages, ...args) {
// Since we are accepting variadic arguments, other arguments can only
// be accessing by spreading them.
const store = args[1].__store
const spinner = Spinner(`Downloading packages`)

try {
if (!store?.isOnline) {
return logError('Internet connection is required to download packages.')
}

spinner.start()

const response = await getPackages(packages)

// `data` contains all the resolved packages metadata.
// 1. Download the tarball to cache directory.
// 2. Read the cached tarball & install in CWD.
response?.data?.map((res) => {
const timerLabel = chalk.green(`Installed \`${res.name}\` in`)
console.time(timerLabel)

const tarballFileName = getTarballName(res?.dist?.tarball)
const cacheLocation = upsertDir(generateCacheDirPath(res?.name))
const installLocation = upsertDir(generateCratesInstallDir(res?.name))

// Create a write file stream to download the tar file
const file = createWriteStream(`${cacheLocation}/${tarballFileName}`)

// Initiate the HTTP request to download package archive
// (.tgz) files from the cloud repository
https.get(res?.dist?.tarball, function (response) {
response
.on('error', function () {
throw 'Something went wrong downloading the package.'
})
.on('data', function (data) {
file.write(data)
})
.on('end', function () {
file.end()
createReadStream(`${cacheLocation}/${tarballFileName}`).pipe(
tar.x({ cwd: installLocation })
)
})
})

console.timeEnd(timerLabel)
})

console.log('\n')

// When only a few packages are resolved, the errors array
// contains list of packages that were not resolved.
// We shall display these for better UX.
console.group(
chalk.yellow('The following errors occured during this operation:')
)

if (response?.errors?.length) {
logError(response?.errors?.join('\n'))
}

console.groupEnd()

console.log('\n')

spinner.succeed()
} catch (error) {
spinner.fail()

// When all the requested packages could not be resolved
// API responds with status 404 and list of errors.
if (Array.isArray(error)) {
return logError(error.join('\n'))
}

return logError(error)
}
}

export default unloadPackages
25 changes: 15 additions & 10 deletions jscrates.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
// @ts-check
#!/usr/bin/env node

import { readFile } from 'fs/promises'
import { Command } from 'commander'
import Configstore from 'configstore'
import checkOnlineStatus from 'is-online'

import { CONFIG_FILE } from './lib/constants.js'
import downloadPackage from './actions/download.js'
import unloadPackages from './actions/packages/unload.js'
import publishPackage from './actions/publish.js'
import login from './actions/auth/login.js'
import register from './actions/auth/register.js'
import logout from './actions/auth/logout.js'

async function jscratesApp() {
const packageJSON = JSON.parse(await readFile('./package.json', 'utf-8'))
const isOnline = await checkOnlineStatus()
const program = new Command()
const configStore = new Configstore(CONFIG_FILE, {
Expand All @@ -27,7 +25,8 @@ async function jscratesApp() {
program
.name('jscrates')
.description(`Welcome to JSCrates 📦, yet another package manager for Node`)
.version(packageJSON.version, '-v, --version', 'display current version')
// TODO: Find a way to read version build time.
.version('0.0.0-alpha', '-v, --version', 'display current version')
.hook('preAction', (_, actionCommand) => {
actionCommand['__store'] = appState
})
Expand All @@ -48,11 +47,17 @@ async function jscratesApp() {
.action(logout(configStore))

program
.command('download')
.description(`Download a package from official JSCrates registry`)
.argument('<package name>', 'package to download')
.argument('[version]', 'version of the package to download')
.action(downloadPackage)
.command('unload')
.description('🔽 Download package(s) from the JSCrates registry')
.argument('<packages...>', 'List of packages delimited by a space')
.action(unloadPackages)
.addHelpText(
'after',
'\nExamples:\n jscrates unload bodmas' +
'\n jscrates unload physics-formulae@1.0.0' +
'\n jscrates unload binary-search merge-sort bodmas@1.0.0'
)
.aliases(['u'])

program
.command('publish')
Expand Down
19 changes: 19 additions & 0 deletions lib/api/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ const apiErrorHandler = (error) => {
}
}

const apiAction = async (bodyFn, errorHandlerFn = undefined) => {
try {
return await bodyFn()
} catch (error) {
return errorHandlerFn ? errorHandlerFn(error) : apiErrorHandler(error)
}
}

export const registerUser = async ({ email, password }) => {
try {
const { data: apiResponse } = await api.post('/auth/register', {
Expand All @@ -31,3 +39,14 @@ export const loginUser = async ({ email, password }) => {
return apiErrorHandler(error)
}
}

export const getPackages = async (packages) => {
return await apiAction(
async () => {
return (await api.put('/pkg', { packages })).data
},
(error) => {
throw error?.response?.data?.errors
}
)
}
Loading

0 comments on commit cf15804

Please sign in to comment.