Your IP : 3.147.42.34
const fs = require('fs/promises')
const { runInThisContext } = require('vm')
const { promisify } = require('util')
const { randomBytes } = require('crypto')
const { Module } = require('module')
const { dirname, basename } = require('path')
const { read } = require('read')
const files = {}
class PromZard {
#file = null
#backupFile = null
#ctx = null
#unique = randomBytes(8).toString('hex')
#prompts = []
constructor (file, ctx = {}, options = {}) {
this.#file = file
this.#ctx = ctx
this.#backupFile = options.backupFile
}
static async promzard (file, ctx, options) {
const pz = new PromZard(file, ctx, options)
return pz.load()
}
static async fromBuffer (buf, ctx, options) {
let filename = 0
do {
filename = '\0' + Math.random()
} while (files[filename])
files[filename] = buf
const ret = await PromZard.promzard(filename, ctx, options)
delete files[filename]
return ret
}
async load () {
if (files[this.#file]) {
return this.#loaded()
}
try {
files[this.#file] = await fs.readFile(this.#file, 'utf8')
} catch (er) {
if (er && this.#backupFile) {
this.#file = this.#backupFile
this.#backupFile = null
return this.load()
}
throw er
}
return this.#loaded()
}
async #loaded () {
const mod = new Module(this.#file, module)
mod.loaded = true
mod.filename = this.#file
mod.id = this.#file
mod.paths = Module._nodeModulePaths(dirname(this.#file))
this.#ctx.prompt = this.#makePrompt()
this.#ctx.__filename = this.#file
this.#ctx.__dirname = dirname(this.#file)
this.#ctx.__basename = basename(this.#file)
this.#ctx.module = mod
this.#ctx.require = (p) => mod.require(p)
this.#ctx.require.resolve = (p) => Module._resolveFilename(p, mod)
this.#ctx.exports = mod.exports
const body = `(function(${Object.keys(this.#ctx).join(', ')}) { ${files[this.#file]}\n })`
runInThisContext(body, this.#file).apply(this.#ctx, Object.values(this.#ctx))
this.#ctx.res = mod.exports
return this.#walk()
}
#makePrompt () {
return (...args) => {
let p, d, t
for (let i = 0; i < args.length; i++) {
const a = args[i]
if (typeof a === 'string') {
if (p) {
d = a
} else {
p = a
}
} else if (typeof a === 'function') {
t = a
} else if (a && typeof a === 'object') {
p = a.prompt || p
d = a.default || d
t = a.transform || t
}
}
try {
return `${this.#unique}-${this.#prompts.length}`
} finally {
this.#prompts.push([p, d, t])
}
}
}
async #walk (o = this.#ctx.res) {
const keys = Object.keys(o)
const len = keys.length
let i = 0
while (i < len) {
const k = keys[i]
const v = o[k]
i++
if (v && typeof v === 'object') {
o[k] = await this.#walk(v)
continue
}
if (v && typeof v === 'string' && v.startsWith(this.#unique)) {
const n = +v.slice(this.#unique.length + 1)
// default to the key
// default to the ctx value, if there is one
const [prompt = k, def = this.#ctx[k], tx] = this.#prompts[n]
try {
o[k] = await this.#prompt(prompt, def, tx)
} catch (er) {
if (er.notValid) {
// eslint-disable-next-line no-console
console.log(er.message)
i--
} else {
throw er
}
}
continue
}
if (typeof v === 'function') {
// XXX: remove v.length check to remove cb from functions
// would be a breaking change for `npm init`
// XXX: if cb is no longer an argument then this.#ctx should
// be passed in to allow arrow fns to be used and still access ctx
const fn = v.length ? promisify(v) : v
o[k] = await fn.call(this.#ctx)
// back up so that we process this one again.
// this is because it might return a prompt() call in the cb.
i--
continue
}
}
return o
}
async #prompt (prompt, def, tx) {
const res = await read({ prompt: prompt + ':', default: def }).then((r) => tx ? tx(r) : r)
// XXX: remove this to require throwing an error instead of
// returning it. would be a breaking change for `npm init`
if (res instanceof Error && res.notValid) {
throw res
}
return res
}
}
module.exports = PromZard.promzard
module.exports.fromBuffer = PromZard.fromBuffer
module.exports.PromZard = PromZard