Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-install packages #57

Closed
OnurGvnc opened this issue May 11, 2021 · 6 comments
Closed

Auto-install packages #57

OnurGvnc opened this issue May 11, 2021 · 6 comments

Comments

@OnurGvnc
Copy link

first of all, thank you very much. zx is a very nice tool.

As far as I can see, people want to use the packages they are used to with zx.
I've browsed some forks. People create their own packages by adding their own dependencies.

I am not sure about the side effects.
However, it seems like a good idea to install and use packages in the runtime.

Maybe something like this.. await use('package-name')
If this package does not exist in node_modules in the directory where zx is installed, it is installed with npm install and added to global with Object.assing

#!/usr/bin/env zx

await use('shelljs')

shelljs.rm('-rf', 'out/Release');
shelljs.cp('-R', 'stuff/', 'out/Release');

I implemented this idea experimentally as follows.

zx.mjs

import { join, basename, dirname } from 'path'
import os, { tmpdir } from 'os'
import { promises as fs } from 'fs'
import url from 'url'
import { v4 as uuid } from 'uuid'
import { $, cd, question, fetch, chalk, ProcessOutput } from './index.mjs'
import { version } from './version.js'

const use = async (packageName, packageNameGlobal = '') => {
  if (packageNameGlobal == '') {
    packageNameGlobal = packageName
  }

  const __dirname = dirname(url.fileURLToPath(import.meta.url))

  if (global[packageNameGlobal]) {
    return global[packageNameGlobal]
  }

  let isExist = false
  try {
    isExist = (
      await fs.lstat(join(__dirname, 'node_modules', packageName))
    ).isDirectory()
  } catch (e) {}

  if (isExist) {
    Object.assign(global, {
      [packageNameGlobal]: import(packageName),
    })
  } else {
    const cwdPrev = $.cwd
    $.cwd = __dirname

    await $`npm install ${packageName}`

    $.cwd = cwdPrev

    Object.assign(global, {
      [packageNameGlobal]: import(packageName),
    })
  }

  return global[packageNameGlobal]
}

Object.assign(global, {
  $,
  cd,
  fetch,
  question,
  chalk,
  fs,
  os,
  use,
})

....
@antonmedv
Copy link
Collaborator

I thought about it, but this is a dangerous road. What about version, package-lock.json?

@antonmedv antonmedv changed the title package install and use at runtime Auto-install packages May 11, 2021
@OnurGvnc
Copy link
Author

OnurGvnc commented May 11, 2021

I thought about it, but this is a dangerous road. What about version, package-lock.json?

The version issue may be solved as follows.

#!/usr/bin/env zx

deps({
  yargs: '^17.0',
  chokidar: '^3.4.3',
  colorette: '1.2',
  shelljs: '^0.8.4',
})

const yargs = await use('yargs')
const { hideBin } = await use('yargs/helpers')

const argv = yargs(hideBin(process.argv)).argv

if (argv.ships > 3 && argv.distance < 53.5) {
  console.log('Plunder more riffiwobbles!')
} else {
  console.log('Retreat from the xupptumblers!')
}

console.log(argv)
`zx.mjs` experimental basic implementation
import { join, basename, dirname } from 'path'
import os, { tmpdir } from 'os'
import { promises as fs } from 'fs'
import url from 'url'
import { v4 as uuid } from 'uuid'
import { $, cd, question, fetch, chalk, ProcessOutput } from './index.mjs'
import { version } from './version.js'

let dependencies = {}

const deps = (deps) => {
  dependencies = deps
}

const use = async (packageNameOrPath) => {
  let packageName = packageNameOrPath

  const pathParts = packageName.split('/')

  if (packageName.startsWith('@') && pathParts.length > 1) {
    packageName = pathParts[0] + '/' + pathParts[1]
  } else if (pathParts.length > 1) {
    packageName = pathParts[0]
  }

  const __dirname = dirname(url.fileURLToPath(import.meta.url))

  let isExist = false
  try {
    isExist = (
      await fs.lstat(join(__dirname, 'node_modules', packageName))
    ).isDirectory()
  } catch (e) {}

  if (!isExist) {
    const cwdPrev = $.cwd
    $.cwd = __dirname

    let packageNameWithVersion = packageName
    if (dependencies[packageName]) {
      packageNameWithVersion = `${packageName}@${dependencies[packageName]}`
    }

    await $`npm install ${packageNameWithVersion}`

    $.cwd = cwdPrev
  }

  const p = await import(packageNameOrPath)
  return p.default ?? p
}

Object.assign(global, {
  $,
  cd,
  fetch,
  question,
  chalk,
  fs,
  os,
  use,
  deps,
})

....

I have no idea about package-lock.json

@antonmedv
Copy link
Collaborator

antonmedv commented May 11, 2021

This looks already complicated =)
What about distributing package-lock.json with your script and add next line to beginning of your script:

await $`npm i`

@andyrichardson
Copy link

How about support for imports using the http protocol?

Unversioned esmodule

import yargs from 'https://unpkg.com/yargs?module';

Versioned commonjs

import yargs from 'https://unpkg.com/yargs@17.0.1';

@mainrs
Copy link

mainrs commented May 12, 2021

I thought about it, but this is a dangerous road. What about version, package-lock.json?

I do agree with this. I think forking is probably the best thing to do if custom packages are wanted, at the end, it's a 5min change and merging back with upstream shouldn't be a problem in almost all cases.

Just as a side note: I had the same demand as I often use enquirer for scripts. I've gone ahead and used deno instead, which actually allows for remote imports natively.

@antonmedv
Copy link
Collaborator

I think this is out of the scope of the zx project. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants